diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/hi-q.c | 275 | ||||
-rw-r--r-- | tools/makefile | 32 | ||||
-rw-r--r-- | tools/pido.c | 85 | ||||
-rwxr-xr-x | tools/qmail | 254 |
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 |