From c4741c59ea5c65dde44a3ec7ac5464709fe42cae Mon Sep 17 00:00:00 2001 From: John Denker 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 cmd; + + jobber(const moder _mode, const vector _cmd) + : mode(_mode), cmd(_cmd) + {} + + jobber(const string _mode, const vector _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 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 post){ + for(vector::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 cmd; - - jobber(const moder _mode, const vector _cmd) - : mode(_mode), cmd(_cmd) - {} - - jobber(const string _mode, const vector _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