#include /* for exit(), getenv() */ #include #include #include #include /* for stat() */ #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 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); // 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; 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; } //////////////// // 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); 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: 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, const string _dirname) : dirname(_dirname), progname(name), mypid(getpid()), mod_age(0), ac_age(0) { gettimeofday(&now, NULL); } int doit(const int penalty=0); // access comes after modification: void update(const string msg, const timeval new_mod, const timeval new_ac, const int penalty=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 p, const int copies=1){ 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 << 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) << 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"; } 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'; } } 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, pen_mod }; utimes(ipname.c_str(), upd); } int main(int _argc, char** _argv){ progname = *_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); while (argc > 0) { string arg = argv[0]; argc--; argv++; if (prefix(arg, "-scan")) { scanmode++; } else if (prefix(arg, "-copy")) { copies++; } 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 { cerr << "Unrecognized arg: " << arg << endl; exeunt(ex_syserr); } } if (scanmode) { scan(dirname, copies); return 0; } return foo.doit(penalty); } int whatsit::doit(const int penalty){ char* ipvar = getenv("TCPREMOTEIP"); if (!ipvar) { 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 << "[" << mypid << "] " << " from " << ipbase << " ... TCPREMOTEHOST not set???" << endl; exeunt(ex_spam); } else { hostname = hostvar; } // 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); exeunt(ex_syserr); } } 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); update("new customer", now, now); exeunt(ex_greylisting); } // 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 < 0) { 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); } 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); } if (ac_age > maximum_age) { 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, penalty); return 0; }