From c4741c59ea5c65dde44a3ec7ac5464709fe42cae Mon Sep 17 00:00:00 2001
From: John Denker <jsd@av8n.com>
Date: Fri, 20 Jul 2012 16:15:13 -0700
Subject: fix up error parsing and error logging

---
 tools/filters.conf  |   2 +
 tools/greylist.c    |  37 +++++---
 tools/hi-q.c        | 263 ++++++++++++++++++++++++++++++++++------------------
 tools/hi-test2.conf |   7 +-
 4 files changed, 206 insertions(+), 103 deletions(-)

diff --git a/tools/filters.conf b/tools/filters.conf
index dfd1180..3ef7524 100644
--- a/tools/filters.conf
+++ b/tools/filters.conf
@@ -3,3 +3,5 @@ series  /var/qmail/bin/skrewt
 stub    /var/qmail/bin/greylist
 sa      /usr/local/bin/spamc -Y 0 -s 1000000
 qq      /var/qmail/bin/qmail-queue
+
+postspam /var/qmail/bin/greylist -penalize 86400
diff --git a/tools/greylist.c b/tools/greylist.c
index 00272d8..d1ff1a4 100644
--- a/tools/greylist.c
+++ b/tools/greylist.c
@@ -51,7 +51,6 @@ void dump(const string var){
 }
 
 
-
 ////////////////
 // little utility to help with argument parsing:
 //
