#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; }