#! /usr/local/bin/pike import Protocols.XMLRPC; #if constant(Roxen) #define PMOD #endif #ifdef PMOD mixed parse(object id) #else int main(int argc, array(string) argv, array(string) env) #endif { string response; mixed err = catch { #ifdef PMOD if (!sizeof(id->data)) error("No data"); mixed c = decode_call(id->data); #else string req = Stdio.stdin->read(); if (!sizeof(req)) error("No data"); mixed c = decode_call(req); #endif mixed retval; switch (c->method_name) { case "learnspam": retval = learnspam(c); break; case "checkspam": retval = checkspam(c); break; default: break; } if (!retval) error("No data to return"); if (!arrayp(retval)) retval = ({ retval }); response = encode_response( retval ); }; if (err) { response = encode_response_fault(666, describe_error(err)); } #ifdef PMOD return Roxen.http_string_answer(response, "text/xml"); #else write("Content-type: text/xml\r\n\r\n%s", response ); #endif } mixed learnspam(mixed c) { if (sizeof(c->params) != 2) error("learnspam takes two parameters: an array of messageids, and an options struct"); mixed retval = ""; mapping options = ([ ]); /* accept a string for arg 1, and convert it to a 1-elem array */ if (stringp(c->params[0])) { c->params[0] = ({ c->params[0] }); } /* Accept a string as arg 2, and convert it to the mapping ([ "flag":$2 ]). This means you want defaults for any other options. */ if (stringp(c->params[1])) { c->params[1] = ([ "flag":c->params[1] ]); } options = c->params[1]; if (!options->user) options->user = "nouser"; if (options->flag != "HAM" && options->flag != "SPAM") error("Invalid spam indicator " + options->flag); object s = Sql.Sql("mysql://address@hidden/mail"); /* remove empty elements */ c->params[0] = c->params[0] - ({ "" }); /* fetch the messages */ array r = s->query("SELECT * FROM messages WHERE messageid in "+ "('" + map(c->params[0], s->quote)*"', '" + "')"); if (sizeof(r) == 0) { if (sizeof(c->params[0]) == 1) { retval += sprintf("%=-40s\n", "Your message was not found in the database, and cannot be analyzed."); } else { retval += sprintf("%=-40s\n", "None of the selected messages were found in the database. Nothing to do."); } return retval; } /* If we're the primary server, submit to razor, and submit to the slave server via XML-RPC */ if (!options->mirror) { retval += sprintf("%d message%s found in the database.\n", sizeof(r), sizeof(r) == 1?" was":"s were"); /* Fetch a list of messages already submitted. Only match ones flagged the same as the current request, so you can correct bad submissions. */ array dups = s->query("SELECT * FROM submitted WHERE messageid in " + "('" + map(r->messageid, s->quote)*"', '" + "') and flag = '" + options->flag + "'"); // retval += sprintf("%d dups for %O\n", sizeof(dups), r->messageid); if (sizeof(dups) == sizeof(r)) { if (sizeof(r) == 1) { retval += sprintf("%=-40s\n", "This message has already been submitted."); } else { retval += sprintf("%=-40s\n", "All of the selected messages have already been submitted."); } return retval; } if (sizeof(dups)) { retval += sprintf("%d message%s previously submitted.\n", sizeof(dups), sizeof(dups) == 1?" was":"s were"); r = filter(r, lambda(mapping element) { return search(dups->messageid, element->messageid) == -1;}); } } /* Build a fake mbox containing the messages */ string mbox = ""; string mboxname = sprintf("/tmp/mbox.%u.%u", getpid(), Thread.id_number()); foreach (r, mapping message) { mbox += sprintf("From %s %s\n%s\n\n", message->env_from, ctime(time())-"\n", message->message); } Stdio.FILE(mboxname, "ctw")->write(mbox); /* pipe the message to sa-learn */ retval += "Spamassassin results:\n"; retval += prepend(Process.popen(sprintf("/usr/local/bin/sa-learn %s --mbox %s 2>&1", (options->flag == "SPAM")?"--spam":"--ham", mboxname)), " "); /* pipe it to razor-report and also submit to our neighbor if we're the master server */ if (!options->mirror) { /* find the name of the slave */ string host = (System.gethostname()/".")[0]; switch (host) { case "email1": host = "email2"; break; case "email2": host = "email1"; break; default: error("Not running on a known host"); } retval += "Razor results:\n"; retval += prepend(Process.popen(sprintf("/usr/local/bin/%s -home=/usr/tmp/razor -f %s 2>&1 && echo Done.", (options->flag == "SPAM")?"razor-report":"razor-revoke", mboxname)), " "); retval += "Neighbor results:\n"; string nretval; /* This may fail if the slave server is down */ mixed e = catch { nretval = Protocols.XMLRPC.Client("http://"+host+"/spam/submit.pike") ["learnspam"](r->messageid, options + ([ "mirror":"1" ]))[0]; }; if (e) nretval = describe_error(e); retval += prepend(nretval, " "); /* This should not fail, but it's okay if it does. */ e = catch { s->query("INSERT IGNORE INTO submitted (messageid, env_from, env_rcpt, message, messagelen, user, flag) " + "SELECT messageid, env_from, env_rcpt, message, messagelen, '"+ s->quote(options->user)+"', '"+ s->quote(options->flag)+"' "+ "FROM messages WHERE messageid in "+ "('" + map(r->messageid, s->quote)*"', '" + "')"); }; if (e) retval += describe_error(e); } return retval; } mixed checkspam(mixed c) { string retval = ""; if (sizeof(c->params) != 1) error("checkspam takes one parameter: a string containing the messageid to check"); object s = Sql.Sql("mysql://address@hidden/mail"); /* fetch the message */ array r = s->query("SELECT * FROM messages WHERE messageid = %s", c->params[0]); if (sizeof(r) == 0) { retval += "Your message was not found in the database, and cannot be analyzed."; return retval; } /* Build a fake mbox containing the message */ string mbox; string mboxname = sprintf("/tmp/mbox.%u.%u", getpid(), Thread.id_number()); mbox = sprintf("From %s %s\n%s\n\n", r[0]->env_from, ctime(time())-"\n", r[0]->message); Stdio.FILE(mboxname, "ctw")->write(mbox); /* pipe the message to spamassassin */ retval += "Spamassassin results:\n"; mixed result = Process.popen(sprintf("/usr/local/bin/spamassassin -x -t < %s 2>&1", mboxname)); result = result / "\n"; /* find the text description at the bottom of the message */ int i; for (i=sizeof(result)-1; i>=0 ; i--) { if (search(result[i], "Content analysis details:")==0) break; } if (i < 0) error(result); return result[i..]*"\n"; } /* Takes a multiline string and prepends another string to each line */ string prepend(string text, string indent) { array ret = ({ }); foreach (text/"\n", string line) ret += ({ indent+line }); /* don't mess with the null string after a trailing eol */ if (ret[-1] == indent) ret[-1] = ""; return ret*"\n"; }