diff options
| author | John Denker <jsd@av8n.com> | 2012-06-02 09:07:44 -0700 | 
|---|---|---|
| committer | John Denker <jsd@av8n.com> | 2012-06-02 17:30:23 -0700 | 
| commit | 32e75879103a943ac682cbde7b37ea4e4fbc24ab (patch) | |
| tree | e734de5ec0cb5ece57d0f5aa01f6731a11f86a9b | |
| parent | 9c9c853dd2c7b4b174fd435d092bb43e737b8b35 (diff) | |
add some 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 | 
