Mercurial > dnsbl
view src/spamass.cpp.in @ 163:97d7da45fe2a
spamassassin changes
author | carl |
---|---|
date | Sun, 26 Aug 2007 19:03:17 -0700 |
parents | |
children | 5809bcdc325b |
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 "config.h" #include "dnsbl.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$"; bool warnedmacro = false; /* have we logged that we couldn't fetch a macro? */ const int maxlen = 1000; // used for snprintf buffers SpamAssassin::SpamAssassin() { error = false; running = false; first_recipient = true; } 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_connect(mlfiPriv *priv_, int ip) { priv = priv_; ip4 = ip; } void SpamAssassin::mlfi_helo(char *helohost) { helo = helohost; } void SpamAssassin::mlfi_envfrom(char *from, char *qid) { envfrom = from; queueid = qid; } void SpamAssassin::mlfi_envrcpt(SMFICTX *ctx, char *envrcpt) { if (first_recipient) { /* 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 = smfi_getsymval(ctx, "j"); if (!macro_j) { macro_j = "localhost"; warnmacro("j", "ENVRCPT"); } /* Protocol used to receive the message */ macro_r = smfi_getsymval(ctx, "r"); if (!macro_r) { macro_r = "SMTP"; warnmacro("r", "ENVRCPT"); } macro_s = helo; /* Sendmail binary version */ macro_v = smfi_getsymval(ctx, "v"); if (!macro_v) { macro_v = "8.13.0"; warnmacro("v", "ENVRCPT"); } /* Sendmail .cf version */ macro_Z = smfi_getsymval(ctx, "Z"); if (!macro_Z) { macro_Z = "8.13.0"; warnmacro("Z", "ENVRCPT"); } /* Validated sending site's address */ macro__ = smfi_getsymval(ctx, "_"); if (!macro__) { macro__ = "unknown"; warnmacro("_", "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"); first_recipient = false; } void SpamAssassin::mlfi_header(char* headerf, char* headerv) { if (!running) Connect(); output(spamc_input); output(headerf); output(": "); output(headerv); output("\r\n"); spamc_input.empty(); } 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[0] = "/usr/bin/testspamc"; 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++); } 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; }