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;
+}
+