Mercurial > dnsbl
diff 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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/spamass.cpp.in Sun Aug 26 19:03:17 2007 -0700 @@ -0,0 +1,433 @@ +/* + +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; +} +