summaryrefslogtreecommitdiff
path: root/tools/libltgrey.c
diff options
context:
space:
mode:
authorJohn Denker <jsd@av8n.com>2012-07-30 14:34:05 -0700
committerJohn Denker <jsd@av8n.com>2012-07-30 14:34:05 -0700
commit7024dc330299921af649330c45b99c21c3d7f022 (patch)
tree9f747a0423647b016376fe353ceea197a5848cbf /tools/libltgrey.c
parentce0dbe5332d4eb921c09cc48cc52634211d7089a (diff)
parentf8be4baf5a2318363b42f8883f66ed8a976dfc79 (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.c508
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';
+ }
+}