summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Denker <jsd@av8n.com>2012-06-02 09:07:44 -0700
committerJohn Denker <jsd@av8n.com>2012-06-02 17:30:23 -0700
commit32e75879103a943ac682cbde7b37ea4e4fbc24ab (patch)
treee734de5ec0cb5ece57d0f5aa01f6731a11f86a9b
parent9c9c853dd2c7b4b174fd435d092bb43e737b8b35 (diff)
add some tools
-rw-r--r--tools/hi-q.c275
-rw-r--r--tools/makefile32
-rw-r--r--tools/pido.c85
-rwxr-xr-xtools/qmail254
4 files changed, 646 insertions, 0 deletions
diff --git a/tools/hi-q.c b/tools/hi-q.c
new file mode 100644
index 0000000..31ec77b
--- /dev/null
+++ b/tools/hi-q.c
@@ -0,0 +1,275 @@
+///////////////
+// lightweight connection from qmail to filters e.g. spamassassin
+// (hi-q filter, get it?)
+
+// TODO: Panic stop should signal all children.
+// TODO: Possibly: Wait for all kids in parallel?
+// That's because they might finish out of order.
+
+#include <unistd.h>
+#include <stdlib.h> /* for malloc() */
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h> /* for fork(), wait() */
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+using namespace std;
+#include <string>
+#include <vector>
+
+// error exit codes, as stated in qmail.c
+const int ex_spam = 21;
+const int ex_syserr = 71;
+const int ex_comerr = 74;
+
+#define bufsize 16384
+
+void panic(const int sts) {
+ // FIXME: stop other children
+ exit(sts);
+}
+
+void slurp(const int inch, const int ouch){
+ char buf[bufsize];
+ ssize_t todo;
+ for (;;) {
+ ssize_t got = read(inch, buf, bufsize);
+ if (got == 0) { // EoF
+ break;
+ }
+ if (got < 0) {
+ fprintf(stderr, "hi-q: input error: ");
+ perror(0);
+ panic(ex_comerr);
+ }
+
+ todo = got;
+ while (todo) {
+ ssize_t sent = write(ouch, buf, todo);
+ if (sent < 0 && errno != EINTR) {
+ fprintf(stderr, "hi-q: output error: ");
+ perror(0);
+ panic(ex_comerr);
+ }
+ todo -= sent;
+ }
+ }
+}
+
+
+void probe_fd(){
+ int ii;
+ struct stat buf;
+ for (ii = 0; ii < 16; ii++) {
+ int rslt = fstat(ii, &buf);
+ fprintf(stderr, "fd %2d status %2d", ii, rslt);
+ if (rslt==0)
+ fprintf(stderr, " : %d", (int)buf.st_dev);
+ fprintf(stderr, "\n");
+ }
+ fprintf(stderr, "============\n");
+}
+
+
+void blurb(const int ii, const pid_t* kidpid) {
+ int kidstatus;
+ /*pid_t somekid = */ waitpid(kidpid[ii], &kidstatus, WUNTRACED);
+ if (WIFEXITED(kidstatus))
+ fprintf(stderr, "kid #%d (%d) exited with status %d\n",
+ ii, kidpid[ii], WEXITSTATUS(kidstatus));
+ if (WIFSIGNALED(kidstatus))
+ fprintf(stderr, "kid #%d (%d) killed by signal %d\n",
+ ii, kidpid[ii], WTERMSIG(kidstatus));
+
+}
+
+// We are fussy about the argument types because we want
+// this to compile cleanly under g++ as well as gcc,
+// and each is strict about different things, such that
+// one or the other will complain unless everything is
+// done just right.
+
+// This is the way execve really behaves:
+// the characters are held constant
+// and the (char*) pointers are held constant:
+int Execve(char const * fn,
+ char const * const * argv,
+ char const * const * env) {
+// coerce the arg types to match the unwise declaration in unistd.h :
+ return execve(fn, (char*const*) argv, (char*const*) env);
+}
+
+////////////////////////////////////////
+// we have data coming in on fd 0.
+// and control coming in on fd 1.
+
+int main(int argc, char** argv, char const * const * env) {
+ int kidstatus;
+ pid_t somekid;
+
+ int rslt;
+ int loose_end = 0;
+
+#ifdef SpareStuff
+ char* slurp2_args[] = {"/home/jsd/hack/slurp2", 0};
+ char* echo_args[] = {"/bin/echo", "hi there", 0};
+ char* wc_args[] = {"/usr/bin/wc", 0};
+ char* cat_args[] = {"/bin/cat", 0};
+ char* spama_args[] = {"/usr/local/bin/spamassassin", "-e", 0};
+ char* spamc_args[] = {"/usr/local/bin/spamc", "-Z", "7", 0};
+ char* qq_args[] = {"/var/qmail/bin/qmail-queue", 0};
+
+ const char* spamc_args[] = {"/usr/local/bin/spamc", "-Z", "7", 0};
+ const char* qq_args[] = {"/var/qmail/bin/qmail-queue", 0};
+
+
+ const char** joblist[] = {
+ cat_args,
+ slurp2_args,
+ 0 // required: zero terminates the list
+ };
+
+#endif
+
+ const char* spamc_args[] = {"/usr/local/bin/spamc", "-Z", "7", 0};
+ const char* qq_args[] = {"/var/qmail/bin/qmail-queue", 0};
+ const char** joblist[] = {
+ spamc_args,
+ qq_args,
+ 0 // required: zero terminates the list
+ };
+
+ vector<vector<string> > filters;
+
+ int nkids;
+ pid_t* kidpid; // indexed by kid number
+
+ for (nkids = 0; joblist[nkids]; nkids++) {} // count 'em
+ kidpid = (pid_t*) malloc(nkids * sizeof(pid_t));
+
+// At this point, there is some loop invariants;
+// (a) fd0 is open and ready for the next child to read, and
+// (b) fd1 is open but is something children can (and should)
+// throw away as soon as convenient.
+
+ {int ii; for (ii=0; joblist[ii]; ii++){ /* loop over all kids */
+ int datapipe[2];
+ int kid_end;
+ int lastkid = !joblist[ii+1];
+#define flip(a,b) (lastkid ? b : a)
+
+//xx fprintf(stderr, "Top of loop %d loose: %d\n", ii, loose_end);
+
+// Create a pipe, which will be used to connect
+// this child's fd1 to the next child's fd0 ...
+// except for the last kid, which reads fd1 rather
+// than writing it.
+ rslt = pipe(datapipe);
+ if (rslt < 0) {
+ fprintf(stderr, "hi-q: could not create datapipe: ");
+ perror(0);
+ panic(ex_syserr);
+ }
+
+//xx fprintf(stderr, "pipe: %d %d\n", datapipe[0], datapipe[1]);
+ if (loose_end) {
+ close(0);
+ dup2(loose_end, 0);
+ close(loose_end);
+ }
+
+ loose_end = datapipe[flip(0,1)];
+ kid_end = datapipe[flip(1,0)];
+
+ kidpid[ii] = fork();
+ if (!kidpid[ii]) { /*** child code ***/
+ const char ** prog;
+
+// Now that we are through creating pipes, get rid
+// of the placeholder:
+ close(1);
+
+ close(loose_end); // the reading end is none of our business
+
+ rslt = dup2(kid_end, 1);
+ if (rslt < 0) {
+ fprintf(stderr, "hi-q: dup2(kid(%d),1) failed: ", kid_end);
+ perror(0);
+ exit(ex_syserr);
+ }
+
+ close(kid_end); // use fd1 instead now
+ // OK, at this point we are set up to read fd0
+ // and write fd1 (except last kid reads fd1).
+//// probe_fd();
+
+ prog = joblist[ii];
+ rslt = Execve(prog[0], prog, env);
+ fprintf(stderr, "hi-q: failed to exec '%s': ", prog[0]);
+ perror(0);
+ exit(ex_syserr);
+ }
+
+ /*** parent code ***/
+ if (kidpid[ii] < 0) {
+ fprintf(stderr, "hi-q: failure to fork kid#%d: ", ii);
+ perror(0);
+ panic(ex_syserr);
+ }
+ close(kid_end);
+#ifdef more_testing
+ fprintf(stderr, "forked kid #%d (%d) piping %d\n",
+ ii, kidpid[ii], kid_end);
+// sleep(1); /* let kid run a while */
+#endif
+ }}
+
+// here with the whole pipeline of kids running
+
+ close(0); // the reading end of stdin was
+ // delegated to the first child
+
+
+ {int ii; for (ii=0; ii<nkids-1; ii++){ /* loop over N-1 kids */
+ /* not last kid */
+
+#ifdef testing
+ blurb(ii, kidpid);
+#else
+ somekid = waitpid(kidpid[ii], &kidstatus, WUNTRACED);
+ if (somekid) {} // avoid silly compiler warning
+ if (WIFEXITED(kidstatus)) {
+ if (WEXITSTATUS(kidstatus) == 1) panic(ex_spam);
+ if (WEXITSTATUS(kidstatus) != 0) panic(ex_syserr);
+ /* otherwise kidstatus==0 and we fall through */
+ }
+ else panic(ex_syserr); // any kill, not a normal exit
+#endif
+
+
+ }}
+
+//xx fprintf(stderr, "slurping %d %d\n", 1, loose_end);
+ slurp(1, loose_end);
+ close(1);
+ close(loose_end);
+
+ {
+ int ii = nkids-1;
+
+#ifdef moretesting
+fprintf(stderr, "About to wait for kid #%d (%d)\n",
+ ii, kidpid[ii]);
+ blurb(nkids-1, kidpid);
+#endif
+ somekid = waitpid(kidpid[ii], &kidstatus, WUNTRACED);
+ if (WIFEXITED(kidstatus)) return WEXITSTATUS(kidstatus);
+ }
+
+#ifdef testing
+ sleep(1);
+#endif
+ return ex_syserr; // any kill, not a normal exit
+
+}
diff --git a/tools/makefile b/tools/makefile
new file mode 100644
index 0000000..e87fa57
--- /dev/null
+++ b/tools/makefile
@@ -0,0 +1,32 @@
+CC= /usr/bin/g++ -Wall -g -I $(HOME)/lib/include
+
+
+#?? exhibits = checkpassword.patch hi-q.c pido.c pop3.conf smtp.conf \
+#?? smtp.rules spamc-zap.patch spamd qmail
+
+
+.PHONY : shipit clean list-src ALWAYS foo dirs setup imgs \
+ zip wc html all hacha hevea tcprules
+
+.SECONDARY : # do not remove any intermediate files
+
+all: pido hi-q
+
+install:
+ install pido hi-q /var/qmail/bin/
+
+/etc/tcpserver/smtp.rules :
+ install -d /etc/tcpserver
+ @echo '10.:allow,RELAYCLIENT=""' > $@
+ @echo '127.0.0.:allow,RELAYCLIENT=""' >> $@
+ @echo ':allow' >> $@
+
+tcprules : /etc/tcpserver/smtp.rules
+ < $< tcprules /etc/tcpserver/smtp.cdb /etc/tcpserver/smtp.tmp
+
+
+ALWAYS:
+ @echo ...
+
+##?? include $(chapters:.htm=.d)
+##?? include $(fancy:%.htm=aux/%.fig)
diff --git a/tools/pido.c b/tools/pido.c
new file mode 100644
index 0000000..9b92337
--- /dev/null
+++ b/tools/pido.c
@@ -0,0 +1,85 @@
+///////////////
+// pido
+// writes the pid, then execs something.
+//
+// Usage:
+// pido pidfile /absolute/path/prog progarg1 progarg2 ...
+
+// If the first arg is missing or is "-" or is ""
+// then we write to STDOUT.
+// If the first arg is "--" then we write to STDERR.
+// Otherwise the first arg is an ordinary filename.
+
+// Pido is important for scripts in init.d/ if they
+// want to start a pipeline.
+
+// If you use the command:
+// aa | bb | cc &
+// then $! returns the pid of cc.
+// Alas, aa and/or bb may be just as important as cc
+// (or even more important).
+// For example, cc might be a simple logging utility,
+// while aa is the daemon of interest.
+//
+// It is often true BUT NOT PROVABLY TRUE that the pid
+// of bb is $! minus 1, and the pid of aa is $! minus 2.
+
+// If you want something provably correct, do this:
+// pido /var/run/aa.pid /absolute/path/aa | bb | cc &
+// Then you know the correct pid of aa has been written
+// to the file aa.pid.
+
+// The perfect test for this is:
+// ./pido - ./pido -
+// which should print the same pid number twice.
+
+// There might be a way to do this with a simple shell script:
+// echo ... exec ...
+// but if there is, I haven't figured it out.
+
+
+#include <unistd.h>
+#include <stdlib.h> /* for exit() */
+#include <errno.h>
+#include <sys/types.h> /* for fork(), wait() */
+#include <stdio.h>
+
+////////////////////////////////////////
+// Here with data coming in on fd 0.
+// and control coming in on ft 1.
+
+int main(int argc, char* argv[], char* env[]) {
+
+ FILE* ouch = stdout;
+ int isfile = 1;
+ if (argc > 1) {
+ if (argv[1][0] == '-' || argv[1][0] == 0) {
+ isfile = 0;
+ if (argv[1][1] == '-') ouch = stderr;
+ }
+ if (isfile) {
+ ouch = fopen (argv[1], "w");
+ if (!ouch) {
+ fprintf(stderr, "pido: cannot open pidfile '%s': ", argv[1]);
+ perror(0);
+ exit (1);
+ }
+ }
+ }
+
+ fprintf(ouch, "%d\n", getpid());
+
+ if (isfile) fclose(ouch);
+
+ if (argc > 2) {
+ execve(argv[2], argv+2, env);
+ fprintf(stderr, "pido: failed to exec '%s': ", argv[2]);
+ perror(0);
+ for (int ii = 0; ii < argc; ii++){
+ fprintf(stderr, "'%s' ", argv[ii]);
+ }
+ fprintf(stderr, "\n");
+ exit(2);
+ }
+ return 0;
+}
diff --git a/tools/qmail b/tools/qmail
new file mode 100755
index 0000000..9b715ee
--- /dev/null
+++ b/tools/qmail
@@ -0,0 +1,254 @@
+#! /bin/sh
+#
+# Qmail-init-script
+#
+# chkconfig: 2345 90 10
+# description: Qmail, the low-impact mail server.
+
+PATH="/var/qmail/bin:/usr/local/sbin:/usr/local/bin:$PATH"
+export PATH
+mailhost=0 ## bind to all addresses, including localhost and ether
+tcps=$( which tcpserver )
+
+qmaild=$(id -u qmaild )
+nofiles=$(id -g qmaild )
+smtpconf=/etc/stunnel/smtp.conf
+pop3conf=/etc/stunnel/pop3.conf
+
+## Plain smtp never asks for a password,
+## and will not relay except from inside the firewall.
+## in accordance with /etc/tcpserver/smtp.rules.
+
+## SMTPS will relay for anybody who has a password.
+
+## The firewall ensures that plain pop3 is
+## accessible only on local-area-network, inside the firewall.
+
+# The following can be overridden by command-line
+# environment setting, e.g. pop3s=no ./qmail start
+: ${CKPW:=/bin/qmaild/checkpassword}
+: ${PIDO:=/var/qmail/bin/pido}
+
+banner="Starting Qmail MTA:"
+test -n "$mailhost" || mailhost=$(hostname -f )
+
+
+proc_running(){
+ proc=$1
+ if test -f /var/run/$proc.pid ; then
+ pid=$( cat /var/run/$proc.pid )
+ if test -n "pid" && 2>/dev/null kill -0 $pid ; then
+ return 0
+ fi
+ fi
+ return 1
+}
+
+## Note that this will EXIT if the proc can't start
+## within ten seconds
+## cutting off any chance of continuing toward partial success.
+proc_ok(){
+ proc=$1
+ ii=0
+ while test $ii -lt 10 ; do
+ if proc_running $proc ; then
+ echo " ok."
+ return
+ else
+ echo -n .
+ sleep 1
+ fi
+ : $((ii += 1))
+ done
+
+ echo "failed!"
+ exit 1
+}
+
+stop_proc() {
+ proc="$1"
+ sign="$2"
+ proc_running $proc || return 0
+ pid=$( cat /var/run/$proc.pid )
+ echo -n "Stopping $proc ($pid): "
+ kill -15 $sign$pid
+ ii=0
+ while test $ii -lt 10 ; do
+ if proc_running $proc ; then
+ echo -n .
+ sleep 1
+ else
+ echo "stopped."
+ return
+ fi
+ : $((ii += 1))
+ done
+ echo "Ooops, still running ($pid)."
+}
+
+stop_all() {
+ flag="$1"
+ ## first, the fancy server daemons:
+ test -z "${smtp%%[Nn0]*}" || stop_proc qmail-smtp ""
+ test -z "${smtps%%[Nn0]*}" || stop_proc qmail-smtps ""
+ test -z "${pop3%%[Nn0]*}" || stop_proc qmail-pop3 ""
+ test -z "${pop3s%%[Nn0]*}" || stop_proc qmail-pop3s ""
+ test -z "${send%%[Nn0]*}" || stop_proc qmail-send "$flag"
+}
+
+
+
+verb="$1";
+if test "$#" -gt 0 ; then shift ; fi
+
+for thing in $* ; do
+ case $thing in
+ send) send=yes ;;
+ smtp) smtp=yes ;;
+ smtps) smtps=yes ;;
+ pop3) pop3=yes ;;
+ pop3s) pop3s=yes ;;
+ *) 1>&2 echo "Extraneous verbiage '$thing'" ; exit 1 ;;
+ esac
+done
+
+
+if test -n "${smtp%%[Nn0]*}${smtps%%[Nn0]*}${pop3%%[Nn0]*}${pop3s%%[Nn0]*}${send%%[Nn0]*}" ; then
+: ${default:=no}
+else
+: ${default:=yes}
+fi
+: ${send:=$default}
+: ${smtps:=$default}
+: ${pop3s:=$default}
+: ${pop3:=$default} ## FIXME: disable this ASAP.
+: ${smtp:=$default}
+
+
+flag=""
+case "$verb" in
+ restart)
+ $0 stop $@
+ $0 start $@
+ ;;
+ start)
+ if test -n "${send%%[Nn0]*}" ; then
+ test -n "$banner" && echo $banner ; banner=""
+ proc=qmail-send
+ if proc_running $proc ; then
+ 1>&2 echo " Oops, $proc is already running ($pid)."
+ else
+ echo -n " qmail-send: "
+ /bin/rm -f /var/run/$proc.pid
+ qmail-start ./Maildir/ splogger qmail >/dev/null 2>&1 &
+ echo $! > /var/run/$proc.pid
+ proc_ok $proc
+ fi
+ fi
+
+ if test -n "${smtp%%[Nn0]*}" ; then
+ test -n "$banner" && echo $banner ; banner=""
+ proc=qmail-smtp
+ if proc_running $proc ; then
+ 1>&2 echo " Oops, $proc is already running ($pid)."
+ else
+ echo -n " SMTP-server "
+ /bin/rm -f /var/run/$proc.pid
+ ## xxxQMAILQUEUE=/var/qmail/bin/hi-q \
+ $PIDO /var/run/$proc.pid \
+ $tcps -R -x/etc/tcpserver/smtp.cdb \
+ -u$qmaild -g$nofiles $mailhost smtp \
+ qmail-smtpd 2>&1 \
+ | splogger smtp &
+ proc_ok $proc
+ fi
+ fi
+
+ if test -n "${smtps%%[Nn0]*}" ; then
+ test -n "$banner" && echo $banner ; banner=""
+ proc=qmail-smtps
+ if proc_running $proc ; then
+ 1>&2 echo " Oops, $proc is already running ($pid)."
+ else
+ echo -n " SMTPS-server "
+ /bin/rm -f /var/run/$proc.pid
+ $PIDO /var/run/$proc.pid \
+ $tcps -R -u$qmaild -g$nofiles $mailhost smtps \
+ stunnel $smtpconf 2>&1 \
+ | splogger smtps &
+ proc_ok $proc
+ fi
+ fi
+
+
+ if test -n "${pop3%%[Nn0]*}" ; then
+ test -n "$banner" && echo $banner ; banner=""
+ proc=qmail-pop3
+ if proc_running $proc ; then
+ 1>&2 echo " Oops, $proc is already running ($pid)."
+ else
+ echo -n " POP3-server "
+ /bin/rm -f /var/run/$proc.pid
+ $PIDO /var/run/$proc.pid \
+ $tcps -v -p -R $mailhost pop3 qmail-popup $mailhost \
+ $CKPW qmail-pop3d Maildir 2>&1 \
+ | splogger pop3 &
+ proc_ok $proc
+ fi
+ fi
+
+ if test -n "${pop3s%%[Nn0]*}" ; then
+ test -n "$banner" && echo $banner ; banner=""
+ proc=qmail-pop3s
+ if proc_running $proc ; then
+ 1>&2 echo " Oops, $proc is already running ($pid)."
+ else
+ echo -n " POP3S-server "
+ /bin/rm -f /var/run/$proc.pid
+ $PIDO /var/run/$proc.pid \
+ $tcps -v -p -R $mailhost pop3s \
+ stunnel $pop3conf 2>&1 | \
+ splogger pop3s &
+ proc_ok $proc
+ fi
+ fi
+ ;;
+
+ zap)
+ stop_all "-"
+ ;;
+
+ stop)
+ stop_all ""
+ ;;
+
+## cause qmail-send to reread a couple of control files:
+ reload)
+ echo -n "Reread control/locals, control/rcpthosts, and control/virtualdomains: "
+ if kill -1 $( cat /var/run/qmail-send.pid ) ; then
+ echo " ok."
+ else
+ echo " failed."
+ fi
+ ;;
+ status)
+ for thing in send smtp smtps pop3 pop3s ; do
+ xthing="qmail-$thing"
+ printf "%-12s : " "$xthing"
+ pid=$( cat /var/run/$xthing.pid 2>/dev/null )
+ : ${pid:=xxx}
+ if kill -0 "$pid" 2>/dev/null ; then
+ echo " up. $pid"
+ else
+ echo " down. $pid"
+ fi
+ done
+ if ! test -r '/etc/tcpserver/smtp.cdb' ; then
+ echo "Beware: /etc/tcpserver/smtp.cdb is missing."
+ fi
+ ;;
+ *)
+ echo "Usage: $0 {start|stop|reload|zap|restart|status}"
+ exit 1
+ ;;
+esac