diff options
author | John Denker <jsd@av8n.com> | 2012-07-30 14:34:05 -0700 |
---|---|---|
committer | John Denker <jsd@av8n.com> | 2012-07-30 14:34:05 -0700 |
commit | 7024dc330299921af649330c45b99c21c3d7f022 (patch) | |
tree | 9f747a0423647b016376fe353ceea197a5848cbf /tools/libltgrey.c | |
parent | ce0dbe5332d4eb921c09cc48cc52634211d7089a (diff) | |
parent | f8be4baf5a2318363b42f8883f66ed8a976dfc79 (diff) |
Merge branch 'master' of ephedra:usr/src/qmail into e_master
Conflicts:
tools/makefile
Diffstat (limited to 'tools/libltgrey.c')
-rw-r--r-- | tools/libltgrey.c | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/tools/libltgrey.c b/tools/libltgrey.c new file mode 100644 index 0000000..4e92665 --- /dev/null +++ b/tools/libltgrey.c @@ -0,0 +1,508 @@ +#include <stdlib.h> /* for exit(), getenv() */ +#include <iostream> +#include <iomanip> +#include <string> + +#include <sys/types.h> /* for stat(), getaddrinfo() */ +#include <sys/stat.h> /* for stat() */ +#include <unistd.h> /* for stat() */ +#include <stdio.h> /* for perror */ +#include <errno.h> /* for ENOENT */ +#include <fstream> /* for ofstream() */ +#include <fcntl.h> /* for creat() */ +#include <sys/time.h> /* for gettimeofday() */ +#include <sstream> /* for stringstream */ +#include <signal.h> /* for kill(), SIGUSR1 */ + +// requires apt-get install libboost-filesystem-dev: +#include <boost/filesystem.hpp> + +#include <sys/socket.h> /* for getaddrinfo() */ +#include <netdb.h> /* for getaddrinfo() */ +#include <string.h> /* for memset() */ +#include <arpa/inet.h> /* 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 <sys/time.h> /* for gettimeofday */ +#include <iomanip> /* for setw */ +#include <unistd.h> /* for stat */ +#include <sys/types.h> /* for stat, creat */ +#include <sys/stat.h> /* for stat, creat */ +#include <fcntl.h> /* for creat */ +#include <fstream> /* for ofstream() */ + +#include "libltgrey.h" +#include "utils.h" +#include "qq_exit_codes.h" + +// constructor +whatsit::whatsit(const std::string name, const std::string _parent_dir) +: parent_dir(_parent_dir), progname(name), mypid(getpid()), + verbosity(0) +{ +// expand the codes to make some <const int> names: +# define foo(name) decode_40[name] = #name; + state_40_macro +# undef foo +} + +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(); + + return 0; +} + +#if 1 +void whatsit::update(const string msg, const timeval new_mod, + const timeval new_ac, const int penalty, const int stain){ +} +#else +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; +} +#endif + +int whatsit::maybe_mkdir(const string somedir, const string msg){ +// see if our directory exists: + struct stat dirstat; + int rslt = stat(somedir.c_str(), &dirstat); + if (rslt != 0){ + if (errno != ENOENT) { + cerr << progid << " stat failed for " + << msg + << " '" << somedir << "' : "; + perror(0); + } + rslt = mkdir(somedir.c_str(), 0755); + if (rslt != 0) { + cerr << progid + << "uid " << getuid() + << " mkdir failed for " + << msg + << "' " << somedir << "' : "; + perror(0); + return(ex_syserr); + } + } + return 0; +} + +#if 1 +int whatsit::doit(const int penalty, const int stain){ + return ex_syserr; +} + +#else +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); + } + + maybe_mkdir(parent_dir, "parent dir"); + maybe_mkdir(parent_dir + "/quarante", "quarantine dir"); + maybe_mkdir(parent_dir + "/repute", "reputation dir"); + + 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; +} +#endif + +typedef vector<unsigned char> 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(const char* ipvar, const char* namevar){ + if (!ipvar) { + cerr << progid << " check_dns: no addr specified." << endl; + return ex_syserr; + } + string addr("()"), host("()"); + vector<string> checked; + int rslt = check_dns_sub(ipvar, namevar, addr, host, checked); + int sts = rslt; +#if 1 + sts = 0; // demote badDNS to just a warning +#endif + if (rslt || verbosity) { + cerr << progid; + if (rslt && !sts) cerr << " (warning)"; + if (rslt) cerr << " DNS inconsistency: "; + else cerr << " DNS OK: "; + cerr << addr << " --> " + << host << " ==>"; + if (!checked.size()) cerr << " ()"; + else for (vector<string>::const_iterator chk = checked.begin(); + chk != checked.end(); chk++) cerr << " " << *chk; + cerr << endl; + } + return sts; +} + +int whatsit::check_dns_sub(const char* ipvar, const char* namevar, + string &addr, string &host, vector<string> &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 + +// convert address-as-string to address-as-bits. +// also get information about family + 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; + } + +// reconvert from bits to string + family info + VUx ipAddr = parse_sockaddr(ipresult->ai_addr); + addr = ipAddr.str(); + + if (namevar) { + // inverse lookup already done + host = namevar; + } else { + // namevar not specified; do inverse lookup on our own + + char hostname[NI_MAXHOST] = ""; + char service[NI_MAXHOST] = ""; + +#ifdef convert_bits_to_string + const char* rslt = inet_ntop(ipAddr.fam, &ipAddr.addr[0], + msgbuf, sizeof(msgbuf)); + if (rslt) fprintf(stdout, "%s addrsize: %2d --> ", + msgbuf, addrsize); +#endif + error = getnameinfo(ipresult->ai_addr, ipresult->ai_addrlen, + hostname, NI_MAXHOST, service, NI_MAXHOST, 0); + if (error != 0) { + cerr << progid << " reverse DNS lookup failed: " + << gai_strerror(error) << endl; + return (ex_badDNS); + } + namevar = hostname; // a char*, used below + host = hostname; // a string, returned to caller + } + + error = getaddrinfo(namevar, 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; +} + +state_40 whatsit::get40(const string mid){ + timeval junk[2]; + return get40(mid, junk); +} + +state_40 whatsit::get40(const string mid, timeval times[2]){ + timeval now; + gettimeofday(&now, NULL); + times[0] = times[1] = now; // in case of early return + string fname = parent_dir + "/quarante/mid_" + purify(mid); + //xxx cerr << ".... " << fname << endl; + struct stat mid_stat; + int rslt = stat(fname.c_str(), &mid_stat); + if (rslt != 0) { + if (errno == ENOENT) return unseen; + cerr << progid << " stat: unexpected failure for '" + << fname << "' : "; + perror(0); + return fail; + } + times[upd_ac ].tv_sec = mid_stat.st_atime; + times[upd_ac ].tv_usec = 0; + times[upd_mod].tv_sec = mid_stat.st_mtime; + times[upd_mod].tv_usec = 0; + int mod_age = now.tv_sec - mid_stat.st_mtime; +// int ac_age = now.tv_sec - mid_stat.st_atime; + if (mod_age < minimum_age) return green; + if (mod_age < probation) return ripe; + return spoiled; +} + +int whatsit::set40(const string mid, const int shift){ + string fname = parent_dir + "/quarante/mid_" + purify(mid); + //xxx cerr << ".... " << fname << endl; + int fd = creat(fname.c_str(), 0644); + if (fd < 0){ + cerr << progid << ": create failed for '" + << fname << "' : "; + perror(0); + } + if (!shift) { + close(fd); + return 0; + } + + struct stat mid_stat; + int rslt = fstat(fd, &mid_stat); + close(fd); + if (rslt != 0) { + cerr << progid << " fstat: unexpected failure for '" + << fname << "' : "; + perror(0); + return ex_syserr; + } + + timeval now; + gettimeofday(&now, NULL); + timeval upd[2] = { + now, // access + now // modification + }; + upd[upd_mod].tv_sec += shift; + if (utimes(fname.c_str(), upd)){ + cerr << progid << " utimes failed!" << endl; + return ex_syserr; + } + return 0; +} + +void whatsit::scan40(const int copies){ + timeval now; + gettimeofday(&now, NULL); + using namespace boost::filesystem; + + string dir40 = parent_dir + "/quarante"; + + if (is_directory(dir40)) { + for (directory_iterator itr(dir40); itr!=directory_iterator(); ++itr) { + string basename = itr->path().filename(); + if (is_regular_file(itr->status())) { + string tag("mid_"); + if (prefix(tag, basename)) { + for (int ii = 0; ii < copies; ii++) + cout << setw(20) << left << basename; // display filename only + + timeval times[2]; + state_40 rslt = get40(basename.substr(tag.length()), times); + int mod_age = now.tv_sec - times[upd_mod].tv_sec; + int ac_age = now.tv_sec - times[upd_ac ].tv_sec; + cout << setw(10) << right << time_out(mod_age) + << " " << setw(10) << right << time_out(ac_age); + cout << " " << decode_40[rslt]; + cout << endl; + } else { + /* filename does not begin with tag "mid_" */ + } + } + } + } + else { + // starting point is not a directory: + cout << (exists(dir40) ? "Found: " : "Not found: ") << dir40 << '\n'; + } +} |