From 379794ea0d610165e75fca2c71e7161d66e0c10d Mon Sep 17 00:00:00 2001 From: John Denker Date: Thu, 19 Jul 2012 15:20:43 -0700 Subject: add greylist --- tools/greylist.c | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 tools/greylist.c (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c new file mode 100644 index 0000000..fa7d701 --- /dev/null +++ b/tools/greylist.c @@ -0,0 +1,5 @@ + + +int main(){ + return 0; +} -- cgit v1.2.3 From 84688a05a4430daf8dedf80bce35286aff4f4b1c Mon Sep 17 00:00:00 2001 From: John Denker Date: Thu, 19 Jul 2012 17:24:43 -0700 Subject: bare beginnings of a greylisting system --- .gitignore | 3 ++ tools/filters.conf | 1 + tools/greylist.c | 89 +++++++++++++++++++++++++++++++++++++++++- tools/hi-q.c | 111 +++++++++++++++++++++++++++++------------------------ 4 files changed, 152 insertions(+), 52 deletions(-) (limited to 'tools/greylist.c') diff --git a/.gitignore b/.gitignore index 3561ee3..ca1ef6d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.orig *.rej *.logg +*#[0-9]* has?????.h auto-gid @@ -151,7 +152,9 @@ checkpasswd/hasuserpw.h skrewt mail-scan hi-test +greylist +auto_uids.c control.tar.gz data.tar.gz dummy-mail-transfer-agent_all.deb diff --git a/tools/filters.conf b/tools/filters.conf index 8bc2efe..641b792 100644 --- a/tools/filters.conf +++ b/tools/filters.conf @@ -1,4 +1,5 @@ # configuration file for hi-q black /var/qmail/bin/skrewt +gray /var/qmail/bin/greylist black /usr/local/bin/spamc -Y 0 -s 1000000 qq /var/qmail/bin/qmail-queue diff --git a/tools/greylist.c b/tools/greylist.c index fa7d701..8adac05 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -1,5 +1,92 @@ +#include /* for exit(), getenv() */ +#include +#include +#include /* for stat() */ +#include /* for stat() */ +#include /* for stat() */ +#include /* for perror */ +#include /* for ENOENT */ +#include /* for ofstream() */ +#include /* for creat() */ +using namespace std; -int main(){ +const int sa_good = 0; +const int bug_bait_grey = 1; +// qmail_queue and spamc have similar interpretations here: +const int sa_syserr = 71; + +pid_t mypid; +string progname; + +void dump(const string var){ + char* str = getenv(var.c_str()); + cerr << progname + << "[" << mypid << "] " + << var; + if (str) cerr << " is set to '" << str << "'" << endl; + else cerr << " is not set." << endl; +} + +const string dirname("/var/qmail/greylist"); + + // int stat(const char *path, struct stat *buf); + // int fstat(int fd, struct stat *buf); + // int lstat(const char *path, struct stat *buf); + +int main(int argc, char** argv){ + mypid = getpid(); + progname = argv[0]; +// dump("TCPREMOTEIP"); +// dump("TCPREMOTEHOST"); + + char* ipvar = getenv("TCPREMOTEIP"); + if (!ipvar) { + cerr << progname << ": TCPREMOTEIP not set???" << endl; + exit(sa_syserr); + } + string ipbase = ipvar; + +// see if our directory exists: + struct stat dirstat; + int rslt = stat(dirname.c_str(), &dirstat); + if (rslt != 0){ + if (errno != ENOENT) { + cerr << progname << ": stat failed for '" + << dirname << "' : "; + perror(0); + } + rslt = mkdir(dirname.c_str(), 0755); + if (rslt != 0) { + cerr << progname + << "uid " << getuid() + << ": mkdir failed for '" + << dirname << "' : "; + perror(0); + exit(sa_syserr); + } + } + + string ipname = dirname + "/" + ipbase; + struct stat ipstat; + rslt = stat(ipname.c_str(), &ipstat); + if (rslt != 0){ + if (errno != ENOENT) { + cerr << progname << ": stat failed for '" + << ipname << "' : "; + perror(0); + } + ofstream foo; + int fd = creat(ipname.c_str(), 0644); + if (fd < 0){ + cerr << progname << ": create failed for '" + << ipname << "' : "; + perror(0); + } + close(fd); + return(bug_bait_grey); + } else { + cerr << "file exists: " << ipname << endl; + } return 0; } diff --git a/tools/hi-q.c b/tools/hi-q.c index 2ddc448..21724a1 100644 --- a/tools/hi-q.c +++ b/tools/hi-q.c @@ -11,7 +11,7 @@ #include #include /* for exit(), getenv() */ -#include +#include /* for perror */ #include #include /* for fork(), wait() */ #include @@ -29,6 +29,7 @@ using namespace std; // error exit codes, mostly as stated in qmail.c const int ex_good = 0; const int ex_spam = 21; +const int ex_grey = 70; const int ex_syserr = 71; const int ex_comerr = 74; @@ -139,7 +140,7 @@ int xclose(int arg){ extern char** environ; -typedef enum {gray, black, qq, fail} moder; +typedef enum {grey, black, qq, fail} moder; class jobber{ public: @@ -161,8 +162,8 @@ public: void setmode(const string _mode) { if (0) {} - else if (_mode == "gray") mode = gray; - else if (_mode == "grey") mode = gray; // variant spelling + else if (_mode == "gray") mode = grey; + else if (_mode == "grey") mode = grey; // variant spelling else if (_mode == "black") mode = black; else if (_mode == "qq") mode = qq; else { @@ -175,8 +176,8 @@ public: int main(int argc, char** argv) { progname = *argv; mypid = getpid(); - dump("TCPREMOTEIP"); - dump("TCPREMOTEHOST"); +// dump("TCPREMOTEIP"); +// dump("TCPREMOTEHOST"); int verbose(0); int kidstatus; @@ -270,41 +271,44 @@ int main(int argc, char** argv) { // to close it and dup() something useful onto it. map iiofpid; - for (unsigned int ii=0; ii < nkids; ii++){ /* loop starting all kids */ - int datapipe[2]; - int kid_end; + for (unsigned int ii=0; ii < nkids; ii++){ /* loop starting all kids */ //xx fprintf(stderr, "Top of loop %d loose: %d\n", ii, loose_end); - if (loose_end) { - close(0); - dup2(loose_end, 0); - close(loose_end); - } + int kid_end; + if (filter[ii].mode != grey){ + int datapipe[2]; -// 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 both fd0 and fd1, -// while writing nothing. + if (loose_end) { + close(0); + dup2(loose_end, 0); + close(loose_end); + } - rslt = pipe(datapipe); - if (rslt < 0) { - fprintf(stderr, "hi-q: could not create datapipe: "); - perror(0); - panic(ex_syserr); - } + // 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 both fd0 and fd1, + // while writing nothing. -//xx fprintf(stderr, "pipe: %d %d\n", datapipe[0], datapipe[1]); + rslt = pipe(datapipe); + if (rslt < 0) { + fprintf(stderr, "hi-q: could not create datapipe: "); + perror(0); + panic(ex_syserr); + } -// For N-1 kids, the loose end feeds forward. -// It will be written by this kid and read by the next kid. -// For the last kid, the loose end connects to hi-q. -// It will be written by hi-q and read by the last kid. + //xx fprintf(stderr, "pipe: %d %d\n", datapipe[0], datapipe[1]); - int lastkid = (ii == nkids-1); -#define flip(a,b) (lastkid ? b : a) - loose_end = datapipe[flip(rEnd, wEnd)]; - kid_end = datapipe[flip(wEnd, rEnd)]; + // For N-1 kids, the loose end feeds forward. + // It will be written by this kid and read by the next kid. + // For the last kid, the loose end connects to hi-q. + // It will be written by hi-q and read by the last kid. + + int lastkid = (ii == nkids-1); + #define flip(a,b) (lastkid ? b : a) + loose_end = datapipe[flip(rEnd, wEnd)]; + kid_end = datapipe[flip(wEnd, rEnd)]; + } kidpid[ii] = fork(); if (kidpid[ii] == -1) { @@ -358,24 +362,23 @@ int main(int argc, char** argv) { } } -// Now that we are through creating pipes, we don't -// need to continue blocking fd1: - close(1); - - close(loose_end); // the reading end is none of this kid's business - // except last kid: writing end + if (filter[ii].mode != grey){ + close(loose_end); // the reading end is none of this kid's business + // except last kid: writing end + + // Note this does an implicit close on the previously-open fd1: + rslt = dup2(kid_end, 1); // the writing end is stdout for this kid + // except last kid: nonstandard input + if (rslt < 0) { + fprintf(stderr, "hi-q: kid %d: dup2(%d,1) failed: ", ii, kid_end); + perror(0); + exit(ex_syserr); + } - rslt = dup2(kid_end, 1); // the writing end is stdout for this kid - // except last kid: nonstandard input - if (rslt < 0) { - fprintf(stderr, "hi-q: kid %d: dup2(%d,1) failed: ", ii, kid_end); - perror(0); - exit(ex_syserr); + close(kid_end); // use fd1 instead now + // OK, at this point this kid is set up to read fd0 and write fd1 + // (except last kid reads fd1 as well as fd0). } - - close(kid_end); // use fd1 instead now - // OK, at this point this kid is set up to read fd0 and write fd1 - // (except last kid reads fd1 as well as fd0). //// probe_fd(); int ntok = filter[ii].cmd.size(); @@ -499,14 +502,20 @@ int main(int argc, char** argv) { if (best_blame) { string short_name(""); int kidno(iiofpid[argbest_blame]); + string exword = "spam"; + int excode = ex_spam; + if (filter[kidno].mode == grey) { + exword = "greylisting"; + excode = ex_grey; + } if (WIFEXITED(best_blame)) { int sts = WEXITSTATUS(best_blame); if (sts == 1) { cerr << "hi-q says: kid[" << kidno << "]" << " pid " << argbest_blame << " i.e. '" << filter[kidno].cmd[0] << "'" - << " reports spam." << endl; - panic(ex_spam); + << " reports " << exword << endl; + panic(excode); } if (sts != 0) { cerr << "hi-q says: kid " << argbest_blame -- cgit v1.2.3 From 8a896e9b2bce51742d264f6a23c4cce544ec5af7 Mon Sep 17 00:00:00 2001 From: John Denker Date: Thu, 19 Jul 2012 18:55:57 -0700 Subject: might even be working : greylist --- tools/greylist.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 9 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index 8adac05..d769ff4 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -9,6 +9,8 @@ #include /* for ENOENT */ #include /* for ofstream() */ #include /* for creat() */ +#include /* for gettimeofday() */ + using namespace std; const int sa_good = 0; @@ -21,7 +23,7 @@ string progname; void dump(const string var){ char* str = getenv(var.c_str()); - cerr << progname + cerr << progname << "[" << mypid << "] " << var; if (str) cerr << " is set to '" << str << "'" << endl; @@ -34,12 +36,53 @@ const string dirname("/var/qmail/greylist"); // int fstat(int fd, struct stat *buf); // int lstat(const char *path, struct stat *buf); +const int minute(60); +const int hour(60*minute); +const int day(24*hour); + +class whatsit{ +public: + string progname; + pid_t mypid; + timeval now; + string ipname; + int mod_age; + int ac_age; + + whatsit(const string name) + : progname(name), mypid(getpid()) + { + gettimeofday(&now, NULL); + } + int doit(); +// access comes after modification: + void update(const string msg, const timeval new_mod, const timeval new_ac); +}; + +void whatsit::update(const string msg, const timeval new_mod, const timeval new_ac){ + cerr << progname << ": " + << msg << ": " << ipname + << " mod_age: " << mod_age + << " ac_age: " << ac_age + << endl; + timeval upd[2] = { +// beware: access illogically comes *before* modification here: + new_ac, + new_mod + }; + utimes(ipname.c_str(), upd); +} + int main(int argc, char** argv){ - mypid = getpid(); - progname = argv[0]; + // dump("TCPREMOTEIP"); // dump("TCPREMOTEHOST"); + whatsit foo(argv[0]); + return foo.doit(); +} + +int whatsit::doit(){ char* ipvar = getenv("TCPREMOTEIP"); if (!ipvar) { cerr << progname << ": TCPREMOTEIP not set???" << endl; @@ -66,8 +109,8 @@ int main(int argc, char** argv){ exit(sa_syserr); } } - - string ipname = dirname + "/" + ipbase; + + ipname = dirname + "/" + ipbase; struct stat ipstat; rslt = stat(ipname.c_str(), &ipstat); if (rslt != 0){ @@ -84,9 +127,23 @@ int main(int argc, char** argv){ perror(0); } close(fd); + update("new customer", now, now); return(bug_bait_grey); - } else { - cerr << "file exists: " << ipname << endl; } - return 0; -} +// here if stat succeeded + mod_age = now.tv_sec - ipstat.st_mtime; + ac_age = now.tv_sec - ipstat.st_atime; + timeval mod_orig = {ipstat.st_mtime, 0}; + if (mod_age < 5*minute) { + update("early bird", mod_orig, now); + return(bug_bait_grey); + } + if (ac_age < 32*day) { + update("returning customer", mod_orig, now); + return 0; + } + +// here if it is too old: + update("too old, starting over", now, now); + return(bug_bait_grey); +} \ No newline at end of file -- cgit v1.2.3 From 945767f12154698fab3e7e370486a5e9b09276e9 Mon Sep 17 00:00:00 2001 From: John Denker Date: Thu, 19 Jul 2012 20:58:24 -0700 Subject: add "Scan" function to greylist --- tools/greylist.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++---------- tools/makefile | 3 ++ 2 files changed, 77 insertions(+), 15 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index d769ff4..3ba502d 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -1,5 +1,6 @@ #include /* for exit(), getenv() */ #include +#include #include #include /* for stat() */ @@ -11,8 +12,16 @@ #include /* for creat() */ #include /* for gettimeofday() */ +// requires apt-get install libboost-filesystem-dev: +#include + using namespace std; +const int minute(60); +const int hour(60*minute); +const int day(24*hour); + +const int minimum_age(5*minute); const int sa_good = 0; const int bug_bait_grey = 1; // qmail_queue and spamc have similar interpretations here: @@ -30,27 +39,26 @@ void dump(const string var){ else cerr << " is not set." << endl; } -const string dirname("/var/qmail/greylist"); // int stat(const char *path, struct stat *buf); // int fstat(int fd, struct stat *buf); // int lstat(const char *path, struct stat *buf); -const int minute(60); -const int hour(60*minute); -const int day(24*hour); class whatsit{ public: + string dirname; string progname; pid_t mypid; timeval now; + string ipbase; string ipname; + string hostname; int mod_age; int ac_age; - whatsit(const string name) - : progname(name), mypid(getpid()) + whatsit(const string name, const string _dirname) + : dirname(_dirname), progname(name), mypid(getpid()), mod_age(0), ac_age(0) { gettimeofday(&now, NULL); } @@ -59,10 +67,49 @@ public: void update(const string msg, const timeval new_mod, const timeval new_ac); }; +void scan(const string p){ + timeval now; + gettimeofday(&now, NULL); + using namespace boost::filesystem; + + if (is_directory(p)) { + for (directory_iterator itr(p); itr!=directory_iterator(); ++itr) { + string basename = itr->path().filename(); + cout << setw(20) << basename << ' '; // display filename only + if (is_regular_file(itr->status())) { +// cout << " [" << file_size(itr->path()) << ']'; + struct stat mystat; + string fn = p + "/" + basename; + int rslt = stat(fn.c_str(), &mystat); + if (rslt != 0){ + cerr << progname << ": stat failed for '" + << fn << "' : "; + perror(0); + } + int mod_age = now.tv_sec - mystat.st_mtime; + int ac_age = now.tv_sec - mystat.st_atime; + cout << setw(10) << mod_age + << " " << setw(10) << ac_age; + if (mod_age < minimum_age) { + cout << " young"; + } else if (mod_age == ac_age) { + cout << " never used"; + } + } + cout << '\n'; + } + } + else { + // starting point is not a directory: + cout << (exists(p) ? "Found: " : "Not found: ") << p << '\n'; + } +} + void whatsit::update(const string msg, const timeval new_mod, const timeval new_ac){ cerr << progname << ": " - << msg << ": " << ipname - << " mod_age: " << mod_age + << msg << ": " << ipbase; + if (hostname.length()) cerr << " " << hostname; + cerr << " mod_age: " << mod_age << " ac_age: " << ac_age << endl; timeval upd[2] = { @@ -73,12 +120,22 @@ void whatsit::update(const string msg, const timeval new_mod, const timeval new_ utimes(ipname.c_str(), upd); } -int main(int argc, char** argv){ - -// dump("TCPREMOTEIP"); -// dump("TCPREMOTEHOST"); +int main(int _argc, char** _argv){ + int argc(_argc); + char** argv(_argv); + const string dirname("/var/qmail/greylist"); + whatsit foo(argv[0], dirname); argc--; argv++; + while (argc > 0) { + string arg = argv[0]; argc--; argv++; + if (arg == "-scan") { + scan(dirname); + return 0; + } + else { + cerr << "Unrecognized arg, ignored: " << arg << endl; + } + } - whatsit foo(argv[0]); return foo.doit(); } @@ -88,7 +145,9 @@ int whatsit::doit(){ cerr << progname << ": TCPREMOTEIP not set???" << endl; exit(sa_syserr); } - string ipbase = ipvar; + ipbase = ipvar; + char* hostvar = getenv("TCPREMOTEHOST"); + if (hostvar) hostname = hostvar; // see if our directory exists: struct stat dirstat; @@ -134,7 +193,7 @@ int whatsit::doit(){ mod_age = now.tv_sec - ipstat.st_mtime; ac_age = now.tv_sec - ipstat.st_atime; timeval mod_orig = {ipstat.st_mtime, 0}; - if (mod_age < 5*minute) { + if (mod_age < minimum_age) { update("early bird", mod_orig, now); return(bug_bait_grey); } diff --git a/tools/makefile b/tools/makefile index 8837952..97c345c 100644 --- a/tools/makefile +++ b/tools/makefile @@ -14,6 +14,9 @@ progs = pido hi-q skrewt hi-test mail-scan greylist all: $(progs) +greylist: greylist.c + $(CC) $< -lboost_filesystem-mt -o $@ + mail-scan: mail-scan.o $(CC) $< -lboost_regex -o $@ -- cgit v1.2.3 From 3413e333fd7ac49461cfbc89839a2b7e94ac2d3a Mon Sep 17 00:00:00 2001 From: John Denker Date: Thu, 19 Jul 2012 21:09:20 -0700 Subject: nicer formatting of ages --- tools/greylist.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index 3ba502d..613c1a4 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -11,6 +11,7 @@ #include /* for ofstream() */ #include /* for creat() */ #include /* for gettimeofday() */ +#include // requires apt-get install libboost-filesystem-dev: #include @@ -67,6 +68,17 @@ public: void update(const string msg, const timeval new_mod, const timeval new_ac); }; +string time_out(const int ttt){ + int sec(ttt % 60); + int min((ttt / 60) % 60); + int hr(ttt / 3600); + stringstream foo; + foo << hr + << ":" << setw(2) << setfill('0') << min + << ":" << setw(2) << setfill('0') << sec; + return foo.str(); +} + void scan(const string p){ timeval now; gettimeofday(&now, NULL); @@ -88,12 +100,12 @@ void scan(const string p){ } int mod_age = now.tv_sec - mystat.st_mtime; int ac_age = now.tv_sec - mystat.st_atime; - cout << setw(10) << mod_age - << " " << setw(10) << ac_age; + cout << setw(10) << time_out(mod_age) + << " " << setw(10) << time_out(ac_age); if (mod_age < minimum_age) { cout << " young"; } else if (mod_age == ac_age) { - cout << " never used"; + cout << " unused"; } } cout << '\n'; @@ -109,8 +121,8 @@ void whatsit::update(const string msg, const timeval new_mod, const timeval new_ cerr << progname << ": " << msg << ": " << ipbase; if (hostname.length()) cerr << " " << hostname; - cerr << " mod_age: " << mod_age - << " ac_age: " << ac_age + cerr << " mod_age: " << time_out(mod_age) + << " ac_age: " << time_out(ac_age) << endl; timeval upd[2] = { // beware: access illogically comes *before* modification here: -- cgit v1.2.3 From 32994e67f359a556332ad5409063749772cbc206 Mon Sep 17 00:00:00 2001 From: John Denker Date: Thu, 19 Jul 2012 23:27:34 -0700 Subject: implement probation period --- tools/greylist.c | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index 613c1a4..1e62e93 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -23,6 +23,8 @@ const int hour(60*minute); const int day(24*hour); const int minimum_age(5*minute); +const int maximum_age(32*day); +const int probation(4*hour); const int sa_good = 0; const int bug_bait_grey = 1; // qmail_queue and spamc have similar interpretations here: @@ -73,9 +75,16 @@ string time_out(const int ttt){ int min((ttt / 60) % 60); int hr(ttt / 3600); stringstream foo; - foo << hr - << ":" << setw(2) << setfill('0') << min - << ":" << setw(2) << setfill('0') << sec; + int didsome(0); + if (hr) { + foo << hr << ":"; + didsome++; + } + if (didsome || min){ + foo << setw(didsome?2:1) << setfill('0') << min << ":"; + didsome++; + } + foo << setw(didsome?2:1) << setfill('0') << sec; return foo.str(); } @@ -102,10 +111,18 @@ void scan(const string p){ 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 < minimum_age) { + 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 << "!"; + } else if (mod_age < minimum_age) { cout << " young"; } else if (mod_age == ac_age) { cout << " unused"; + } else if (mod_age > maximum_age) { + cout << " expired"; + } else { + cout << " OK"; } } cout << '\n'; @@ -209,12 +226,16 @@ int whatsit::doit(){ update("early bird", mod_orig, now); return(bug_bait_grey); } - if (ac_age < 32*day) { - update("returning customer", mod_orig, now); - return 0; + if (mod_age - ac_age < minimum_age // early bird, or completely unused + && mod_age > probation) { // did not diligently resubmit + update("disprobation", now, now); + return(bug_bait_grey); } - -// here if it is too old: - update("too old, starting over", now, now); - return(bug_bait_grey); -} \ No newline at end of file + if (ac_age > maximum_age) { + update("too old, starting over", now, now); + return(bug_bait_grey); + } +// if all checks are passed, must be OK: + update("returning customer", mod_orig, now); + return 0; +} -- cgit v1.2.3 From 9280a33c63250b841e8a51d4ef3aac2148b4bc12 Mon Sep 17 00:00:00 2001 From: John Denker Date: Fri, 20 Jul 2012 05:43:54 -0700 Subject: 5 minutes is not enough --- qmail.c | 2 +- tools/greylist.c | 2 +- tools/makefile | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) (limited to 'tools/greylist.c') diff --git a/qmail.c b/qmail.c index 476c788..a8ddef8 100644 --- a/qmail.c +++ b/qmail.c @@ -122,7 +122,7 @@ struct qmail *qq; case 65: /* fall through */ case 66: /* fall through */ case 62: return "Zqq trouble creating files in queue (#4.3.0)"; - case 70: return "Zgreylisting in progress; please try again in 00:05:00 (#4.3.0)"; + case 70: return "Zgreylisting in progress; please try again in 00:15:00 (#4.3.0)"; case 71: return "Zmail server temporarily rejected message (#4.3.0)"; case 72: return "Zconnection to mail server timed out (#4.4.1)"; case 73: return "Zconnection to mail server rejected (#4.4.1)"; diff --git a/tools/greylist.c b/tools/greylist.c index 1e62e93..525fc9b 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -22,7 +22,7 @@ const int minute(60); const int hour(60*minute); const int day(24*hour); -const int minimum_age(5*minute); +const int minimum_age(15*minute); const int maximum_age(32*day); const int probation(4*hour); const int sa_good = 0; diff --git a/tools/makefile b/tools/makefile index 97c345c..3e599b8 100644 --- a/tools/makefile +++ b/tools/makefile @@ -43,6 +43,11 @@ install: logmark: logger -t jsd -p mail.info ========================= +todo: + echo zap -- kill program group \ + extended error codes from skrewt, greylist \ + extended error codes [-x] from spamc + ALWAYS: @echo ... -- cgit v1.2.3 From abcd53ddce872e3f331d8a6d7c1ff44c070b91b0 Mon Sep 17 00:00:00 2001 From: John Denker Date: Fri, 20 Jul 2012 09:20:36 -0700 Subject: greylist will now zap its program-group --- tools/greylist.c | 46 +++++++++++++++++++++++++++++++++++----------- tools/hi-test2.conf | 2 +- tools/makefile | 2 +- 3 files changed, 37 insertions(+), 13 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index 525fc9b..910b40c 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -11,7 +11,8 @@ #include /* for ofstream() */ #include /* for creat() */ #include /* for gettimeofday() */ -#include +#include /* for stringstream */ +#include /* for kill(), SIGUSR1 */ // requires apt-get install libboost-filesystem-dev: #include @@ -43,10 +44,32 @@ void dump(const string var){ } - // int stat(const char *path, struct stat *buf); - // int fstat(int fd, struct stat *buf); - // int lstat(const char *path, struct stat *buf); +//////////////// +// little utility to help with argument parsing: +// +int prefix(const string shorter, const string longer){ + return shorter == longer.substr(0, shorter.length()); +} + +void exeunt(const int sts){ + if (sts == sa_good) exit(sts); + + const char* foo = getenv("HI_Q_GROUP"); + if (!foo) exit(sts); + +// No point in signalling ourself: + sighandler_t rslt = signal(SIGUSR1, SIG_IGN); + if (rslt == SIG_ERR) { + cerr << "error setting signal" << endl; + } + int k = kill(-atoi(foo), SIGUSR1); + if (k) { + cerr << "kill failed on group " << atoi(foo) << " ... "; + perror(0); + } + exit(sts); +} class whatsit{ public: @@ -161,7 +184,8 @@ int main(int _argc, char** _argv){ return 0; } else { - cerr << "Unrecognized arg, ignored: " << arg << endl; + cerr << "Unrecognized arg: " << arg << endl; + exeunt(sa_syserr); } } @@ -172,7 +196,7 @@ int whatsit::doit(){ char* ipvar = getenv("TCPREMOTEIP"); if (!ipvar) { cerr << progname << ": TCPREMOTEIP not set???" << endl; - exit(sa_syserr); + exeunt(sa_syserr); } ipbase = ipvar; char* hostvar = getenv("TCPREMOTEHOST"); @@ -194,7 +218,7 @@ int whatsit::doit(){ << ": mkdir failed for '" << dirname << "' : "; perror(0); - exit(sa_syserr); + exeunt(sa_syserr); } } @@ -216,7 +240,7 @@ int whatsit::doit(){ } close(fd); update("new customer", now, now); - return(bug_bait_grey); + exeunt(bug_bait_grey); } // here if stat succeeded mod_age = now.tv_sec - ipstat.st_mtime; @@ -224,16 +248,16 @@ int whatsit::doit(){ timeval mod_orig = {ipstat.st_mtime, 0}; if (mod_age < minimum_age) { update("early bird", mod_orig, now); - return(bug_bait_grey); + exeunt(bug_bait_grey); } if (mod_age - ac_age < minimum_age // early bird, or completely unused && mod_age > probation) { // did not diligently resubmit update("disprobation", now, now); - return(bug_bait_grey); + exeunt(bug_bait_grey); } if (ac_age > maximum_age) { update("too old, starting over", now, now); - return(bug_bait_grey); + exeunt(bug_bait_grey); } // if all checks are passed, must be OK: update("returning customer", mod_orig, now); diff --git a/tools/hi-test2.conf b/tools/hi-test2.conf index c7312fb..3c1422c 100644 --- a/tools/hi-test2.conf +++ b/tools/hi-test2.conf @@ -1,3 +1,3 @@ grey hi-test x0 -snooze 10 -gray greylist x2 -snooze 10 +gray greylist qq hi-test x1 -snooze 1 -exit 3 diff --git a/tools/makefile b/tools/makefile index 3e599b8..cf62473 100644 --- a/tools/makefile +++ b/tools/makefile @@ -44,7 +44,7 @@ logmark: logger -t jsd -p mail.info ========================= todo: - echo zap -- kill program group \ + echo zap penalize greylist status of spam \ extended error codes from skrewt, greylist \ extended error codes [-x] from spamc -- cgit v1.2.3 From 4fad56112022d60688e52fa75261785b51213831 Mon Sep 17 00:00:00 2001 From: John Denker Date: Fri, 20 Jul 2012 10:09:44 -0700 Subject: format stuff in columns --- tools/columns | 33 +++++++++++++++++++++++++++++++++ tools/greylist.c | 19 +++++++++++++------ 2 files changed, 46 insertions(+), 6 deletions(-) create mode 100755 tools/columns (limited to 'tools/greylist.c') diff --git a/tools/columns b/tools/columns new file mode 100755 index 0000000..dcc934f --- /dev/null +++ b/tools/columns @@ -0,0 +1,33 @@ +#! /usr/bin/perl -w + +## not very scalable; stores entire document in memory + +use strict; +use List::Util qw[min max]; + +main:{ +## leftmost column is column ZERO + my @cwid = (); # width of column + + my @store = (); + + while (my $line = <>){ + chomp $line; + my @stuff = split(' ', $line); + for (my $ii = 0; $ii < 0+@stuff; $ii++){ + my $old = $cwid[$ii] || 0; + $cwid[$ii] = max(length($stuff[$ii]), $old); + } + push @store, \@stuff; + } + foreach my $line (@store) { + my @stuff = @$line; + for (my $ii = 0; $ii < 0+@stuff; $ii++){ + if ($ii) { + print " "; + } + printf("\%-$cwid[$ii]s", $stuff[$ii]); + } + print "\n"; + } +} \ No newline at end of file diff --git a/tools/greylist.c b/tools/greylist.c index 910b40c..1745b32 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -111,7 +111,7 @@ string time_out(const int ttt){ return foo.str(); } -void scan(const string p){ +void scan(const string p, const int copies=1){ timeval now; gettimeofday(&now, NULL); using namespace boost::filesystem; @@ -119,7 +119,8 @@ void scan(const string p){ if (is_directory(p)) { for (directory_iterator itr(p); itr!=directory_iterator(); ++itr) { string basename = itr->path().filename(); - cout << setw(20) << basename << ' '; // display filename only + for (int ii = 0; ii < copies; ii++) + cout << setw(20) << left << basename << ' '; // display filename only if (is_regular_file(itr->status())) { // cout << " [" << file_size(itr->path()) << ']'; struct stat mystat; @@ -177,17 +178,23 @@ int main(int _argc, char** _argv){ char** argv(_argv); const string dirname("/var/qmail/greylist"); whatsit foo(argv[0], dirname); argc--; argv++; + int scanmode(0); + int copies(1); while (argc > 0) { string arg = argv[0]; argc--; argv++; if (arg == "-scan") { - scan(dirname); - return 0; - } - else { + scanmode++; + } else if (arg == "-copy") { + copies++; + } else { cerr << "Unrecognized arg: " << arg << endl; exeunt(sa_syserr); } } + if (scanmode) { + scan(dirname, copies); + return 0; + } return foo.doit(); } -- cgit v1.2.3 From e9b59f501b23b53eb6f230e0dfc4d50bb6995d45 Mon Sep 17 00:00:00 2001 From: John Denker Date: Fri, 20 Jul 2012 11:05:02 -0700 Subject: add some exit-code processing; require TCPREMOTEHOST --- tools/greylist.c | 39 ++++++++++++++++++++----------- tools/hi-q.c | 67 +++++++++++++++++++++++++++++++++++++++++------------- tools/hi-test.conf | 2 +- 3 files changed, 78 insertions(+), 30 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index 1745b32..1320257 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -26,10 +26,17 @@ const int day(24*hour); const int minimum_age(15*minute); const int maximum_age(32*day); const int probation(4*hour); -const int sa_good = 0; -const int bug_bait_grey = 1; -// qmail_queue and spamc have similar interpretations here: -const int sa_syserr = 71; + +// error exit codes, mostly as stated in qmail.c +#define foo(name, num) const int ex_ ## name = num +#define bar foo(good, 0) ;\ +foo(spam, 21) ;\ +foo(greylisting, 70) ;\ +foo(syserr, 71) ;\ +foo(comerr, 74) ; + +bar +#undef foo pid_t mypid; string progname; @@ -53,7 +60,7 @@ int prefix(const string shorter, const string longer){ } void exeunt(const int sts){ - if (sts == sa_good) exit(sts); + if (sts == ex_good) exit(sts); const char* foo = getenv("HI_Q_GROUP"); if (!foo) exit(sts); @@ -188,7 +195,7 @@ int main(int _argc, char** _argv){ copies++; } else { cerr << "Unrecognized arg: " << arg << endl; - exeunt(sa_syserr); + exeunt(ex_syserr); } } if (scanmode) { @@ -203,11 +210,17 @@ int whatsit::doit(){ char* ipvar = getenv("TCPREMOTEIP"); if (!ipvar) { cerr << progname << ": TCPREMOTEIP not set???" << endl; - exeunt(sa_syserr); + exeunt(ex_syserr); } ipbase = ipvar; char* hostvar = getenv("TCPREMOTEHOST"); - if (hostvar) hostname = hostvar; + if (!hostvar) { + cerr << progname + << ": from " << ipbase + << " ... TCPREMOTEHOST not set???" << endl; + exeunt(ex_spam); + } + hostname = hostvar; // see if our directory exists: struct stat dirstat; @@ -225,7 +238,7 @@ int whatsit::doit(){ << ": mkdir failed for '" << dirname << "' : "; perror(0); - exeunt(sa_syserr); + exeunt(ex_syserr); } } @@ -247,7 +260,7 @@ int whatsit::doit(){ } close(fd); update("new customer", now, now); - exeunt(bug_bait_grey); + exeunt(ex_greylisting); } // here if stat succeeded mod_age = now.tv_sec - ipstat.st_mtime; @@ -255,16 +268,16 @@ int whatsit::doit(){ timeval mod_orig = {ipstat.st_mtime, 0}; if (mod_age < minimum_age) { update("early bird", mod_orig, now); - exeunt(bug_bait_grey); + exeunt(ex_greylisting); } if (mod_age - ac_age < minimum_age // early bird, or completely unused && mod_age > probation) { // did not diligently resubmit update("disprobation", now, now); - exeunt(bug_bait_grey); + exeunt(ex_greylisting); } if (ac_age > maximum_age) { update("too old, starting over", now, now); - exeunt(bug_bait_grey); + exeunt(ex_greylisting); } // if all checks are passed, must be OK: update("returning customer", mod_orig, now); diff --git a/tools/hi-q.c b/tools/hi-q.c index 21724a1..f195508 100644 --- a/tools/hi-q.c +++ b/tools/hi-q.c @@ -27,11 +27,42 @@ using namespace std; #include // error exit codes, mostly as stated in qmail.c -const int ex_good = 0; -const int ex_spam = 21; -const int ex_grey = 70; -const int ex_syserr = 71; -const int ex_comerr = 74; +#define bar \ +foo(good, 0) ;\ +foo(spam, 21) ;\ +foo(permerr, 31) ;\ +foo(greylisting, 70) ;\ +foo(syserr, 71) ;\ +foo(comerr, 74) ; + +#define foo(name, num) const int ex_ ## name = num +bar +#undef foo + +map codemap; + + +#define bar_sa \ +foo_sa(GOOD, 0, "ham") ;\ +foo_sa(SPAM, 1, "spam") ;\ +foo_sa(USAGE, 64, "command line usage error") ;\ +foo_sa(DATAERR, 65, "data format error") ;\ +foo_sa(NOINPUT, 66, "cannot open input") ;\ +foo_sa(NOUSER, 67, "addressee unknown") ;\ +foo_sa(NOHOST, 68, "host name unknown") ;\ +foo_sa(UNAVAILABLE, 69, "service unavailable") ;\ +foo_sa(SOFTWARE, 70, "internal software error") ;\ +foo_sa(OSERR, 71, "system error (e.g., can't fork)") ;\ +foo_sa(OSFILE, 72, "critical OS file missing") ;\ +foo_sa(CANTCREAT, 73, "can't create (user) output file") ;\ +foo_sa(IOERR, 74, "input/output error") ;\ +foo_sa(TEMPFAIL, 75, "temp failure; user is invited to retry") ;\ +foo_sa(PROTOCOL, 76, "remote error in protocol") ;\ +foo_sa(NOPERM, 77, "permission denied") ;\ +foo_sa(CONFIG, 78, "configuration error") ;\ +foo_sa(TOOBIG, 98, "message was too big to process (see --max-size)" + + #define bufsize 16384 @@ -176,8 +207,11 @@ public: int main(int argc, char** argv) { progname = *argv; mypid = getpid(); -// dump("TCPREMOTEIP"); -// dump("TCPREMOTEHOST"); + +#define foo(name, num) codemap[num] = #name ; +bar +#undef foo + int verbose(0); int kidstatus; @@ -502,15 +536,15 @@ int main(int argc, char** argv) { if (best_blame) { string short_name(""); int kidno(iiofpid[argbest_blame]); - string exword = "spam"; - int excode = ex_spam; - if (filter[kidno].mode == grey) { - exword = "greylisting"; - excode = ex_grey; - } if (WIFEXITED(best_blame)) { + string exword = "spam"; // default, for non-modern status codes + int excode = ex_spam; // default, for non-modern status codes int sts = WEXITSTATUS(best_blame); - if (sts == 1) { + if (filter[kidno].mode == grey) { + exword = codemap[sts]; + excode = sts; + } + if (exword.length()) { cerr << "hi-q says: kid[" << kidno << "]" << " pid " << argbest_blame << " i.e. '" << filter[kidno].cmd[0] << "'" @@ -523,8 +557,9 @@ int main(int argc, char** argv) { << endl; panic(ex_syserr); } else { - // should never get here unless exit status was nonzero - cerr << "hi-q: should never happen" << endl; + // 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); } } else if (WIFSIGNALED(best_blame)) { diff --git a/tools/hi-test.conf b/tools/hi-test.conf index d400373..a89640d 100644 --- a/tools/hi-test.conf +++ b/tools/hi-test.conf @@ -2,5 +2,5 @@ # another comment, with blank line between black hi-test x0 -snooze 10 -black hi-test x1 -snooze 1 -exit 1 -kill +black hi-test x1 -snooze 1 -exit 2 -kill qq hi-test x2 -snooze 10 -- cgit v1.2.3 From 5d8d6c4de1940413f42e7b1c913db4b233606146 Mon Sep 17 00:00:00 2001 From: John Denker Date: Fri, 20 Jul 2012 12:50:55 -0700 Subject: builing some features to penalize spammers, by pushing the greylisting barrier into the future --- tools/greylist.c | 51 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 14 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index 1320257..00272d8 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -95,17 +95,20 @@ public: { gettimeofday(&now, NULL); } - int doit(); + int doit(const int penalty=0); // access comes after modification: - void update(const string msg, const timeval new_mod, const timeval new_ac); + void update(const string msg, const timeval new_mod, + const timeval new_ac, const int penalty=0); }; -string time_out(const int ttt){ +string time_out(const int _ttt){ + int ttt(abs(_ttt)); int sec(ttt % 60); int min((ttt / 60) % 60); int hr(ttt / 3600); stringstream foo; int didsome(0); + if (_ttt < 0) foo << "-"; if (hr) { foo << hr << ":"; didsome++; @@ -142,7 +145,10 @@ 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 - ac_age < minimum_age // early bird, or completely unused + if (mod_age < 0) { + cout << " penalty"; + } + 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 << "!"; @@ -165,17 +171,23 @@ void scan(const string p, const int copies=1){ } } -void whatsit::update(const string msg, const timeval new_mod, const timeval new_ac){ +void whatsit::update(const string msg, const timeval new_mod, + const timeval new_ac, const int penalty){ cerr << progname << ": " << msg << ": " << ipbase; if (hostname.length()) cerr << " " << hostname; cerr << " mod_age: " << time_out(mod_age) << " ac_age: " << time_out(ac_age) << endl; + timeval pen_mod(new_mod); + if (penalty) { + pen_mod = now; + pen_mod.tv_sec += penalty; + } timeval upd[2] = { // beware: access illogically comes *before* modification here: new_ac, - new_mod + pen_mod }; utimes(ipname.c_str(), upd); } @@ -187,12 +199,19 @@ int main(int _argc, char** _argv){ whatsit foo(argv[0], dirname); argc--; argv++; int scanmode(0); int copies(1); + int penalty(0); while (argc > 0) { string arg = argv[0]; argc--; argv++; - if (arg == "-scan") { + if (prefix(arg, "-scan")) { scanmode++; - } else if (arg == "-copy") { + } else if (prefix(arg, "-copy")) { copies++; + } else if (prefix(arg, "-penalize")) { + if (!argc){ + cerr << "Option '" << arg << "' requires an argument" << endl; + exeunt(ex_syserr); + } + penalty = atoi(*argv++); argc--; } else { cerr << "Unrecognized arg: " << arg << endl; exeunt(ex_syserr); @@ -203,10 +222,10 @@ int main(int _argc, char** _argv){ return 0; } - return foo.doit(); + return foo.doit(penalty); } -int whatsit::doit(){ +int whatsit::doit(const int penalty){ char* ipvar = getenv("TCPREMOTEIP"); if (!ipvar) { cerr << progname << ": TCPREMOTEIP not set???" << endl; @@ -266,20 +285,24 @@ int whatsit::doit(){ mod_age = now.tv_sec - ipstat.st_mtime; ac_age = now.tv_sec - ipstat.st_atime; timeval mod_orig = {ipstat.st_mtime, 0}; + if (mod_age < 0) { + update("penalty box", mod_orig, now, penalty); + exeunt(ex_spam); + } if (mod_age < minimum_age) { - update("early bird", mod_orig, now); + update("early bird", mod_orig, now, penalty); exeunt(ex_greylisting); } if (mod_age - ac_age < minimum_age // early bird, or completely unused && mod_age > probation) { // did not diligently resubmit - update("disprobation", now, now); + update("disprobation", now, now, penalty); exeunt(ex_greylisting); } if (ac_age > maximum_age) { - update("too old, starting over", now, now); + update("too old, starting over", now, now, penalty); exeunt(ex_greylisting); } // if all checks are passed, must be OK: - update("returning customer", mod_orig, now); + update("returning customer", mod_orig, now, penalty); return 0; } -- cgit v1.2.3 From eb342191804df42d294e1579a880c58dd213d66d 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(-) (limited to 'tools/greylist.c') 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 From 4e298a27d17d0d0a4181b302b4237363396eb0ed Mon Sep 17 00:00:00 2001 From: John Denker Date: Fri, 20 Jul 2012 17:02:38 -0700 Subject: more regularization of reporting --- tools/filters.conf | 2 +- tools/greylist.c | 41 ++++++++++++++++++++++++++++------------- tools/hi-q.c | 41 ++++++++++++++++++++++++++++------------- tools/hi-test2.conf | 2 +- 4 files changed, 58 insertions(+), 28 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/filters.conf b/tools/filters.conf index 3ef7524..9342441 100644 --- a/tools/filters.conf +++ b/tools/filters.conf @@ -4,4 +4,4 @@ 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 +postspam /var/qmail/bin/greylist -suffix (post) -penalize 86400 diff --git a/tools/greylist.c b/tools/greylist.c index d1ff1a4..465a78e 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -88,6 +88,8 @@ public: string hostname; int mod_age; int ac_age; + string suffix; + string progid; whatsit(const string name, const string _dirname) : dirname(_dirname), progname(name), mypid(getpid()), mod_age(0), ac_age(0) @@ -98,8 +100,16 @@ public: // access comes after modification: void update(const string msg, const timeval new_mod, const timeval new_ac, const int penalty=0); + void bind(); }; +void whatsit::bind(){ + stringstream foo; + foo << progname << suffix + << "[" << mypid << "]"; + progid = foo.str(); +} + string time_out(const int _ttt){ int ttt(abs(_ttt)); int sec(ttt % 60); @@ -120,7 +130,7 @@ string time_out(const int _ttt){ return foo.str(); } -void scan(const string p, const int copies=1){ +void scan(const string progid, const string p, const int copies=1){ timeval now; gettimeofday(&now, NULL); using namespace boost::filesystem; @@ -136,7 +146,7 @@ void scan(const string p, const int copies=1){ string fn = p + "/" + basename; int rslt = stat(fn.c_str(), &mystat); if (rslt != 0){ - cerr << progname << ": stat failed for '" + cerr << progid << ": stat failed for '" << fn << "' : "; perror(0); } @@ -175,7 +185,7 @@ void scan(const string p, const int copies=1){ void whatsit::update(const string msg, const timeval new_mod, const timeval new_ac, const int penalty){ - cerr << progname << ": " + cerr << progid << ": " << msg << ": " << ipbase; if (hostname.length()) cerr << " " << hostname; cerr << " mod_age: " << time_out(mod_age) @@ -195,7 +205,6 @@ 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); @@ -217,13 +226,21 @@ int main(int _argc, char** _argv){ exeunt(ex_syserr); } penalty = atoi(*argv++); argc--; + } else if (prefix(arg, "-suffix")) { + if (!argc){ + cerr << "Option '" << arg << "' requires an argument" << endl; + exeunt(ex_syserr); + } + foo.suffix += *argv++; argc--; } else { cerr << "Unrecognized arg: " << arg << endl; exeunt(ex_syserr); } } + foo.bind(); + if (scanmode) { - scan(dirname, copies); + scan(foo.progid, dirname, copies); return 0; } @@ -233,8 +250,7 @@ int main(int _argc, char** _argv){ int whatsit::doit(const int penalty){ char* ipvar = getenv("TCPREMOTEIP"); if (!ipvar) { - cerr << progname - << "[" << mypid << "] " + cerr << progid << " TCPREMOTEIP not set???" << endl; // should never happen // although you can make it happen using a weird test-harness @@ -243,8 +259,7 @@ int whatsit::doit(const int penalty){ ipbase = ipvar; char* hostvar = getenv("TCPREMOTEHOST"); if (!hostvar) { - cerr << progname - << "[" << mypid << "] " + cerr << progid << " from " << ipbase << " ... TCPREMOTEHOST not set???" << endl; exeunt(ex_spam); @@ -257,13 +272,13 @@ int whatsit::doit(const int penalty){ int rslt = stat(dirname.c_str(), &dirstat); if (rslt != 0){ if (errno != ENOENT) { - cerr << progname << ": stat failed for '" + cerr << progid << ": stat failed for '" << dirname << "' : "; perror(0); } rslt = mkdir(dirname.c_str(), 0755); if (rslt != 0) { - cerr << progname + cerr << progid << "uid " << getuid() << ": mkdir failed for '" << dirname << "' : "; @@ -277,14 +292,14 @@ int whatsit::doit(const int penalty){ rslt = stat(ipname.c_str(), &ipstat); if (rslt != 0){ if (errno != ENOENT) { - cerr << progname << ": stat failed for '" + cerr << progid << ": stat failed for '" << ipname << "' : "; perror(0); } ofstream foo; int fd = creat(ipname.c_str(), 0644); if (fd < 0){ - cerr << progname << ": create failed for '" + cerr << progid << ": create failed for '" << ipname << "' : "; perror(0); } diff --git a/tools/hi-q.c b/tools/hi-q.c index f6b57e1..26e67a7 100644 --- a/tools/hi-q.c +++ b/tools/hi-q.c @@ -258,12 +258,11 @@ void usage() { string progname; pid_t mypid; +string progid; void dump(const string var){ char* str = getenv(var.c_str()); - cerr << progname - << "[" << mypid << "] " - << var; + cerr << progid << var; if (str) cerr << " is set to '" << str << "'" << endl; else cerr << " is not set." << endl; } @@ -276,8 +275,16 @@ int xclose(int arg){ extern char** environ; int main(int argc, char** argv) { - progname = *argv; - mypid = getpid(); + { + progname = *argv; + mypid = getpid(); + string shortname = progname; + size_t where = shortname.rfind("/"); + if (where != string::npos) shortname = shortname.substr(1+where); + stringstream binder; + binder << shortname << "[" << mypid << "]"; + progid = binder.str(); + } #define foo(name, num) codemap[num] = #name ; bar @@ -649,14 +656,18 @@ bar exword = foo.str(); } } - cerr << "hi-q concludes: kid[" << kidno << "]" - << " pid " << argbest_blame - << " i.e. '" << filter[kidno].cmd[0] << "'" + cerr << progid + << " concludes: kid[" << kidno << "]" + << " i.e. " << filter[kidno].cmd[0] + << "[" << argbest_blame << "]" << " reports " << exword << endl; exeunt(excode); } else if (WIFSIGNALED(best_blame)) { int sig = WTERMSIG(best_blame); - cerr << "hi-q says: kid " << argbest_blame + cerr << progid + << " concludes: kid[" << kidno << "]" + << " i.e. " << filter[kidno].cmd[0] + << "[" << argbest_blame << "]" << " was killed by signal " << sig << endl; // if the *best* blame is a kill, that's not normal @@ -677,14 +688,18 @@ bar waitpid(special_pid, &kidstatus, WUNTRACED); if (WIFEXITED(kidstatus)) { int sts = WEXITSTATUS(kidstatus); - cerr << "hi-q says: qq program " << kidpid[nkids-1] - << " i.e. '" << filter[nkids-1].cmd[0] << "'" + cerr << progid + << " says: qq program" + << " i.e. " << filter[nkids-1].cmd[0] + << "[" << kidpid[nkids-1] << "]" << " returned status " << sts << endl; return sts; } else if (WIFSIGNALED(kidstatus)) { - cerr << "hi-q says: qq program " << kidpid[nkids-1] - << " i.e. '" << filter[nkids-1].cmd[0] << "'" + cerr << progid + << " says: qq program" + << " i.e. " << filter[nkids-1].cmd[0] + << "[" << kidpid[nkids-1] << "]" << " was killed by signal " << WTERMSIG(kidstatus) << endl; return ex_syserr; diff --git a/tools/hi-test2.conf b/tools/hi-test2.conf index 51d0361..90997b4 100644 --- a/tools/hi-test2.conf +++ b/tools/hi-test2.conf @@ -1,6 +1,6 @@ stub hi-test x0 -snooze 2 stub hi-test x0 -snooze 1 -exit 0 -stub greylist +stub greylist -suffix (sfx) qq hi-test x1 -snooze 3 -exit 3 postspam /bin/echo post spam ! -- cgit v1.2.3 From 60ebe6c00a2868e6bb69ef30cf04e5568276808b Mon Sep 17 00:00:00 2001 From: John Denker Date: Fri, 20 Jul 2012 22:18:31 -0700 Subject: implement penaltybox return-code (exit status) also make sure DNS checking doesn't interfere with more basic duties such as keeping the greylist database updated --- tools/filters.conf | 6 ++-- tools/greylist.c | 82 +++++++++++++++++++++++++++++++++++------------------ tools/hi-q.c | 21 ++++++++------ tools/hi-test2.conf | 2 +- 4 files changed, 72 insertions(+), 39 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/filters.conf b/tools/filters.conf index 9342441..f5b35f5 100644 --- a/tools/filters.conf +++ b/tools/filters.conf @@ -1,7 +1,7 @@ # configuration file for hi-q series /var/qmail/bin/skrewt -stub /var/qmail/bin/greylist -sa /usr/local/bin/spamc -Y 0 -s 1000000 +stub /var/qmail/bin/greylist -check -v +sa /usr/local/bin/spamc -Y 0 -s 1000000 -x qq /var/qmail/bin/qmail-queue -postspam /var/qmail/bin/greylist -suffix (post) -penalize 86400 +postspam /var/qmail/bin/greylist -suffix (post) -penalize 86400 -v diff --git a/tools/greylist.c b/tools/greylist.c index 465a78e..c5c891f 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -31,6 +31,7 @@ const int probation(4*hour); #define foo(name, num) const int ex_ ## name = num #define bar foo(good, 0) ;\ foo(spam, 21) ;\ +foo(penaltybox, 22) ;\ foo(greylisting, 70) ;\ foo(syserr, 71) ;\ foo(comerr, 74) ; @@ -90,9 +91,12 @@ public: int ac_age; string suffix; string progid; + int verbosity; whatsit(const string name, const string _dirname) - : dirname(_dirname), progname(name), mypid(getpid()), mod_age(0), ac_age(0) + : dirname(_dirname), progname(name), mypid(getpid()), + mod_age(0), ac_age(0), + verbosity(0) { gettimeofday(&now, NULL); } @@ -103,9 +107,15 @@ public: void bind(); }; +string basename(const string path){ + size_t where = path.rfind("/"); + if (where != string::npos) return path.substr(1+where); + return path; +} + void whatsit::bind(){ stringstream foo; - foo << progname << suffix + foo << basename(progname) << suffix << "[" << mypid << "]"; progid = foo.str(); } @@ -166,6 +176,7 @@ void scan(const string progid, const string p, const int copies=1){ if (mod_age != ac_age) cout << "!"; } else if (mod_age < minimum_age) { cout << " young"; + if (mod_age != ac_age) cout << "!"; } else if (mod_age == ac_age) { cout << " unused"; } else if (mod_age > maximum_age) { @@ -185,12 +196,15 @@ void scan(const string progid, const string p, const int copies=1){ void whatsit::update(const string msg, const timeval new_mod, const timeval new_ac, const int penalty){ - cerr << progid << ": " - << msg << ": " << ipbase; - if (hostname.length()) cerr << " " << hostname; - cerr << " mod_age: " << time_out(mod_age) - << " ac_age: " << time_out(ac_age) - << endl; + if (verbosity){ + cerr << progid << ": "; + if (penalty) cerr << " penalty+"; + cerr << msg << ": " << ipbase; + if (hostname.length()) cerr << " " << hostname; + cerr << " mod_age: " << time_out(mod_age) + << " ac_age: " << time_out(ac_age) + << endl; + } timeval pen_mod(new_mod); if (penalty) { pen_mod = now; @@ -213,12 +227,17 @@ int main(int _argc, char** _argv){ int scanmode(0); int copies(1); int penalty(0); + int check(0); while (argc > 0) { string arg = argv[0]; argc--; argv++; if (prefix(arg, "-scan")) { scanmode++; } else if (prefix(arg, "-copy")) { copies++; + } else if (prefix(arg, "-verbose")) { + foo.verbosity++; + } else if (prefix(arg, "-check")) { + check++; } else if (prefix(arg, "-penalize") || prefix(arg, "-penalty")) { if (!argc){ @@ -244,7 +263,22 @@ int main(int _argc, char** _argv){ return 0; } - return foo.doit(penalty); + int sts = foo.doit(penalty); + +// perform some extra checks. +// Probably a better design would be to +// a) make more thorough DNS checks, and +// b) move all the DNS checking to a separate module + if (check){ + char* hostvar = getenv("TCPREMOTEHOST"); + if (!hostvar) { + cerr << foo.progid + << " from " << foo.ipbase + << " ... TCPREMOTEHOST not set???" << endl; + exeunt(ex_spam); + } + } + exeunt(sts); } int whatsit::doit(const int penalty){ @@ -254,18 +288,11 @@ int whatsit::doit(const int penalty){ << " TCPREMOTEIP not set???" << endl; // should never happen // although you can make it happen using a weird test-harness - exeunt(ex_syserr); + return(ex_syserr); } ipbase = ipvar; char* hostvar = getenv("TCPREMOTEHOST"); - if (!hostvar) { - cerr << progid - << " from " << ipbase - << " ... TCPREMOTEHOST not set???" << endl; - exeunt(ex_spam); - } else { - hostname = hostvar; - } + if (hostvar) hostname = hostvar; // see if our directory exists: struct stat dirstat; @@ -283,7 +310,7 @@ int whatsit::doit(const int penalty){ << ": mkdir failed for '" << dirname << "' : "; perror(0); - exeunt(ex_syserr); + return(ex_syserr); } } @@ -304,33 +331,34 @@ int whatsit::doit(const int penalty){ perror(0); } close(fd); - update("new customer", now, now); - exeunt(ex_greylisting); + update("new customer", now, now, penalty); + return(ex_greylisting); } -// here if stat succeeded + +// now for really checking the greylist status: mod_age = now.tv_sec - ipstat.st_mtime; ac_age = now.tv_sec - ipstat.st_atime; timeval mod_orig = {ipstat.st_mtime, 0}; if (mod_age < 0) { update("penalty box", mod_orig, now, penalty); - exeunt(ex_spam); + return(ex_penaltybox); } if (mod_age < ac_age){ update("paroled spammer", now, now, penalty); - exeunt(ex_greylisting); + return(ex_greylisting); } if (mod_age < minimum_age) { update("early bird", mod_orig, now, penalty); - exeunt(ex_greylisting); + return(ex_greylisting); } if (mod_age - ac_age < minimum_age // early bird, or completely unused && mod_age > probation) { // did not diligently resubmit update("disprobation", now, now, penalty); - exeunt(ex_greylisting); + return(ex_greylisting); } if (ac_age > maximum_age) { update("too old, starting over", now, now, penalty); - exeunt(ex_greylisting); + return(ex_greylisting); } // if all checks are passed, must be OK: update("returning customer", mod_orig, now, penalty); diff --git a/tools/hi-q.c b/tools/hi-q.c index 26e67a7..6aaf302 100644 --- a/tools/hi-q.c +++ b/tools/hi-q.c @@ -30,6 +30,7 @@ using namespace std; #define bar \ foo(good, 0) ;\ foo(spam, 21) ;\ +foo(penaltybox, 22) ;\ foo(permerr, 31) ;\ foo(greylisting, 70) ;\ foo(syserr, 71) ;\ @@ -188,6 +189,7 @@ void exeunt(const int sts) { // FIXME: stop other children //xxxx cerr << "hi-q: exeunt called with " << sts << endl; if (sts == ex_spam) fork_and_wait(post); + if (sts == ex_penaltybox) exit(ex_spam); exit(sts); } @@ -274,15 +276,18 @@ int xclose(int arg){ extern char** environ; +string basename(const string path){ + size_t where = path.rfind("/"); + if (where != string::npos) return path.substr(1+where); + return path; +} + int main(int argc, char** argv) { { progname = *argv; mypid = getpid(); - string shortname = progname; - size_t where = shortname.rfind("/"); - if (where != string::npos) shortname = shortname.substr(1+where); stringstream binder; - binder << shortname << "[" << mypid << "]"; + binder << basename(progname) << "[" << mypid << "]"; progid = binder.str(); } @@ -658,7 +663,7 @@ bar } cerr << progid << " concludes: kid[" << kidno << "]" - << " i.e. " << filter[kidno].cmd[0] + << " i.e. " << basename(filter[kidno].cmd[0]) << "[" << argbest_blame << "]" << " reports " << exword << endl; exeunt(excode); @@ -666,7 +671,7 @@ bar int sig = WTERMSIG(best_blame); cerr << progid << " concludes: kid[" << kidno << "]" - << " i.e. " << filter[kidno].cmd[0] + << " i.e. " << basename(filter[kidno].cmd[0]) << "[" << argbest_blame << "]" << " was killed by signal " << sig << endl; @@ -690,7 +695,7 @@ bar int sts = WEXITSTATUS(kidstatus); cerr << progid << " says: qq program" - << " i.e. " << filter[nkids-1].cmd[0] + << " i.e. " << basename(filter[nkids-1].cmd[0]) << "[" << kidpid[nkids-1] << "]" << " returned status " << sts << endl; @@ -698,7 +703,7 @@ bar } else if (WIFSIGNALED(kidstatus)) { cerr << progid << " says: qq program" - << " i.e. " << filter[nkids-1].cmd[0] + << " i.e. " << basename(filter[nkids-1].cmd[0]) << "[" << kidpid[nkids-1] << "]" << " was killed by signal " << WTERMSIG(kidstatus) << endl; diff --git a/tools/hi-test2.conf b/tools/hi-test2.conf index 90997b4..370ba77 100644 --- a/tools/hi-test2.conf +++ b/tools/hi-test2.conf @@ -1,6 +1,6 @@ stub hi-test x0 -snooze 2 stub hi-test x0 -snooze 1 -exit 0 -stub greylist -suffix (sfx) +stub greylist -suffix (sfx) -v -p 10 qq hi-test x1 -snooze 3 -exit 3 postspam /bin/echo post spam ! -- cgit v1.2.3 From d747e6cea96000540619a1f1e2a33a4f01a25a67 Mon Sep 17 00:00:00 2001 From: John Denker Date: Sat, 21 Jul 2012 06:26:42 -0700 Subject: don't return messy exit status codes in non-check mode --- tools/greylist.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index c5c891f..d76ba08 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -264,19 +264,20 @@ int main(int _argc, char** _argv){ } int sts = foo.doit(penalty); + if (sts == ex_syserr) return sts; + if (!check) return ex_good; -// perform some extra checks. +// check mode ... perform some extra checks. // Probably a better design would be to // a) make more thorough DNS checks, and // b) move all the DNS checking to a separate module - if (check){ - char* hostvar = getenv("TCPREMOTEHOST"); - if (!hostvar) { - cerr << foo.progid - << " from " << foo.ipbase - << " ... TCPREMOTEHOST not set???" << endl; - exeunt(ex_spam); - } + + char* hostvar = getenv("TCPREMOTEHOST"); + if (!hostvar) { + cerr << foo.progid + << " from " << foo.ipbase + << " ... TCPREMOTEHOST not set???" << endl; + exeunt(ex_spam); } exeunt(sts); } -- cgit v1.2.3 From acca30bef01fb07b34b0220fa96bcc7cf0df52f9 Mon Sep 17 00:00:00 2001 From: John Denker Date: Sat, 21 Jul 2012 09:47:14 -0700 Subject: adding some code to check DNS consistency --- tools/greylist.c | 113 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 88 insertions(+), 25 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index d76ba08..b64636e 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -3,7 +3,7 @@ #include #include -#include /* for stat() */ +#include /* for stat(), getaddrinfo() */ #include /* for stat() */ #include /* for stat() */ #include /* for perror */ @@ -17,6 +17,10 @@ // requires apt-get install libboost-filesystem-dev: #include +#include /* for getaddrinfo() */ +#include /* for getaddrinfo() */ +#include /* for memset() */ + using namespace std; const int minute(60); @@ -84,6 +88,8 @@ public: string progname; pid_t mypid; timeval now; + char* ipvar; + char* hostvar; string ipbase; string ipname; string hostname; @@ -104,7 +110,8 @@ public: // access comes after modification: void update(const string msg, const timeval new_mod, const timeval new_ac, const int penalty=0); - void bind(); + int setup(); + int check_dns(); }; string basename(const string path){ @@ -113,11 +120,24 @@ string basename(const string path){ return path; } -void whatsit::bind(){ +int whatsit::setup(){ stringstream foo; - foo << basename(progname) << suffix + foo << basename(progname) << suffix << "[" << mypid << "]"; progid = foo.str(); + + ipvar = getenv("TCPREMOTEIP"); + if (!ipvar) { + cerr << progid + << " TCPREMOTEIP not set???" << endl; + // should never happen + // although you can make it happen using a weird test-harness + return(ex_syserr); + } + ipbase = ipvar; + hostvar = getenv("TCPREMOTEHOST"); + if (hostvar) hostname = hostvar; + return 0; } string time_out(const int _ttt){ @@ -218,6 +238,7 @@ void whatsit::update(const string msg, const timeval new_mod, utimes(ipname.c_str(), upd); } + int main(int _argc, char** _argv){ mypid = getpid(); int argc(_argc); @@ -256,7 +277,7 @@ int main(int _argc, char** _argv){ exeunt(ex_syserr); } } - foo.bind(); + if (foo.setup()) return ex_syserr; if (scanmode) { scan(foo.progid, dirname, copies); @@ -269,31 +290,15 @@ int main(int _argc, char** _argv){ // check mode ... perform some extra checks. // Probably a better design would be to -// a) make more thorough DNS checks, and -// b) move all the DNS checking to a separate module +// (a) make more thorough DNS checks, and +// (b) move all the DNS checking to a separate module - char* hostvar = getenv("TCPREMOTEHOST"); - if (!hostvar) { - cerr << foo.progid - << " from " << foo.ipbase - << " ... TCPREMOTEHOST not set???" << endl; - exeunt(ex_spam); - } + int dns = foo.check_dns(); + if (dns == ex_syserr) return dns; exeunt(sts); } int whatsit::doit(const int penalty){ - char* ipvar = getenv("TCPREMOTEIP"); - if (!ipvar) { - cerr << progid - << " TCPREMOTEIP not set???" << endl; - // should never happen - // although you can make it happen using a weird test-harness - return(ex_syserr); - } - ipbase = ipvar; - char* hostvar = getenv("TCPREMOTEHOST"); - if (hostvar) hostname = hostvar; // see if our directory exists: struct stat dirstat; @@ -365,3 +370,61 @@ int whatsit::doit(const int penalty){ update("returning customer", mod_orig, now, penalty); return 0; } + +int whatsit::check_dns(){ + char* hostvar = getenv("TCPREMOTEHOST"); + if (!hostvar) { + cerr << progid + << " from " << ipbase + << " ... TCPREMOTEHOST not set???" << endl; + exeunt(ex_spam); + } + + struct addrinfo *result; + struct addrinfo *ipresult; + struct addrinfo *res; + addrinfo hints; + int error; + + /* resolve the domain name into a list of addresses */ + memset(&hints, 0, sizeof(struct addrinfo)); +#if 1 + // restrict to TCP only; otherwise we get N records per address + hints.ai_protocol = IPPROTO_TCP; +#endif + error = getaddrinfo(hostvar, NULL, &hints, &result); + if (error) { + cerr << "error in getaddrinfo for " << hostvar + << " : " << gai_strerror(error) << endl; + return ex_syserr; + } + + error = getaddrinfo(ipvar, NULL, &hints, &ipresult); + if (error) { + cerr << "error in getaddrinfo for " << ipvar + << " : " << gai_strerror(error) << endl; + return ex_syserr; + } + +// loop over all returned results and check for a match. + for (res = result; res != NULL; res = res->ai_next){ + void *numericAddress; + + sa_family_t fam = ((sockaddr *)res->ai_addr)->sa_family; + int addrsize; + switch (fam) { + case AF_INET: + numericAddress = &(((sockaddr_in *)res->ai_addr)->sin_addr.s_addr); + addrsize = sizeof(in_addr); + break; + case AF_INET6: + numericAddress = &(((sockaddr_in6 *)res->ai_addr)->sin6_addr.s6_addr); + addrsize = sizeof(in6_addr); + break; + default: + cerr << "?Unknown address family " << fam << endl; + return(ex_syserr); + } + } + return 0; +} -- cgit v1.2.3 From effe1d81c837e1b887c307ed607459791bd7c8b5 Mon Sep 17 00:00:00 2001 From: John Denker Date: Sat, 21 Jul 2012 10:45:40 -0700 Subject: doesn't work, don't know why --- tools/greylist.c | 81 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 20 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index b64636e..f9a4ebc 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -371,6 +371,45 @@ int whatsit::doit(const int penalty){ return 0; } +typedef vector VU; + +class VUx : public VU { +public: + sa_family_t fam; +}; + +VUx parse_sockaddr(const sockaddr* ai_addr) { + void* numericAddress; + VUx rslt; + int addrsize; + rslt.fam = ((sockaddr *)ai_addr)->sa_family; + switch (rslt.fam) { + case AF_INET: + numericAddress = &(((sockaddr_in *)ai_addr)->sin_addr.s_addr); + addrsize = sizeof(in_addr); + break; + case AF_INET6: + numericAddress = &(((sockaddr_in6 *)ai_addr)->sin6_addr.s6_addr); + addrsize = sizeof(in6_addr); + break; + default: + cerr << "?Unknown address family " << rslt.fam << endl; + return rslt; + } + unsigned char* foo = (unsigned char*) numericAddress; + (VU)rslt = VU(foo, foo+addrsize); + cerr << "asdf " << rslt.size() << " ... " << VU(foo, foo+addrsize).size() << endl; + return rslt; +} + +int diff(const VU aaa, const VU bbb){ + if(aaa.size() != bbb.size()) return 1; + for (unsigned int ii=0; ii < aaa.size(); ii++){ + if (aaa[ii] != bbb[ii]) return 1; + } + return 0; +} + int whatsit::check_dns(){ char* hostvar = getenv("TCPREMOTEHOST"); if (!hostvar) { @@ -392,39 +431,41 @@ int whatsit::check_dns(){ // restrict to TCP only; otherwise we get N records per address hints.ai_protocol = IPPROTO_TCP; #endif - error = getaddrinfo(hostvar, NULL, &hints, &result); + + error = getaddrinfo(ipvar, NULL, &hints, &ipresult); if (error) { - cerr << "error in getaddrinfo for " << hostvar + cerr << "error in getaddrinfo for " << ipvar << " : " << gai_strerror(error) << endl; return ex_syserr; } + if (!ipresult) { + cerr << "should never happen (addr with no addrs?)" << endl; + return ex_syserr; + } - error = getaddrinfo(ipvar, NULL, &hints, &ipresult); + VU ipAddr = parse_sockaddr(ipresult->ai_addr); + error = getaddrinfo(hostvar, NULL, &hints, &result); if (error) { - cerr << "error in getaddrinfo for " << ipvar + cerr << "error in getaddrinfo for " << hostvar << " : " << gai_strerror(error) << endl; return ex_syserr; } // loop over all returned results and check for a match. + vector checked_hosts; for (res = result; res != NULL; res = res->ai_next){ - void *numericAddress; - - sa_family_t fam = ((sockaddr *)res->ai_addr)->sa_family; - int addrsize; - switch (fam) { - case AF_INET: - numericAddress = &(((sockaddr_in *)res->ai_addr)->sin_addr.s_addr); - addrsize = sizeof(in_addr); - break; - case AF_INET6: - numericAddress = &(((sockaddr_in6 *)res->ai_addr)->sin6_addr.s6_addr); - addrsize = sizeof(in6_addr); - break; - default: - cerr << "?Unknown address family " << fam << endl; - return(ex_syserr); + VU hostAddr = parse_sockaddr(res->ai_addr); +#if 0 + char msgbuf[INET6_ADDRSTRLEN]; + const char* rslt = inet_ntop(fam, numericAddress, + msgbuf, sizeof(msgbuf)); +#endif + if (!diff(hostAddr, ipAddr)) { + cerr << "match! " << ipAddr.size() << endl; + goto done; } } + cerr << "no match" << endl; +done: return 0; } -- cgit v1.2.3 From bcce618000e74f0d36780b48c3c49f4a9b5914e5 Mon Sep 17 00:00:00 2001 From: John Denker Date: Sat, 21 Jul 2012 11:16:12 -0700 Subject: check DNS consistency --- tools/greylist.c | 51 ++++++++++++++++++++++++++++++++++++--------------- tools/makefile | 8 +++++--- 2 files changed, 41 insertions(+), 18 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index f9a4ebc..863a2fe 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -20,6 +20,8 @@ #include /* for getaddrinfo() */ #include /* for getaddrinfo() */ #include /* for memset() */ +#include /* for inet_ntop() */ + using namespace std; @@ -294,7 +296,7 @@ int main(int _argc, char** _argv){ // (b) move all the DNS checking to a separate module int dns = foo.check_dns(); - if (dns == ex_syserr) return dns; + if (dns == ex_syserr || dns == ex_spam) return dns; exeunt(sts); } @@ -373,15 +375,26 @@ int whatsit::doit(const int penalty){ typedef vector VU; -class VUx : public VU { +class VUx{ public: - sa_family_t fam; + VU addr; + sa_family_t fam; + string str(); }; +string VUx::str(){ + char msgbuf[INET6_ADDRSTRLEN]; + const char* rslt = inet_ntop(fam, &addr[0], + msgbuf, sizeof(msgbuf)); + if (!rslt) rslt = ""; + return rslt; +} + VUx parse_sockaddr(const sockaddr* ai_addr) { void* numericAddress; VUx rslt; int addrsize; + rslt.addr = VU(0); rslt.fam = ((sockaddr *)ai_addr)->sa_family; switch (rslt.fam) { case AF_INET: @@ -397,8 +410,7 @@ VUx parse_sockaddr(const sockaddr* ai_addr) { return rslt; } unsigned char* foo = (unsigned char*) numericAddress; - (VU)rslt = VU(foo, foo+addrsize); - cerr << "asdf " << rslt.size() << " ... " << VU(foo, foo+addrsize).size() << endl; + rslt.addr = VU(foo, foo+addrsize); return rslt; } @@ -443,7 +455,7 @@ int whatsit::check_dns(){ return ex_syserr; } - VU ipAddr = parse_sockaddr(ipresult->ai_addr); + VUx ipAddr = parse_sockaddr(ipresult->ai_addr); error = getaddrinfo(hostvar, NULL, &hints, &result); if (error) { cerr << "error in getaddrinfo for " << hostvar @@ -454,18 +466,27 @@ int whatsit::check_dns(){ // loop over all returned results and check for a match. vector checked_hosts; for (res = result; res != NULL; res = res->ai_next){ - VU hostAddr = parse_sockaddr(res->ai_addr); -#if 0 - char msgbuf[INET6_ADDRSTRLEN]; - const char* rslt = inet_ntop(fam, numericAddress, - msgbuf, sizeof(msgbuf)); -#endif - if (!diff(hostAddr, ipAddr)) { - cerr << "match! " << ipAddr.size() << endl; + VUx hostAddr = parse_sockaddr(res->ai_addr); + + if (!diff(hostAddr.addr, ipAddr.addr)) { + ///// cerr << "match! " << ipAddr.addr.size() << endl; goto done; } } - cerr << "no match" << endl; + cerr << "(warning) DNS inconsistency: " + << ipAddr.str() << " does not match"; + for (res = result; res != NULL; res = res->ai_next){ + cerr << " " << parse_sockaddr(res->ai_addr).str(); + } + cerr << endl; +#if 1 + // temporary ... just a warning + return 0; +#else + return ex_spam; +#endif + + done: return 0; } diff --git a/tools/makefile b/tools/makefile index cf62473..1f878f0 100644 --- a/tools/makefile +++ b/tools/makefile @@ -44,9 +44,11 @@ logmark: logger -t jsd -p mail.info ========================= todo: - echo zap penalize greylist status of spam \ - extended error codes from skrewt, greylist \ - extended error codes [-x] from spamc + echo \ + pass message-ID to greylist program \ + ... also provide a way for certain recipients to bypass some checks \ + ... both will require major restructuring, "cat" process \ + ..... ALWAYS: @echo ... -- cgit v1.2.3 From 46cb697732ea2c2c4a68358109a58232ef2666e7 Mon Sep 17 00:00:00 2001 From: John Denker Date: Sat, 21 Jul 2012 11:43:00 -0700 Subject: dns inconsistency is just a warning for the moment --- tools/greylist.c | 22 ++++++++++------------ tools/makefile | 1 + 2 files changed, 11 insertions(+), 12 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index 863a2fe..95a4f0b 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -436,6 +436,8 @@ int whatsit::check_dns(){ struct addrinfo *res; addrinfo hints; int error; + int ex_dnserr(ex_syserr); + ex_dnserr = 0; // temporarily just a warning /* resolve the domain name into a list of addresses */ memset(&hints, 0, sizeof(struct addrinfo)); @@ -448,11 +450,11 @@ int whatsit::check_dns(){ if (error) { cerr << "error in getaddrinfo for " << ipvar << " : " << gai_strerror(error) << endl; - return ex_syserr; + return ex_dnserr; } if (!ipresult) { cerr << "should never happen (addr with no addrs?)" << endl; - return ex_syserr; + return ex_dnserr; } VUx ipAddr = parse_sockaddr(ipresult->ai_addr); @@ -460,7 +462,7 @@ int whatsit::check_dns(){ if (error) { cerr << "error in getaddrinfo for " << hostvar << " : " << gai_strerror(error) << endl; - return ex_syserr; + return ex_dnserr; } // loop over all returned results and check for a match. @@ -473,19 +475,15 @@ int whatsit::check_dns(){ goto done; } } - cerr << "(warning) DNS inconsistency: " - << ipAddr.str() << " does not match"; + if (!ex_dnserr) cerr << "(warning) "; + cerr << "DNS inconsistency: " + << ipAddr.str() << " --> " + << hostvar << " ==>"; for (res = result; res != NULL; res = res->ai_next){ cerr << " " << parse_sockaddr(res->ai_addr).str(); } cerr << endl; -#if 1 - // temporary ... just a warning - return 0; -#else - return ex_spam; -#endif - + return ex_dnserr; done: return 0; diff --git a/tools/makefile b/tools/makefile index 1f878f0..6fa9636 100644 --- a/tools/makefile +++ b/tools/makefile @@ -48,6 +48,7 @@ todo: pass message-ID to greylist program \ ... also provide a way for certain recipients to bypass some checks \ ... both will require major restructuring, "cat" process \ + ... IPv6 reverse-DNS recors \ ..... ALWAYS: -- cgit v1.2.3 From 8ce08aca2410c795dfc46f37dc27402ff6de5dd1 Mon Sep 17 00:00:00 2001 From: John Denker Date: Sat, 21 Jul 2012 15:53:08 -0700 Subject: ignore penalty features for the moment --- tools/greylist.c | 4 ++++ tools/hi-q.c | 22 ++++++++++++++-------- tools/makefile | 3 ++- 3 files changed, 20 insertions(+), 9 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index 95a4f0b..063c3d0 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -68,6 +68,10 @@ int prefix(const string shorter, const string longer){ void exeunt(const int sts){ if (sts == ex_good) exit(sts); +#ifndef PENALIZE_SPAMMERS + if (sts == ex_penaltybox) exit(sts); +#endif + const char* foo = getenv("HI_Q_GROUP"); if (!foo) exit(sts); diff --git a/tools/hi-q.c b/tools/hi-q.c index 6aaf302..8766b08 100644 --- a/tools/hi-q.c +++ b/tools/hi-q.c @@ -158,7 +158,7 @@ int fork_and_wait(const jobber job){ int sts = WEXITSTATUS(kidstatus); if (sts != ex_good && sts != ex_spam) { cerr << "hi-q: job " << prog[0] - << " unexpectedly returns status: " << sts + << " unexpectedly returns status: " << sts << endl; exit(sts); } @@ -179,7 +179,7 @@ int fork_and_wait(const jobber job){ int fork_and_wait(vector post){ for(vector::const_iterator foo = post.begin(); foo != post.end(); foo++) { - int rslt = fork_and_wait(*foo); + int rslt = fork_and_wait(*foo); if (rslt) return rslt; } return 0; @@ -590,7 +590,7 @@ 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, status " + cerr << "hi-q: special kid exited early, status " << WEXITSTATUS(kidstatus) << " with " << alive << " kids still alive" << endl; @@ -599,10 +599,10 @@ bar int sig = WTERMSIG(kidstatus); if (sig == SIGUSR1) {/* normal, no logging required */} else { - cerr << "hi-q: special kid killed by signal " + cerr << "hi-q: special kid killed by signal " << sig << endl; // this is not normal - return(ex_syserr); + return(ex_syserr); } } else { /* paused, not dead */ @@ -612,7 +612,13 @@ bar // here if somekid is not the special kid if (WIFEXITED(kidstatus)) { alive--; - if (WEXITSTATUS(kidstatus)) { + int sts = WEXITSTATUS(kidstatus); +#ifndef PENALIZE_SPAMMERS + // ignore penalties for the moment + // to see whether there are any false positives + if (sts == ex_penaltybox) sts = ex_good; +#endif + if (sts) { argbest_blame = somekid; best_blame = kidstatus; break; @@ -694,7 +700,7 @@ bar if (WIFEXITED(kidstatus)) { int sts = WEXITSTATUS(kidstatus); cerr << progid - << " says: qq program" + << " says: qq program" << " i.e. " << basename(filter[nkids-1].cmd[0]) << "[" << kidpid[nkids-1] << "]" << " returned status " << sts @@ -702,7 +708,7 @@ bar return sts; } else if (WIFSIGNALED(kidstatus)) { cerr << progid - << " says: qq program" + << " says: qq program" << " i.e. " << basename(filter[nkids-1].cmd[0]) << "[" << kidpid[nkids-1] << "]" << " was killed by signal " << WTERMSIG(kidstatus) diff --git a/tools/makefile b/tools/makefile index 6fa9636..9059a2f 100644 --- a/tools/makefile +++ b/tools/makefile @@ -49,7 +49,8 @@ todo: ... also provide a way for certain recipients to bypass some checks \ ... both will require major restructuring, "cat" process \ ... IPv6 reverse-DNS recors \ - ..... + ... "clean up bad DNS reports nnnn --> () ==> ()" \ + ..... ALWAYS: @echo ... -- cgit v1.2.3 From 6c56d656bd407bc5fc4dd713aacb553ebefee892 Mon Sep 17 00:00:00 2001 From: John Denker Date: Sun, 22 Jul 2012 14:44:49 -0700 Subject: regularize log/progress messages --- tools/filters.conf | 3 +- tools/greylist.c | 93 ++++++++++++++++++++++++++++++++---------------------- tools/skrewt.c | 33 ++++++++++++++----- 3 files changed, 83 insertions(+), 46 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/filters.conf b/tools/filters.conf index f5b35f5..7768e6a 100644 --- a/tools/filters.conf +++ b/tools/filters.conf @@ -4,4 +4,5 @@ stub /var/qmail/bin/greylist -check -v sa /usr/local/bin/spamc -Y 0 -s 1000000 -x qq /var/qmail/bin/qmail-queue -postspam /var/qmail/bin/greylist -suffix (post) -penalize 86400 -v +# postspam /var/qmail/bin/greylist -suffix (post) -penalize 86400 -v +postspam /var/qmail/bin/greylist -suffix (post) -penalize 1 -v diff --git a/tools/greylist.c b/tools/greylist.c index 063c3d0..fd5ac4f 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -38,6 +38,7 @@ const int probation(4*hour); #define bar foo(good, 0) ;\ foo(spam, 21) ;\ foo(penaltybox, 22) ;\ +foo(badDNS, 23) ;\ foo(greylisting, 70) ;\ foo(syserr, 71) ;\ foo(comerr, 74) ; @@ -118,6 +119,7 @@ public: const timeval new_ac, const int penalty=0); int setup(); int check_dns(); + int check_dns_sub(string &addr, string &host, vector &checked); }; string basename(const string path){ @@ -133,14 +135,7 @@ int whatsit::setup(){ progid = foo.str(); ipvar = getenv("TCPREMOTEIP"); - if (!ipvar) { - cerr << progid - << " TCPREMOTEIP not set???" << endl; - // should never happen - // although you can make it happen using a weird test-harness - return(ex_syserr); - } - ipbase = ipvar; + if (ipvar) ipbase = ipvar; hostvar = getenv("TCPREMOTEHOST"); if (hostvar) hostname = hostvar; return 0; @@ -306,6 +301,14 @@ int main(int _argc, char** _argv){ int whatsit::doit(const int penalty){ + if (!ipvar) { + cerr << progid + << " TCPREMOTEIP not set???" << endl; + // should never happen + // although you can make it happen using a weird test-harness + return(ex_syserr); + } + // see if our directory exists: struct stat dirstat; int rslt = stat(dirname.c_str(), &dirstat); @@ -356,6 +359,7 @@ int whatsit::doit(const int penalty){ return(ex_penaltybox); } if (mod_age < ac_age){ +// when he comes out on parole, he starts over with no reputation: update("paroled spammer", now, now, penalty); return(ex_greylisting); } @@ -427,23 +431,35 @@ int diff(const VU aaa, const VU bbb){ } int whatsit::check_dns(){ - char* hostvar = getenv("TCPREMOTEHOST"); - if (!hostvar) { - cerr << progid - << " from " << ipbase - << " ... TCPREMOTEHOST not set???" << endl; - exeunt(ex_spam); - } + string addr("()"), host("()"); + vector checked; + int sts = check_dns_sub(addr, host, checked); + if (sts == 0) return sts; + if (sts != ex_badDNS) return sts; // possible ex_syserr +#if 1 + sts = 0; // demote badDNS to just a warning +#endif + cerr << progid; + if (!sts) cerr << " (warning)"; + cerr << " DNS inconsistency: " + << addr << " --> " + << host << " ==>"; + if (!checked.size()) cerr << " ()"; + else for (vector::const_iterator chk = checked.begin(); + chk != checked.end(); chk++) cerr << " " << *chk; + cerr << endl; + + return sts; +} + +int whatsit::check_dns_sub(string &addr, string &host, vector &checked){ struct addrinfo *result; struct addrinfo *ipresult; struct addrinfo *res; addrinfo hints; int error; - int ex_dnserr(ex_syserr); - ex_dnserr = 0; // temporarily just a warning - /* resolve the domain name into a list of addresses */ memset(&hints, 0, sizeof(struct addrinfo)); #if 1 // restrict to TCP only; otherwise we get N records per address @@ -451,43 +467,46 @@ int whatsit::check_dns(){ #endif error = getaddrinfo(ipvar, NULL, &hints, &ipresult); - if (error) { - cerr << "error in getaddrinfo for " << ipvar + if (error == EAI_NONAME) return ex_badDNS; + if (error) { // some unexpected error + cerr << progid + << " odd error " << error + << " in getaddrinfo for " << ipvar << " : " << gai_strerror(error) << endl; - return ex_dnserr; + return ex_syserr; } if (!ipresult) { cerr << "should never happen (addr with no addrs?)" << endl; - return ex_dnserr; + return ex_syserr; } - VUx ipAddr = parse_sockaddr(ipresult->ai_addr); + addr = ipAddr.str(); + + char* hostvar = getenv("TCPREMOTEHOST"); + if (hostvar) host = hostvar; + else return(ex_badDNS); + error = getaddrinfo(hostvar, NULL, &hints, &result); + if (error == EAI_NONAME) return ex_badDNS; if (error) { - cerr << "error in getaddrinfo for " << hostvar - << " : " << gai_strerror(error) << endl; - return ex_dnserr; + cerr << progid + << " error " << error + << " compare " << EAI_NONAME + << " in getaddrinfo for " << ipvar + << " :: " << gai_strerror(error) << endl; + return ex_syserr; } // loop over all returned results and check for a match. - vector checked_hosts; for (res = result; res != NULL; res = res->ai_next){ VUx hostAddr = parse_sockaddr(res->ai_addr); - + checked.push_back(hostAddr.str()); if (!diff(hostAddr.addr, ipAddr.addr)) { ///// cerr << "match! " << ipAddr.addr.size() << endl; goto done; } } - if (!ex_dnserr) cerr << "(warning) "; - cerr << "DNS inconsistency: " - << ipAddr.str() << " --> " - << hostvar << " ==>"; - for (res = result; res != NULL; res = res->ai_next){ - cerr << " " << parse_sockaddr(res->ai_addr).str(); - } - cerr << endl; - return ex_dnserr; + return ex_badDNS; done: return 0; diff --git a/tools/skrewt.c b/tools/skrewt.c index 44e885b..6de3dd9 100644 --- a/tools/skrewt.c +++ b/tools/skrewt.c @@ -11,6 +11,7 @@ #include #include /* perror */ +#include using namespace std; @@ -134,6 +135,15 @@ void exeunt(const int sts){ exit(sts); } +string basename(const string path){ + size_t where = path.rfind("/"); + if (where != string::npos) return path.substr(1+where); + return path; +} + +string progname, progid; +int mypid; + //////////////////////////////////////////////////////////// int main(int _argc, const char** _argv){ //// pid_t pid = getpid(); @@ -141,7 +151,14 @@ int main(int _argc, const char** _argv){ //// cout << getpgid(pid) << endl; int argc(_argc); const char **argv(_argv); - string progname(*argv); argv++; argc--; + { + progname = *argv++; argc--; + mypid = getpid(); + stringstream binder; + binder << basename(progname) << "[" << mypid << "]"; + progid = binder.str(); + } + int maxsize(1000000); @@ -183,7 +200,7 @@ int main(int _argc, const char** _argv){ if (getline(cin, header).fail()) continue; msgsize += header.length()+1; if (msgsize > maxsize) { - cerr << "skrewt rejection: bigger than " << maxsize << endl; + cerr << progid << " rejection: bigger than " << maxsize << endl; exeunt(ex_spam); } for (;;) { @@ -197,7 +214,7 @@ int main(int _argc, const char** _argv){ if (getline(cin, line).fail()) continue; msgsize += line.length()+1; if (msgsize > maxsize) { - cerr << "skrewt rejection: bigger than " << maxsize << endl; + cerr << progid << " rejection: bigger than " << maxsize << endl; exeunt(ex_spam); } header += "\n" + line; @@ -206,7 +223,7 @@ int main(int _argc, const char** _argv){ if (len && header[len-1] == '\r') len--; // reduced length, not counting if (len == 0) { if (!gotdate) { - cerr << "skrewt rejection: no date" << endl; + cerr << progid << " rejection: no date" << endl; exeunt(ex_spam); // disallow mail with no date } inheads = 0; @@ -256,7 +273,7 @@ int main(int _argc, const char** _argv){ gotdate++; } else if (headword == "subject") { if (rest.find("-please-bounce-this-") != string::npos) { - cerr << "skrewt rejection: by request" << endl; + cerr << progid << " rejection: by request" << endl; exeunt(ex_spam); } } @@ -268,7 +285,7 @@ int main(int _argc, const char** _argv){ if (!getline(cin, line).fail()) { msgsize += line.length()+1; if (msgsize > maxsize) { - cerr << "skrewt rejection: bigger than " << maxsize << endl; + cerr << progid << " rejection: bigger than " << maxsize << endl; exeunt(ex_spam); } if (line == "--" + boundary) { @@ -284,9 +301,9 @@ int main(int _argc, const char** _argv){ } if (0) cerr << "textlines: " << textlines << endl; if (!textlines) { - cerr << "skrewt rejection: no text" << endl; + cerr << progid << " rejection: no text" << endl; exeunt(ex_spam); } - cerr << "skrewt normal completion" << endl; + cerr << progid << " normal completion" << endl; exit(ex_good); } -- cgit v1.2.3 From 065d189b49449c073cfa23cba4172060abe36c6c Mon Sep 17 00:00:00 2001 From: John Denker Date: Sun, 22 Jul 2012 19:10:38 -0700 Subject: implement "-stain" feature; the "-penalty" feature was a baaaad idea --- tools/filters.conf | 2 +- tools/greylist.c | 57 +++++++++++++++++++++++++++++++++++------------------- 2 files changed, 38 insertions(+), 21 deletions(-) (limited to 'tools/greylist.c') diff --git a/tools/filters.conf b/tools/filters.conf index 7768e6a..bd8eb33 100644 --- a/tools/filters.conf +++ b/tools/filters.conf @@ -5,4 +5,4 @@ sa /usr/local/bin/spamc -Y 0 -s 1000000 -x qq /var/qmail/bin/qmail-queue # postspam /var/qmail/bin/greylist -suffix (post) -penalize 86400 -v -postspam /var/qmail/bin/greylist -suffix (post) -penalize 1 -v +postspam /var/qmail/bin/greylist -suffix (post) -stain 1 -v diff --git a/tools/greylist.c b/tools/greylist.c index fd5ac4f..92e638f 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -113,10 +113,10 @@ public: { gettimeofday(&now, NULL); } - int doit(const int penalty=0); + int doit(const int penalty, const int stain); // access comes after modification: void update(const string msg, const timeval new_mod, - const timeval new_ac, const int penalty=0); + const timeval new_ac, const int penalty, const int stain); int setup(); int check_dns(); int check_dns_sub(string &addr, string &host, vector &checked); @@ -216,27 +216,37 @@ void scan(const string progid, const string p, const int copies=1){ } void whatsit::update(const string msg, const timeval new_mod, - const timeval new_ac, const int penalty){ + const timeval new_ac, const int penalty, const int stain){ if (verbosity){ cerr << progid << ": "; - if (penalty) cerr << " penalty+"; - cerr << msg << ": " << ipbase; - if (hostname.length()) cerr << " " << hostname; - cerr << " mod_age: " << time_out(mod_age) - << " ac_age: " << time_out(ac_age) - << endl; + if (penalty) cerr << " penalty " << penalty; + if (stain) cerr << " stain " << stain; + if (verbosity > 1) { + if (penalty || stain) cerr << "+"; // separation, punctuation + cerr << msg << ": " << ipbase; + if (hostname.length()) cerr << " " << hostname; + cerr << " mod_age: " << time_out(mod_age) + << " ac_age: " << time_out(ac_age); + } + cerr << endl; } timeval pen_mod(new_mod); + timeval stain_ac(new_ac); if (penalty) { pen_mod = now; pen_mod.tv_sec += penalty; } + if (stain) { + stain_ac = now; + stain_ac.tv_sec -= stain; + } timeval upd[2] = { // beware: access illogically comes *before* modification here: - new_ac, + stain_ac, pen_mod }; - utimes(ipname.c_str(), upd); + if (utimes(ipname.c_str(), upd)) + cerr << "oops" << endl; } @@ -249,6 +259,7 @@ int main(int _argc, char** _argv){ int scanmode(0); int copies(1); int penalty(0); + int stain(0); int check(0); while (argc > 0) { string arg = argv[0]; argc--; argv++; @@ -267,6 +278,12 @@ int main(int _argc, char** _argv){ exeunt(ex_syserr); } penalty = atoi(*argv++); argc--; + } else if (prefix(arg, "-stain")) { + if (!argc){ + cerr << "Option '" << arg << "' requires an argument" << endl; + exeunt(ex_syserr); + } + stain = atoi(*argv++); argc--; } else if (prefix(arg, "-suffix")) { if (!argc){ cerr << "Option '" << arg << "' requires an argument" << endl; @@ -285,7 +302,7 @@ int main(int _argc, char** _argv){ return 0; } - int sts = foo.doit(penalty); + int sts = foo.doit(penalty, stain); if (sts == ex_syserr) return sts; if (!check) return ex_good; @@ -299,7 +316,7 @@ int main(int _argc, char** _argv){ exeunt(sts); } -int whatsit::doit(const int penalty){ +int whatsit::doit(const int penalty, const int stain){ if (!ipvar) { cerr << progid @@ -346,7 +363,7 @@ int whatsit::doit(const int penalty){ perror(0); } close(fd); - update("new customer", now, now, penalty); + update("new customer", now, now, penalty, stain); return(ex_greylisting); } @@ -355,29 +372,29 @@ int whatsit::doit(const int penalty){ ac_age = now.tv_sec - ipstat.st_atime; timeval mod_orig = {ipstat.st_mtime, 0}; if (mod_age < 0) { - update("penalty box", mod_orig, now, penalty); + update("penalty box", mod_orig, now, penalty, stain); return(ex_penaltybox); } if (mod_age < ac_age){ // when he comes out on parole, he starts over with no reputation: - update("paroled spammer", now, now, penalty); + update("paroled spammer", now, now, penalty, stain); return(ex_greylisting); } if (mod_age < minimum_age) { - update("early bird", mod_orig, now, penalty); + update("early bird", mod_orig, now, penalty, stain); return(ex_greylisting); } if (mod_age - ac_age < minimum_age // early bird, or completely unused && mod_age > probation) { // did not diligently resubmit - update("disprobation", now, now, penalty); + update("disprobation", now, now, penalty, stain); return(ex_greylisting); } if (ac_age > maximum_age) { - update("too old, starting over", now, now, penalty); + update("too old, starting over", now, now, penalty, stain); return(ex_greylisting); } // if all checks are passed, must be OK: - update("returning customer", mod_orig, now, penalty); + update("returning customer", mod_orig, now, penalty, stain); return 0; } -- cgit v1.2.3 From e2778bb957309e8d66f4a385c45e403125b40696 Mon Sep 17 00:00:00 2001 From: John Denker Date: Mon, 23 Jul 2012 08:44:42 -0700 Subject: fix very small bug --- tools/greylist.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'tools/greylist.c') diff --git a/tools/greylist.c b/tools/greylist.c index 92e638f..89396e7 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -73,6 +73,10 @@ void exeunt(const int sts){ if (sts == ex_penaltybox) exit(sts); #endif +#ifndef KILL_GROUP + exit(sts); +#endif + const char* foo = getenv("HI_Q_GROUP"); if (!foo) exit(sts); @@ -218,7 +222,7 @@ void scan(const string progid, const string p, const int copies=1){ void whatsit::update(const string msg, const timeval new_mod, const timeval new_ac, const int penalty, const int stain){ if (verbosity){ - cerr << progid << ": "; + if (penalty || stain || verbosity>1) cerr << progid << ": "; if (penalty) cerr << " penalty " << penalty; if (stain) cerr << " stain " << stain; if (verbosity > 1) { -- cgit v1.2.3 From d2564d25e802d1ee3230cf045c4940e836b5c6a2 Mon Sep 17 00:00:00 2001 From: John Denker Date: Sun, 29 Jul 2012 16:50:11 -0700 Subject: split ltgrey (and libltgrey) off from greylist; put some utility functions into their own file. --- .gitignore | 1 + tools/greylist.c | 50 +------- tools/libltgrey.c | 343 ++++++++++++++++++++++++++++++++++++++++++++++++++ tools/libltgrey.h | 38 ++++++ tools/ltgrey.c | 153 ++++++++++++++++++++++ tools/makefile | 12 +- tools/qq_exit_codes.h | 15 +++ tools/skrewt.c | 17 +-- tools/utils.c | 44 +++++++ tools/utils.h | 3 + 10 files changed, 611 insertions(+), 65 deletions(-) create mode 100644 tools/libltgrey.c create mode 100644 tools/libltgrey.h create mode 100644 tools/ltgrey.c create mode 100644 tools/qq_exit_codes.h create mode 100644 tools/utils.c create mode 100644 tools/utils.h (limited to 'tools/greylist.c') diff --git a/.gitignore b/.gitignore index e929027..b6369d1 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,4 @@ data.tar.gz dummy-mail-transfer-agent_all.deb bash-c wripper +ltgrey diff --git a/tools/greylist.c b/tools/greylist.c index 89396e7..9af70eb 100644 --- a/tools/greylist.c +++ b/tools/greylist.c @@ -22,7 +22,6 @@ #include /* for memset() */ #include /* for inet_ntop() */ - using namespace std; const int minute(60); @@ -33,18 +32,7 @@ const int minimum_age(15*minute); const int maximum_age(32*day); const int probation(4*hour); -// error exit codes, mostly as stated in qmail.c -#define foo(name, num) const int ex_ ## name = num -#define bar foo(good, 0) ;\ -foo(spam, 21) ;\ -foo(penaltybox, 22) ;\ -foo(badDNS, 23) ;\ -foo(greylisting, 70) ;\ -foo(syserr, 71) ;\ -foo(comerr, 74) ; - -bar -#undef foo +#include "qq_exit_codes.h" pid_t mypid; string progname; @@ -58,14 +46,6 @@ void dump(const string var){ else cerr << " is not set." << endl; } - -//////////////// -// little utility to help with argument parsing: -// -int prefix(const string shorter, const string longer){ - return shorter == longer.substr(0, shorter.length()); -} - void exeunt(const int sts){ if (sts == ex_good) exit(sts); @@ -93,6 +73,8 @@ void exeunt(const int sts){ exit(sts); } +#include "utils.h" + class whatsit{ public: string dirname; @@ -126,12 +108,6 @@ public: int check_dns_sub(string &addr, string &host, vector &checked); }; -string basename(const string path){ - size_t where = path.rfind("/"); - if (where != string::npos) return path.substr(1+where); - return path; -} - int whatsit::setup(){ stringstream foo; foo << basename(progname) << suffix @@ -145,26 +121,6 @@ int whatsit::setup(){ return 0; } -string time_out(const int _ttt){ - int ttt(abs(_ttt)); - int sec(ttt % 60); - int min((ttt / 60) % 60); - int hr(ttt / 3600); - stringstream foo; - int didsome(0); - if (_ttt < 0) foo << "-"; - if (hr) { - foo << hr << ":"; - didsome++; - } - if (didsome || min){ - foo << setw(didsome?2:1) << setfill('0') << min << ":"; - didsome++; - } - foo << setw(didsome?2:1) << setfill('0') << sec; - return foo.str(); -} - void scan(const string progid, const string p, const int copies=1){ timeval now; gettimeofday(&now, NULL); diff --git a/tools/libltgrey.c b/tools/libltgrey.c new file mode 100644 index 0000000..d4ec0da --- /dev/null +++ b/tools/libltgrey.c @@ -0,0 +1,343 @@ +#include /* for exit(), getenv() */ +#include +#include +#include + +#include /* for stat(), getaddrinfo() */ +#include /* for stat() */ +#include /* for stat() */ +#include /* for perror */ +#include /* for ENOENT */ +#include /* for ofstream() */ +#include /* for creat() */ +#include /* for gettimeofday() */ +#include /* for stringstream */ +#include /* for kill(), SIGUSR1 */ + +// requires apt-get install libboost-filesystem-dev: +#include + +#include /* for getaddrinfo() */ +#include /* for getaddrinfo() */ +#include /* for memset() */ +#include /* for inet_ntop() */ + +using namespace std; + +const int minute(60); +const int hour(60*minute); +const int day(24*hour); + +const int minimum_age(15*minute); +const int maximum_age(32*day); +const int probation(4*hour); + +#if 0 +void exeunt(const int sts){ + if (sts == ex_good) exit(sts); + +#ifndef PENALIZE_SPAMMERS + if (sts == ex_penaltybox) exit(sts); +#endif + +#ifndef KILL_GROUP + exit(sts); +#endif + + const char* foo = getenv("HI_Q_GROUP"); + if (!foo) exit(sts); + +// No point in signalling ourself: + sighandler_t rslt = signal(SIGUSR1, SIG_IGN); + if (rslt == SIG_ERR) { + cerr << "error setting signal" << endl; + } + int k = kill(-atoi(foo), SIGUSR1); + if (k) { + cerr << "kill failed on group " << atoi(foo) << " ... "; + perror(0); + } + exit(sts); +} +#endif + +#include /* for gettimeofday */ +#include /* for setw */ +#include /* for stat */ +#include /* for stat, creat */ +#include /* for stat, creat */ +#include /* for creat */ +#include /* for ofstream() */ + +#include "libltgrey.h" +#include "utils.h" +#include "qq_exit_codes.h" + +void whatsit::dump(const string var){ + char* str = getenv(var.c_str()); + cerr << progname + << "[" << mypid << "] " + << var; + if (str) cerr << " is set to '" << str << "'" << endl; + else cerr << " is not set." << endl; +} + +int whatsit::setup(){ + stringstream foo; + foo << basename(progname) << suffix + << "[" << mypid << "]"; + progid = foo.str(); + + ipvar = getenv("TCPREMOTEIP"); + if (ipvar) ipbase = ipvar; + hostvar = getenv("TCPREMOTEHOST"); + if (hostvar) hostname = hostvar; + return 0; +} + +void whatsit::update(const string msg, const timeval new_mod, + const timeval new_ac, const int penalty, const int stain){ + if (verbosity){ + if (penalty || stain || verbosity>1) cerr << progid << ": "; + if (penalty) cerr << " penalty " << penalty; + if (stain) cerr << " stain " << stain; + if (verbosity > 1) { + if (penalty || stain) cerr << "+"; // separation, punctuation + cerr << msg << ": " << ipbase; + if (hostname.length()) cerr << " " << hostname; + cerr << " mod_age: " << time_out(mod_age) + << " ac_age: " << time_out(ac_age); + } + cerr << endl; + } + timeval pen_mod(new_mod); + timeval stain_ac(new_ac); + if (penalty) { + pen_mod = now; + pen_mod.tv_sec += penalty; + } + if (stain) { + stain_ac = now; + stain_ac.tv_sec -= stain; + } + timeval upd[2] = { +// beware: access illogically comes *before* modification here: + stain_ac, + pen_mod + }; + if (utimes(ipname.c_str(), upd)) + cerr << "oops" << endl; +} + +int whatsit::doit(const int penalty, const int stain){ + + if (!ipvar) { + cerr << progid + << " TCPREMOTEIP not set???" << endl; + // should never happen + // although you can make it happen using a weird test-harness + return(ex_syserr); + } + +// see if our directory exists: + struct stat dirstat; + int rslt = stat(dirname.c_str(), &dirstat); + if (rslt != 0){ + if (errno != ENOENT) { + cerr << progid << ": stat failed for '" + << dirname << "' : "; + perror(0); + } + rslt = mkdir(dirname.c_str(), 0755); + if (rslt != 0) { + cerr << progid + << "uid " << getuid() + << ": mkdir failed for '" + << dirname << "' : "; + perror(0); + return(ex_syserr); + } + } + + ipname = dirname + "/" + ipbase; + struct stat ipstat; + rslt = stat(ipname.c_str(), &ipstat); + if (rslt != 0){ + if (errno != ENOENT) { + cerr << progid << ": stat failed for '" + << ipname << "' : "; + perror(0); + } + ofstream foo; + int fd = creat(ipname.c_str(), 0644); + if (fd < 0){ + cerr << progid << ": create failed for '" + << ipname << "' : "; + perror(0); + } + close(fd); + update("new customer", now, now, penalty, stain); + return(ex_greylisting); + } + +// now for really checking the greylist status: + mod_age = now.tv_sec - ipstat.st_mtime; + ac_age = now.tv_sec - ipstat.st_atime; + timeval mod_orig = {ipstat.st_mtime, 0}; + if (mod_age < 0) { + update("penalty box", mod_orig, now, penalty, stain); + return(ex_penaltybox); + } + if (mod_age < ac_age){ +// when he comes out on parole, he starts over with no reputation: + update("paroled spammer", now, now, penalty, stain); + return(ex_greylisting); + } + if (mod_age < minimum_age) { + update("early bird", mod_orig, now, penalty, stain); + return(ex_greylisting); + } + if (mod_age - ac_age < minimum_age // early bird, or completely unused + && mod_age > probation) { // did not diligently resubmit + update("disprobation", now, now, penalty, stain); + return(ex_greylisting); + } + if (ac_age > maximum_age) { + update("too old, starting over", now, now, penalty, stain); + return(ex_greylisting); + } +// if all checks are passed, must be OK: + update("returning customer", mod_orig, now, penalty, stain); + return 0; +} + +typedef vector VU; + +class VUx{ +public: + VU addr; + sa_family_t fam; + string str(); +}; + +string VUx::str(){ + char msgbuf[INET6_ADDRSTRLEN]; + const char* rslt = inet_ntop(fam, &addr[0], + msgbuf, sizeof(msgbuf)); + if (!rslt) rslt = ""; + return rslt; +} + +VUx parse_sockaddr(const sockaddr* ai_addr) { + void* numericAddress; + VUx rslt; + int addrsize; + rslt.addr = VU(0); + rslt.fam = ((sockaddr *)ai_addr)->sa_family; + switch (rslt.fam) { + case AF_INET: + numericAddress = &(((sockaddr_in *)ai_addr)->sin_addr.s_addr); + addrsize = sizeof(in_addr); + break; + case AF_INET6: + numericAddress = &(((sockaddr_in6 *)ai_addr)->sin6_addr.s6_addr); + addrsize = sizeof(in6_addr); + break; + default: + cerr << "?Unknown address family " << rslt.fam << endl; + return rslt; + } + unsigned char* foo = (unsigned char*) numericAddress; + rslt.addr = VU(foo, foo+addrsize); + return rslt; +} + +int diff(const VU aaa, const VU bbb){ + if(aaa.size() != bbb.size()) return 1; + for (unsigned int ii=0; ii < aaa.size(); ii++){ + if (aaa[ii] != bbb[ii]) return 1; + } + return 0; +} + +int whatsit::check_dns(){ + string addr("()"), host("()"); + vector checked; + int sts = check_dns_sub(addr, host, checked); + if (sts == 0) return sts; + if (sts != ex_badDNS) return sts; // possible ex_syserr +#if 1 + sts = 0; // demote badDNS to just a warning +#endif + cerr << progid; + if (!sts) cerr << " (warning)"; + cerr << " DNS inconsistency: " + << addr << " --> " + << host << " ==>"; + if (!checked.size()) cerr << " ()"; + else for (vector::const_iterator chk = checked.begin(); + chk != checked.end(); chk++) cerr << " " << *chk; + cerr << endl; + + return sts; +} + +int whatsit::check_dns_sub(string &addr, string &host, vector &checked){ + + struct addrinfo *result; + struct addrinfo *ipresult; + struct addrinfo *res; + addrinfo hints; + int error; + + memset(&hints, 0, sizeof(struct addrinfo)); +#if 1 + // restrict to TCP only; otherwise we get N records per address + hints.ai_protocol = IPPROTO_TCP; +#endif + + error = getaddrinfo(ipvar, NULL, &hints, &ipresult); + if (error == EAI_NONAME) return ex_badDNS; + if (error) { // some unexpected error + cerr << progid + << " odd error " << error + << " in getaddrinfo for " << ipvar + << " : " << gai_strerror(error) << endl; + return ex_syserr; + } + if (!ipresult) { + cerr << "should never happen (addr with no addrs?)" << endl; + return ex_syserr; + } + VUx ipAddr = parse_sockaddr(ipresult->ai_addr); + addr = ipAddr.str(); + + char* hostvar = getenv("TCPREMOTEHOST"); + if (hostvar) host = hostvar; + else return(ex_badDNS); + + error = getaddrinfo(hostvar, NULL, &hints, &result); + if (error == EAI_NONAME) return ex_badDNS; + if (error) { + cerr << progid + << " error " << error + << " compare " << EAI_NONAME + << " in getaddrinfo for " << ipvar + << " :: " << gai_strerror(error) << endl; + return ex_syserr; + } + +// loop over all returned results and check for a match. + for (res = result; res != NULL; res = res->ai_next){ + VUx hostAddr = parse_sockaddr(res->ai_addr); + checked.push_back(hostAddr.str()); + if (!diff(hostAddr.addr, ipAddr.addr)) { + ///// cerr << "match! " << ipAddr.addr.size() << endl; + goto done; + } + } + return ex_badDNS; + +done: + return 0; +} diff --git a/tools/libltgrey.h b/tools/libltgrey.h new file mode 100644 index 0000000..585ec01 --- /dev/null +++ b/tools/libltgrey.h @@ -0,0 +1,38 @@ +#include +#include /* for gettimeofday(), timeval */ +#include + +class whatsit{ +public: + std::string dirname; + std::string progname; + pid_t mypid; + timeval now; + char* ipvar; + char* hostvar; + std::string ipbase; + std::string ipname; + std::string hostname; + int mod_age; + int ac_age; + std::string suffix; + std::string progid; + int verbosity; + + whatsit(const std::string name, const std::string _dirname) + : dirname(_dirname), progname(name), mypid(getpid()), + mod_age(0), ac_age(0), + verbosity(0) + { + gettimeofday(&now, NULL); + } + int doit(const int penalty, const int stain); +// access comes after modification: + void update(const std::string msg, const timeval new_mod, + const timeval new_ac, const int penalty, const int stain); + int setup(); + int check_dns(); + int check_dns_sub(std::string &addr, std::string &host, + std::vector &checked); + void dump(const std::string var); +}; diff --git a/tools/ltgrey.c b/tools/ltgrey.c new file mode 100644 index 0000000..afdb4c1 --- /dev/null +++ b/tools/ltgrey.c @@ -0,0 +1,153 @@ +#include +#include /* for exit(), atoi() */ + +#include "libltgrey.h" +#include "utils.h" +#include "qq_exit_codes.h" + +using namespace std; +pid_t mypid; +string progname; + +#define exeunt exit + +// forward reference: +void scan(const string progid, const string p, const int copies=1); + +int main(int _argc, char** _argv){ + mypid = getpid(); + int argc(_argc); + char** argv(_argv); + const string dirname("/var/qmail/greylist"); + whatsit foo(argv[0], dirname); argc--; argv++; + int scanmode(0); + int copies(1); + int penalty(0); + int stain(0); + int check(0); + while (argc > 0) { + string arg = argv[0]; argc--; argv++; + if (prefix(arg, "-scan")) { + scanmode++; + } else if (prefix(arg, "-copy")) { + copies++; + } else if (prefix(arg, "-verbose")) { + foo.verbosity++; + } else if (prefix(arg, "-check")) { + check++; + } else if (prefix(arg, "-penalize") + || prefix(arg, "-penalty")) { + if (!argc){ + cerr << "Option '" << arg << "' requires an argument" << endl; + exeunt(ex_syserr); + } + penalty = atoi(*argv++); argc--; + } else if (prefix(arg, "-stain")) { + if (!argc){ + cerr << "Option '" << arg << "' requires an argument" << endl; + exeunt(ex_syserr); + } + stain = atoi(*argv++); argc--; + } else if (prefix(arg, "-suffix")) { + if (!argc){ + cerr << "Option '" << arg << "' requires an argument" << endl; + exeunt(ex_syserr); + } + foo.suffix += *argv++; argc--; + } else { + cerr << "Unrecognized arg: " << arg << endl; + exeunt(ex_syserr); + } + } + if (foo.setup()) return ex_syserr; + + if (scanmode) { + scan(foo.progid, dirname, copies); + return 0; + } + + int sts = foo.doit(penalty, stain); + if (sts == ex_syserr) return sts; + if (!check) return ex_good; + +// check mode ... perform some extra checks. +// Probably a better design would be to +// (a) make more thorough DNS checks, and +// (b) move all the DNS checking to a separate module + + int dns = foo.check_dns(); + if (dns == ex_syserr || dns == ex_spam) return dns; + exeunt(sts); +} + +////////////////////////////////////////////////////////////////////// +// requires apt-get install libboost-filesystem-dev: +#include +#include +#include /* for stat(), getaddrinfo() */ +#include /* for stat() */ +#include /* for stat() */ +#include /* for perror */ +#include + +const int minute(60); +const int hour(60*minute); +const int day(24*hour); + +const int minimum_age(15*minute); +const int maximum_age(32*day); +const int probation(4*hour); + +void scan(const string progid, const string p, const int copies){ + timeval now; + gettimeofday(&now, NULL); + using namespace boost::filesystem; + + if (is_directory(p)) { + for (directory_iterator itr(p); itr!=directory_iterator(); ++itr) { + string basename = itr->path().filename(); + for (int ii = 0; ii < copies; ii++) + cout << setw(20) << left << basename << ' '; // display filename only + if (is_regular_file(itr->status())) { +// cout << " [" << file_size(itr->path()) << ']'; + struct stat mystat; + string fn = p + "/" + basename; + int rslt = stat(fn.c_str(), &mystat); + if (rslt != 0){ + cerr << progid << ": stat failed for '" + << fn << "' : "; + perror(0); + } + int mod_age = now.tv_sec - mystat.st_mtime; + int ac_age = now.tv_sec - mystat.st_atime; + cout << setw(10) << time_out(mod_age) + << " " << setw(10) << time_out(ac_age); + if (0) { + + } else if (mod_age < 0) { + cout << " penalty"; + } 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 << "!"; + } else if (mod_age < minimum_age) { + cout << " young"; + if (mod_age != ac_age) cout << "!"; + } else if (mod_age == ac_age) { + cout << " unused"; + } else if (mod_age > maximum_age) { + cout << " expired"; + } else { + cout << " OK"; + } + } + cout << '\n'; + } + } + else { + // starting point is not a directory: + cout << (exists(p) ? "Found: " : "Not found: ") << p << '\n'; + } +} diff --git a/tools/makefile b/tools/makefile index 76df23b..f0a3f70 100644 --- a/tools/makefile +++ b/tools/makefile @@ -15,10 +15,10 @@ qmain = pido.c hi-q.c skrewt.c hi-test.c mail-scan.c greylist.c wripper.c qprogs = $(qmain:%.c=%) # sources for other main programs: -moremain = wripper.c bash-c.c +moremain = wripper.c bash-c.c ltgrey.c moreprogs = $(moremain:%.c=%) -nonmain = +nonmain = libltgrey.c sources = $(qmain) $(moremain) $(nonmain) @@ -37,10 +37,14 @@ all: $(qprogs) $(moreprogs) show: : --- $(qprogs) +++ $(moreprogs) -greylist: greylist.o +skrewt: skrewt.o $(CC) $< -lboost_filesystem-mt -lboost_system -o $@ -# $(CC) $< -lboost_filesystem -o $@ +greylist: greylist.o utils.o + $(CC) $^ -lboost_filesystem-mt -lboost_system -o $@ + +ltgrey: ltgrey.o utils.o libltgrey.o + $(CC) $^ -lboost_filesystem-mt -lboost_system -o $@ wripper: wripper.o $(CC) $< -o $@ diff --git a/tools/qq_exit_codes.h b/tools/qq_exit_codes.h new file mode 100644 index 0000000..2af6848 --- /dev/null +++ b/tools/qq_exit_codes.h @@ -0,0 +1,15 @@ +// error exit codes, mostly as stated in qmail.c +#define qq_exit_codes \ +foo(good, 0) ;\ +foo(spam, 21) ;\ +foo(penaltybox, 22) ;\ +foo(badDNS, 23) ;\ +foo(usage, 39) ;\ +foo(greylisting, 70) ;\ +foo(syserr, 71) ;\ +foo(comerr, 74) ; + +// expand the codes to make some names: +#define foo(name, num) const int ex_ ## name = num +qq_exit_codes +#undef foo diff --git a/tools/skrewt.c b/tools/skrewt.c index a43fd13..3fee644 100644 --- a/tools/skrewt.c +++ b/tools/skrewt.c @@ -37,19 +37,7 @@ void usage(const int sts){ exit(sts); } -// error exit codes, mostly as stated in qmail.c -#define ErrorCodes \ -foo(good, 0) ;\ -foo(spam, 21) ;\ -foo(permerr, 31) ;\ -foo(usage, 39) ;\ -foo(greylisting, 70) ;\ -foo(syserr, 71) ;\ -foo(comerr, 74) ; - -#define foo(name, num) const int ex_ ## name = num -ErrorCodes -#undef foo +#include "qq_exit_codes.h" ///////////////////////////////////////////////////////// @@ -316,8 +304,8 @@ int main(int _argc, const char** _argv){ headrec += "\n" + noCR(line); } // here with a fully assembled header record +// headrec (unlike line) contains no DOS CR characters int len = headrec.length(); - if (len && headrec[len-1] == '\r') len--; // reduced length, not counting if (len == 0) { saw_blank_line = 1; break; // no more headers in this message @@ -351,6 +339,7 @@ int main(int _argc, const char** _argv){ if (0) if (recno <= 6) cerr << progid << "#" << recno << " " << headrec << endl; } + if (saw_blank_line) {/* ignore */} cerr << progid <<" Mid '" << message_id << "'" << endl; // Headers are done. diff --git a/tools/utils.c b/tools/utils.c new file mode 100644 index 0000000..3ec6e4c --- /dev/null +++ b/tools/utils.c @@ -0,0 +1,44 @@ +#include +#include +#include +//#include /* for abs() */ +#include + +// strip off the directory part of a path, leaving just +// the basic filename +std::string basename(const std::string path){ + size_t where = path.rfind("/"); + if (where != std::string::npos) return path.substr(1+where); + return path; +} + +//////////////// +// little utility to help with argument parsing: +// +int prefix(const std::string shorter, const std::string longer){ + return shorter == longer.substr(0, shorter.length()); +} + +/////////////// +// print a time as (-)hh:mm:ss +// +std::string time_out(const int _ttt){ +using namespace std; + int ttt(abs(_ttt)); + int sec(ttt % 60); + int min((ttt / 60) % 60); + int hr(ttt / 3600); + stringstream foo; + int didsome(0); + if (_ttt < 0) foo << "-"; + if (hr) { + foo << hr << ":"; + didsome++; + } + if (didsome || min){ + foo << setw(didsome?2:1) << setfill('0') << min << ":"; + didsome++; + } + foo << setw(didsome?2:1) << setfill('0') << sec; + return foo.str(); +} diff --git a/tools/utils.h b/tools/utils.h new file mode 100644 index 0000000..450db85 --- /dev/null +++ b/tools/utils.h @@ -0,0 +1,3 @@ +std::string basename(const std::string path); +int prefix(const std::string shorter, const std::string longer); +std::string time_out(const int _ttt); -- cgit v1.2.3