@@ -145,10 +144,13 @@ void scan(const string p, const int copies=1){
         int ac_age = now.tv_sec - mystat.st_atime;
         cout << setw(10) << time_out(mod_age)
                 << " " << setw(10) << time_out(ac_age);
-        if (mod_age < 0) {
+        if (0) {
+
+        } else if (mod_age < 0) {
           cout << " penalty";
-        }
-        else if (mod_age - ac_age < minimum_age    // early bird, or completely unused
+        } else if (mod_age < ac_age) {
+          cout << " parole";
+        } else if (mod_age - ac_age < minimum_age    // early bird, or completely unused
           && mod_age > probation) {           // did not diligently resubmit
           cout << " disprobation";
           if (mod_age != ac_age) cout << "!";
@@ -182,7 +184,7 @@ void whatsit::update(const string msg, const timeval new_mod,
   timeval pen_mod(new_mod);
   if (penalty) {
     pen_mod = now;
-    pen_mod.tv_sec += penalty;    
+    pen_mod.tv_sec += penalty;
   }
   timeval upd[2] = {
 // beware:  access illogically comes *before* modification here:
@@ -193,6 +195,8 @@ void whatsit::update(const string msg, const timeval new_mod,
 }
 
 int main(int _argc, char** _argv){
+  progname = *_argv;
+  mypid = getpid();
   int argc(_argc);
   char** argv(_argv);
   const string dirname("/var/qmail/greylist");
@@ -206,7 +210,8 @@ int main(int _argc, char** _argv){
       scanmode++;
     } else if (prefix(arg, "-copy"))  {
       copies++;
-    } else if (prefix(arg, "-penalize"))  {
+    } else if (prefix(arg, "-penalize")
+        || prefix(arg, "-penalty"))  {
       if (!argc){
         cerr << "Option '" << arg << "' requires an argument" << endl;
         exeunt(ex_syserr);
@@ -228,18 +233,24 @@ int main(int _argc, char** _argv){
 int whatsit::doit(const int penalty){
   char* ipvar = getenv("TCPREMOTEIP");
   if (!ipvar) {
-    cerr << progname << ": TCPREMOTEIP not set???" << endl;
+    cerr << progname
+      << "[" << mypid << "] "
+      << " TCPREMOTEIP not set???" << endl;
+    // should never happen
+    // although you can make it happen using a weird test-harness
     exeunt(ex_syserr);
   }
   ipbase = ipvar;
   char* hostvar = getenv("TCPREMOTEHOST");
   if (!hostvar) {
-    cerr << progname 
-     << ": from " << ipbase
-     << " ... TCPREMOTEHOST not set???" << endl;
+    cerr << progname
+      << "[" << mypid << "] "
+      << " from " << ipbase
+      << " ... TCPREMOTEHOST not set???" << endl;
     exeunt(ex_spam);
+  } else {
+    hostname = hostvar;
   }
-  hostname = hostvar;
 
 // see if our directory exists:
   struct stat dirstat;
@@ -289,6 +300,10 @@ int whatsit::doit(const int penalty){
     update("penalty box", mod_orig, now, penalty);
     exeunt(ex_spam);
   }
+  if (mod_age < ac_age){
+    update("paroled spammer", now, now, penalty);
+    exeunt(ex_greylisting);
+  }
   if (mod_age < minimum_age) {
     update("early bird", mod_orig, now, penalty);
     exeunt(ex_greylisting);
diff --git a/tools/hi-q.c b/tools/hi-q.c
index 369935e..f6b57e1 100644
--- a/tools/hi-q.c
+++ b/tools/hi-q.c
@@ -5,7 +5,7 @@
 // Hint:  For testing, see also hi-test.conf which invokes ./hi-test:
 //   ./hi-q hi-test.conf
 
-// TODO:  Panic stop should signal all children.
+// TODO:  Exeunt stop should signal all children.
 // TODO:  Possibly:  Wait for all kids in parallel?
 //      That's because they might finish out of order.
 
@@ -66,9 +66,128 @@ foo_sa(TOOBIG,       98,  "message was too big to process (see --max-size)"
 
 #define bufsize 16384
 
-void panic(const int sts) {
+// meanings:
+// sa is a filter, using not-very-expressive exit codes: 0=ham 1=spam.
+// stub is not a filter; no stdin or stdout; just looks at environment.
+// series is a filter.
+// qq is not a filter, just an absorber.
+//
+// Note that series and stub use the same exit codes as qq.
+//
+typedef enum {series, stub, sa, qq, postspam, fail} moder;
+
+class jobber{
+public:
+  moder mode;
+  vector<string> cmd;
+
+  jobber(const moder _mode, const vector<string> _cmd)
+  : mode(_mode), cmd(_cmd)
+  {}
+
+  jobber(const string _mode, const vector<string> _cmd)
+  : mode(fail), cmd(_cmd){
+    setmode(_mode);
+  }
+
+  jobber()
+  : mode(fail), cmd(0)
+  {}
+
+  void setmode(const string _mode) {
+    if (0) {}
+    else if (_mode == "sa")  mode = sa;
+    else if (_mode == "stub")  mode = stub;
+    else if (_mode == "series") mode = series;
+    else if (_mode == "qq") mode = qq;
+    else if (_mode == "postspam") mode = postspam;
+    else {
+      cerr << "jobber: bad mode: " << _mode << endl;
+      mode = fail;
+    }
+  }
+};
+
+// klugey global variable:
+vector<jobber> post;
+
+// 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);
+}
+
+int fork_and_wait(const jobber job){
+  pid_t kidpid = fork();
+  if (kidpid == -1) {
+    cerr << "hi-q: fork failed : ";
+    perror(0);
+    exit(ex_syserr);
+  }
+  int ntok = job.cmd.size();
+  const char* prog[1+ntok];
+  for (int jj = 0; jj < ntok; jj++){
+    prog[jj] = job.cmd[jj].c_str();
+  }
+  prog[ntok] = 0;
+
+  if (!kidpid){
+    /*** child code ***/
+    int rslt;
+    rslt = Execve(prog[0], prog, environ);
+    fprintf(stderr, "hi-q: failed to exec '%s': ", prog[0]);
+    perror(0);
+    exit(ex_syserr);
+  } else {
+    /*** parent code ***/
+    int kidstatus;
+    pid_t somekid;
+    somekid = waitpid(kidpid, &kidstatus, WUNTRACED);
+    if (WIFEXITED(kidstatus)) {
+      int sts = WEXITSTATUS(kidstatus);
+      if (sts != ex_good && sts != ex_spam) {
+        cerr << "hi-q: job " << prog[0]
+              << " unexpectedly returns status: " << sts 
+              << endl;
+        exit(sts);
+      }
+      return 0;
+    } else if (WIFSIGNALED(kidstatus)) {
+      int sig = WTERMSIG(kidstatus);
+      if (sig == SIGUSR1) {/* normal, no logging required */}
+      else cerr << "hi-q: job " << prog[0]
+                << " killed by signal " << sig << endl;
+      return(ex_syserr);
+    } else {
+      /* paused, not dead */
+    }
+  }
+  return 0;
+}
+
+int fork_and_wait(vector<jobber> post){
+  for(vector<jobber>::const_iterator foo = post.begin();
+    foo != post.end();  foo++) {
+    int rslt = fork_and_wait(*foo);      
+    if (rslt) return rslt;
+  }
+  return 0;
+}
+
+void exeunt(const int sts) {
   // FIXME: stop other children
-  cerr << "hi-q: panic called with " << sts << endl;
+  //xxxx cerr << "hi-q: exeunt called with " << sts << endl;
+  if (sts == ex_spam) fork_and_wait(post);
   exit(sts);
 }
 
@@ -83,7 +202,7 @@ void slurp(const int inch, const int ouch){
     if (got < 0) {
       fprintf(stderr, "hi-q: input error: ");
       perror(0);
-      panic(ex_comerr);
+      exeunt(ex_comerr);
     }
 
     todo = got;
@@ -92,7 +211,7 @@ void slurp(const int inch, const int ouch){
       if (sent < 0 && errno != EINTR) {
         fprintf(stderr, "hi-q: output error on fd%d : ", ouch);
         perror(0);
-        panic(ex_comerr);
+        exeunt(ex_comerr);
       }
       todo -= sent;
     }
@@ -126,22 +245,6 @@ void blurb(const int ii, const pid_t* kidpid) {
 
 }
 
-// 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);
-}
-
 void usage() {
   cerr << "Usage:\n"
 "  hi-q filter.conf\n"
@@ -172,47 +275,6 @@ int xclose(int arg){
 
 extern char** environ;
 
-// meanings:
-// sa is a filter, using not-very-expressive exit codes: 0=ham 1=spam.
-// stub is not a filter; no stdin or stdout; just looks at environment.
-// series is a filter.
-// qq is not a filter, just an absorber.
-//
-// Note that series and stub use the same exit codes as qq.
-//
-typedef enum {series, stub, sa, qq, fail} moder;
-
-class jobber{
-public:
-  moder mode;
-  vector<string> cmd;
-
-  jobber(const moder _mode, const vector<string> _cmd)
-  : mode(_mode), cmd(_cmd)
-  {}
-
-  jobber(const string _mode, const vector<string> _cmd)
-  : mode(fail), cmd(_cmd){
-    setmode(_mode);
-  }
-
-  jobber()
-  : mode(fail), cmd(0)
-  {}
-
-  void setmode(const string _mode) {
-    if (0) {}
-    else if (_mode == "sa")  mode = sa;
-    else if (_mode == "stub")  mode = stub;
-    else if (_mode == "series") mode = series;
-    else if (_mode == "qq") mode = qq;
-    else {
-      cerr << "jobber: bad mode: " << _mode << endl;
-      mode = fail;
-    }
-  }
-};
-
 int main(int argc, char** argv) {
   progname = *argv;
   mypid = getpid();
@@ -278,7 +340,14 @@ bar
       job.setmode(job.cmd.front());
       job.cmd.erase(job.cmd.begin());
     }
-    if (job.cmd.size()) filter.push_back(job);
+    // here with a properly built job descriptor
+    if (job.cmd.size()) {
+      if (job.mode == postspam) {
+        post.push_back(job);
+      } else {
+        filter.push_back(job);
+      }
+    }
   }
   unsigned int nkids = filter.size();
 
@@ -337,7 +406,7 @@ bar
       if (rslt < 0) {
         fprintf(stderr, "hi-q: could not create datapipe: ");
         perror(0);
-        panic(ex_syserr);
+        exeunt(ex_syserr);
       }
 
   //xx fprintf(stderr, "pipe: %d %d\n", datapipe[0], datapipe[1]);
@@ -451,7 +520,7 @@ bar
     if (kidpid[ii] < 0) {
       fprintf(stderr, "hi-q: failure to fork kid#%d: ", ii);
       perror(0);
-      panic(ex_syserr);
+      exeunt(ex_syserr);
     }
     close(kid_end);
 
@@ -509,11 +578,20 @@ bar
       // do not decrement the "alive" counter
       // since that only applies to non-special kids
       if (WIFEXITED(kidstatus)) {
-        cerr << "hi-q: special kid exited early" << endl;
-        return(ex_syserr);
-      } else if (WIFSIGNALED(kidstatus) && WTERMSIG(kidstatus) != SIGUSR1) {
-        cerr << "hi-q: special kid exited early" << endl;
+        cerr << "hi-q: special kid exited early, status " 
+               << WEXITSTATUS(kidstatus)
+               << "  with " << alive << " kids still alive"
+               << endl;
         return(ex_syserr);
+      } else if (WIFSIGNALED(kidstatus)) {
+        int sig = WTERMSIG(kidstatus);
+        if (sig == SIGUSR1) {/* normal, no logging required */}
+        else {
+          cerr << "hi-q: special kid killed by signal " 
+                  << sig << endl;
+          // this is not normal
+          return(ex_syserr);          
+        }
       } else {
         /* paused, not dead */
       }
@@ -542,42 +620,47 @@ bar
 
 ///////////////////
 // decode the best reason why the filter-chain terminated
+  //xx cerr << "cleanup: " << best_blame << endl;
   if (best_blame) {
     string short_name("");
     int kidno(iiofpid[argbest_blame]);
     if (WIFEXITED(best_blame)) {
-      string exword = "spam";           // default, for non-modern status codes
-      int excode = ex_spam;             // default, for non-modern status codes
+      string exword = "???";            // default, should never happen
+      int excode = ex_syserr;           // default, should never happen
       int sts = WEXITSTATUS(best_blame);
-      if (filter[kidno].mode != sa) {
-        exword = codemap[sts];
-        excode = sts;
-      }
-      if (exword.length()) {
-        cerr << "hi-q says: kid[" << kidno << "]"
-           << " pid " << argbest_blame
-           << " i.e. '" << filter[kidno].cmd[0] << "'"
-           << " reports " << exword << endl;
-        panic(excode);
-      }
-      if (sts != 0) {
-        cerr << "hi-q says: kid " << argbest_blame
-             << " exited with bad status: " << sts
-             << endl;
-        panic(ex_syserr);
-      } else {
+      if (sts == 0){
         // should never get here
         // should be no accounting for blame if there was no blame
         cerr << "hi-q: should never happen: no child to blame" << endl;
-        panic(ex_syserr);
+        exeunt(ex_syserr);
+      }
+
+      if (filter[kidno].mode != sa) {
+        exword = codemap[sts];
+        excode = sts;
+      } else {                          // here to translate spamc results
+        if (sts == 1) {
+          excode = ex_spam;
+          exword = "spam";
+        } else {
+          excode = ex_syserr;
+          stringstream foo;
+          foo << "bad status: " << sts;
+          exword = foo.str();
+        }
       }
+      cerr << "hi-q concludes: kid[" << kidno << "]"
+         << " pid " << argbest_blame
+         << " i.e. '" << filter[kidno].cmd[0] << "'"
+         << " reports " << exword << endl;
+      exeunt(excode);
     } else if (WIFSIGNALED(best_blame)) {
       int sig = WTERMSIG(best_blame);
       cerr << "hi-q says: kid " << argbest_blame
              << " was killed by signal " << sig
              << endl;
       // if the *best* blame is a kill, that's not normal
-      panic(ex_syserr);
+      exeunt(ex_syserr);
     }
   }
 
diff --git a/tools/hi-test2.conf b/tools/hi-test2.conf
index e8e4390..51d0361 100644
--- a/tools/hi-test2.conf
+++ b/tools/hi-test2.conf
@@ -1,3 +1,6 @@
-stub    hi-test x0 -snooze 10
+stub    hi-test x0 -snooze 2
+stub    hi-test x0 -snooze 1 -exit 0
 stub    greylist
-qq      hi-test x1 -snooze 1 -exit 3
+qq      hi-test x1 -snooze 3 -exit 3
+
+postspam /bin/echo post spam !
-- 
cgit v1.2.3