Mercurial > dnsbl
view src/spamass.cpp.in @ 201:752d4315675c stable-6-0-16
add reference to mercurial repository in the documentation
author | Carl Byington <carl@five-ten-sg.com> |
---|---|
date | Sat, 02 Feb 2008 12:31:53 -0800 |
parents | a4d313c2460b |
children | 92a5c866bdfa |
line wrap: on
line source
/* Copyright (c) 2007 Carl Byington - 510 Software Group, released under the GPL version 3 or any later version at your choice available at http://www.gnu.org/licenses/gpl-3.0.txt Based on spamass-milter by Georg C. F. Greve <greve@gnu.org> */ #include "includes.h" #include <errno.h> #include <fcntl.h> #include <poll.h> #include <signal.h> #include <string> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> static const char Id[] = "$Id$"; char *spamc = "@SPAMC@"; char *spamc_empty = ""; static bool warnedmacro = false; // have we logged that we couldn't fetch a macro? const int maxlen = 1000; // used for snprintf buffers SpamAssassin::SpamAssassin(mlfiPriv *priv_, int ip, char *helo_, char *from, char *qid) { error = false; running = false; first_recipient = true; priv = priv_; ip4 = ip; helo = helo_; envfrom = from; queueid = qid; pid = 0; pipe_io[0][0] = -1; pipe_io[0][1] = -1; pipe_io[1][0] = -1; pipe_io[1][1] = -1; } SpamAssassin::~SpamAssassin() { // close all pipes that are still open if (pipe_io[0][0] > -1) close(pipe_io[0][0]); if (pipe_io[0][1] > -1) close(pipe_io[0][1]); if (pipe_io[1][0] > -1) close(pipe_io[1][0]); if (pipe_io[1][1] > -1) close(pipe_io[1][1]); // child still running? if (running) { // make sure the pid is valid if (pid > 0) { // slaughter child kill(pid, SIGKILL); // wait for child to terminate int status; waitpid(pid, &status, 0); } } } void SpamAssassin::mlfi_envrcpt(SMFICTX *ctx, char *envrcpt) { if (first_recipient) { first_recipient = false; /* Send the envelope headers as X-Envelope-From: and X-Envelope-To: so that SpamAssassin can use them in its whitelist checks. Also forge as complete a dummy Received: header as possible because SA gets a lot of info from it. HReceived: $?sfrom $s $.$?_($?s$|from $.$_) $.$?{auth_type}(authenticated$?{auth_ssf} bits=${auth_ssf}$.) $.by $j ($v/$Z)$?r with $r$. id $i$?{tls_version} (version=${tls_version} cipher=${cipher} bits=${cipher_bits} verify=${verify})$.$?u for $u; $|; $.$b$?g (envelope-from $g)$. */ const char *macro_b, *macro_i, *macro_j, *macro_r, *macro_s, *macro_v, *macro_Z, *macro__; char date[32]; time_t tval; time(&tval); strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&tval)); macro_b = date; // queue ID macro_i = queueid; // FQDN of this site macro_j = getorwarnmacro(ctx, "j", "localhost", "ENVRCPT"); // Protocol used to receive the message, not really needed by spam assassin macro_r = "SMTP"; // helo value we already have macro_s = helo; // Sendmail binary version, not really needed by spam assassin macro_v = "8.13.0"; // Sendmail .cf version, not really needed by spam assassin macro_Z = "8.13.0"; // Validated sending site's address macro__ = getorwarnmacro(ctx, "_", "unknown", "ENVRCPT"); output(string("Received: from ") + macro_s + " (" + macro__+ ")\r\n\t" + "by " + macro_j + " (" + macro_v + "/" + macro_Z + ") with " + macro_r + " id " + macro_i + "\r\n\t" + "for " + envfrom + ";\r\n\t" + macro_b + "\r\n"); output(string("X-Envelope-From: ") + envfrom + "\r\n"); } output(string("X-Envelope-To: ") + envrcpt + "\r\n"); } void SpamAssassin::mlfi_header(char* headerf, char* headerv) { if (!running) Connect(); if (running) { output(spamc_input); spamc_input.empty(); } output(headerf); output(": "); output(headerv); output("\r\n"); } void SpamAssassin::mlfi_eoh() { output("\r\n"); } void SpamAssassin::mlfi_body(u_char *bodyp, size_t bodylen) { output((char *)bodyp, bodylen); } int SpamAssassin::mlfi_eom() { close_output(); // signal EOF to SpamAssassin input(); // read what the Assassin is telling us my_syslog(priv, "spamc returned " + spamc_output); return atoi(spamc_output.c_str()); } void SpamAssassin::Connect() { if (error) return; // set up pipes for in- and output error |= (pipe(pipe_io[0])); error |= (pipe(pipe_io[1])); if (error) return; // now execute SpamAssassin client for contact with SpamAssassin spamd // start child process pid = fork(); switch (pid) { case -1: // forking trouble. my_syslog(priv, "unable to fork for spamc"); error = true; close(pipe_io[0][0]); close(pipe_io[0][1]); close(pipe_io[1][0]); close(pipe_io[1][1]); pipe_io[0][0] = -1; pipe_io[0][1] = -1; pipe_io[1][0] = -1; pipe_io[1][1] = -1; return; case 0: // +++ CHILD +++ // close unused pipes close(pipe_io[1][0]); close(pipe_io[0][1]); // redirect stdin(0), stdout(1) and stderr(2) dup2(pipe_io[0][0],0); dup2(pipe_io[1][1],1); dup2(pipe_io[1][1],2); closeall(3); // execute spamc char* argv[3]; argv[0] = spamc; argv[1] = "-c"; argv[2] = NULL; execvp(argv[0] , argv); // does not return! _exit(1); // exec failed break; } // +++ PARENT +++ // close unused pipes close(pipe_io[0][0]); close(pipe_io[1][1]); pipe_io[0][0] = -1; pipe_io[1][1] = -1; // mark the pipes non-blocking if (fcntl(pipe_io[0][1], F_SETFL, O_NONBLOCK) == -1) error = true; #if 0 /* don't really need to make the sink pipe nonblocking */ if (fcntl(pipe_io[1][0], F_SETFL, O_NONBLOCK) == -1) error = true; #endif // we have to assume the client is running now. running = true; } void SpamAssassin::output(const char* buffer, size_t size) { // if there are problems, fail. if (error) return; if (!running) { // buffer it spamc_input.append(buffer, size); return; } // send to SpamAssassin long total = 0; long wsize = 0; string reason; int status; do { struct pollfd fds[2]; int nfds = 2, nready; fds[0].fd = pipe_io[0][1]; fds[0].events = POLLOUT; fds[0].revents = 0; fds[1].fd = pipe_io[1][0]; fds[1].events = POLLIN; fds[1].revents = 0; nready = poll(fds, nfds, 1000); if (nready == -1) { my_syslog(priv, "poll failed"); error = true; return; } if (fds[1].revents & (POLLERR|POLLNVAL|POLLHUP)) { my_syslog(priv, "poll says my read pipe is busted"); error = true; return; } if (fds[0].revents & (POLLERR|POLLNVAL|POLLHUP)) { my_syslog(priv, "poll says my write pipe is busted"); error = true; return; } if (fds[1].revents & POLLIN) { read_pipe(); } if (fds[0].revents & POLLOUT) { switch(wsize = write(pipe_io[0][1], (char *)buffer + total, size - total)) { case -1: if (errno == EAGAIN) continue; reason = string(strerror(errno)); // close the pipes close(pipe_io[0][1]); close(pipe_io[1][0]); pipe_io[0][1] = -1; pipe_io[1][0] = -1; // Slaughter child kill(pid, SIGKILL); // wait for child to terminate waitpid(pid, &status, 0); my_syslog(priv, "write error: " + reason); error = true; running = false; return; default: total += wsize; break; } } } while ( total < size ); } void SpamAssassin::output(const char* buffer) { output(buffer, strlen(buffer)); } void SpamAssassin::output(string buffer) { output(buffer.c_str(), buffer.size()); } void SpamAssassin::close_output() { if (close(pipe_io[0][1])) my_syslog(priv, "close error: " + string(strerror(errno))); pipe_io[0][1] = -1; } void SpamAssassin::input() { if (!running || error) return; empty_and_close_pipe(); if (running) { // wait until child is dead int status; if (waitpid(pid, &status, 0) < 0) { error = true; }; } running = false; } int SpamAssassin::read_pipe() { long size; int status; char iobuff[1024]; string reason; if (pipe_io[1][0] == -1) return 0; size = read(pipe_io[1][0], iobuff, 1024); if (size < 0) { reason = string(strerror(errno)); // Close remaining pipe. close(pipe_io[1][0]); pipe_io[1][0] = -1; // Slaughter child kill(pid, SIGKILL); // wait for child to terminate waitpid(pid, &status, 0); my_syslog(priv, "read error: " + reason); size = 0; error = true; running = false; } else if (size == 0) { // EOF. Close the pipe if (close(pipe_io[1][0])) { error = true; my_syslog(priv, "close error: " + string(strerror(errno))); } pipe_io[1][0] = -1; } else { // append to mail buffer spamc_output.append(iobuff, size); } return size; } void SpamAssassin::empty_and_close_pipe() { while (read_pipe()) ; } void SpamAssassin::closeall(int fd) { int fdlimit = sysconf(_SC_OPEN_MAX); while (fd < fdlimit) close(fd++); } char *SpamAssassin::getorwarnmacro(SMFICTX *ctx, char *macro, char *def, char *scope) { char *rc = smfi_getsymval(ctx, macro); if (!rc) { rc = def; warnmacro(macro, scope); } return rc; } void SpamAssassin::warnmacro(char *macro, char *scope) { if (warnedmacro) return; char buf[maxlen]; snprintf(buf, sizeof(buf), "Could not retrieve sendmail macro %s. Add it to confMILTER_MACROS_%s for better results.", macro, scope); my_syslog(priv, buf); warnedmacro = true; }