* (c) 2003 Mark Wooding
*/
-/*----- Licensing notice --------------------------------------------------*
+/*----- Licensing notice --------------------------------------------------*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include <ctype.h>
#include <errno.h>
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/unistd.h>
+#include <syslog.h>
+
#include <libspamc.h>
/*----- Main code ---------------------------------------------------------*/
if (!p) return (d);
errno = 0;
f = strtod(p, &q);
- if (errno) return (d);
+ if (errno) {
+ syslog(LOG_ERR, "bad floating value `%s' for %s'; ignoring", p, e);
+ return (d);
+ }
errno = err;
return (f);
}
l = strtol(p, &q, 0);
if (errno) return (d);
errno = err;
- if (l < 0 || l > INT_MAX) return (d);
+ if (l < 0 || l > INT_MAX) {
+ syslog(LOG_ERR, "bad integer value `%s' for `%s'; ignoring", p, e);
+ return (d);
+ }
return ((int)l);
}
-int shovel(int from, int to)
+static int safewrite(int fd, const void *p, size_t sz)
+{
+ const char *pp = p;
+ ssize_t n;
+
+ while (sz > 0) {
+ n = write(fd, pp, sz);
+ if (sz <= 0 && errno != EINTR && errno != EAGAIN)
+ return (-1);
+ sz -= n;
+ pp += n;
+ }
+ return (0);
+}
+
+static int shovel(int from, int to, const char *what)
{
char buf[4096];
ssize_t n;
- char *p;
- size_t r;
for (;;) {
n = read(from, buf, sizeof(buf));
- if (n < 0 && errno != EINTR && errno != EAGAIN)
+ if (n < 0 && errno != EINTR && errno != EAGAIN) {
+ syslog(LOG_ERR, "failed to read %s: %m", what);
return (-1);
+ }
else if (!n)
return (0);
- p = buf;
- r = n;
- while (r) {
- n = write(to, p, n);
- if (n <= 0 && errno != EINTR && errno != EAGAIN)
- return (-1);
- r -= n;
- p += n;
+ if (safewrite(to, buf, n) < 0) {
+ syslog(LOG_ERR, "failed to write %s: %m", what);
+ return (-1);
}
}
}
+#define ENVBUF 4096
+#define NRCPT 16
+struct envelope {
+ unsigned f;
+#define EF_TRUNC 1u
+ char buf[ENVBUF + 1];
+ size_t n;
+ const char *send;
+ const char *rcpt[NRCPT];
+ size_t nrcpt;
+};
+
+static int readenv(struct envelope *e)
+{
+ ssize_t n;
+ const char *p, *l;
+
+ /* Read the raw envelope data. */
+ e->n = 0;
+ e->f = 0;
+ while (e->n < ENVBUF) {
+ n = read(1, e->buf + e->n, ENVBUF - e->n);
+ if (n < 0 && errno != EINTR && errno != EAGAIN) {
+ syslog(LOG_ERR, "failed to read envelope: %m");
+ return (-1);
+ }
+ if (!n) break;
+ e->n += n;
+ }
+ e->buf[e->n] = 0;
+
+ /* Parse up the envelope data. */
+ p = e->buf; l = p + e->n;
+ e->nrcpt = 0;
+ if (*p++ != 'F') {
+ e->send = "<invalid>";
+ syslog(LOG_ERR, "corrupt envelope (no sender marker)");
+ return (-1);
+ }
+ e->send = p;
+ for (;;) {
+ if (p >= l) goto trunc;
+ else if (!*p++) break;
+ }
+
+ for (;;) {
+ if (p >= l) goto trunc;
+ if (!*p) break;
+ else if (*p++ != 'T') {
+ syslog(LOG_ERR, "corrupt envelope (no recipient marker)");
+ return (-1);
+ }
+ if (p >= l) goto trunc;
+ if (e->nrcpt < NRCPT) e->rcpt[e->nrcpt++] = p;
+ for (;;) {
+ if (p >= l) goto trunc;
+ else if (!*p++) break;
+ }
+ }
+ return (0);
+
+ /* Failed to reach the final terminator; maybe there's more. This isn't a
+ * very bad situation.
+ */
+trunc:
+ e->f |= EF_TRUNC;
+ return (0);
+}
+
+static void real(void)
+{
+ const char *qmq =
+ strenv("QMAIL_CHECKSPAM_QUEUE", "/var/qmail/bin/qmail-queue");
+ execlp(qmq, qmq, (char *)0);
+ syslog(LOG_ERR, "failed to exec %s: %m", qmq);
+ exit(56);
+}
+
+static int split(int *fd_m, int *fd_e)
+{
+ pid_t kid;
+ int pm[2], pe[2];
+
+ if (pipe(pm) || pipe(pe)) return (-1);
+ if ((kid = fork()) < 0) return (-1);
+ if (kid) {
+ dup2(pm[0], 0); close(pm[0]); close(pm[1]);
+ dup2(pe[0], 1); close(pe[0]); close(pe[1]);
+ real();
+ } else {
+ close(pm[0]); *fd_m = pm[1];
+ close(pe[0]); *fd_e = pe[1];
+ }
+ return (0);
+}
+
+static int writemsg(struct message *m, int fd_m, int shovelp)
+{
+ if (message_write(fd_m, m) < 0) {
+ syslog(LOG_ERR, "failed to write message body: %m");
+ return (-1);
+ }
+ if (shovelp && shovel(0, fd_m, "message body"))
+ return (-1);
+ close(fd_m);
+ return (0);
+}
+
+static int writeenv(struct envelope *e, int fd_e)
+{
+ if (safewrite(fd_e, e->buf, e->n) < 0) {
+ syslog(LOG_ERR, "failed to write envelope: %m");
+ return (-1);
+ }
+ if ((e->f & EF_TRUNC) && shovel(1, fd_e, "envelope"))
+ return (-1);
+ close(fd_e);
+ return (0);
+}
+
+static void logenv(struct envelope *e)
+{
+ size_t i;
+
+ syslog(LOG_NOTICE, "sender = %s", e->send);
+ for (i = 0; i < e->nrcpt; i++)
+ syslog(LOG_NOTICE, "recipient %lu = %s", (unsigned long)i, e->rcpt[i]);
+}
+
int main(int argc, char *argv[])
{
struct sockaddr sa;
struct message m;
- int fd_m[2], fd_e[2];
- pid_t kid;
- int rc;
+ struct envelope e;
+ int fd_m, fd_e;
+ const char *p;
+ double thresh;
+ int rc, port;
+ /* Set up logging output. */
+ if ((p = strrchr(argv[0], '/')) == 0) p = argv[0]; else p++;
+ openlog(p, LOG_PID, LOG_MAIL);
+
+ /* Read configuration from the environment. */
+ if (getenv("RELAYCLIENT")) real();
m.max_len = intenv("QMAIL_CHECKSPAM_MAXLEN", 2 * 1024 * 1024);
m.timeout = intenv("QMAIL_CHECKSPAM_TIMEOUT", 300);
+
+ /* Slurp an initial chunk of the message. */
rc = message_read(0, 0, &m);
- if (rc != 0 && rc != EX_TOOBIG)
- return (54);
- if (!rc) {
- if (lookup_host(strenv("QMAIL_CHECKSPAM_SPAMDHOST", "localhost"),
- intenv("QMAIL_CHECKSPAM_SPAMDPORT", 783),
- &sa))
- return (56);
- if (message_filter(&sa, "spamd", 0, &m))
- return (74);
- if (m.score >= dblenv("QMAIL_CHECKSPAM_THRESH", m.threshold))
- return (31);
- }
- if (pipe(fd_m) || pipe(fd_e))
- return (56);
- if ((kid = fork()) < 0)
- return (56);
- if (!kid) {
- close(fd_m[0]);
- close(fd_e[0]);
- if (message_write(fd_m[1], &m) < 0)
- _exit(127);
- if (rc == EX_TOOBIG && shovel(0, fd_m[1]))
- _exit(127);
- close(fd_m[1]);
- if (shovel(1, fd_e[1]))
- _exit(127);
- close(fd_e[1]);
- _exit(0);
+
+ switch (rc) {
+
+ case 0:
+ /* We read the message body OK. Now try to slurp in the envelope and
+ * write a useful message.
+ */
+ if (readenv(&e)) return (54);
+ syslog(LOG_NOTICE, "scanning message");
+ logenv(&e);
+
+ /* Filter the message and see what happens. */
+ p = strenv("QMAIL_CHECKSPAM_SPAMDHOST", "localhost");
+ port = intenv("QMAIL_CHECKSPAM_SPAMDPORT", 783);
+ if (lookup_host(p, port, &sa)) {
+ syslog(LOG_ERR, "lookup failed for host `%s', port %d", p, port);
+ return (56);
+ }
+ if (message_filter(&sa, "spamd", 0, &m)) {
+ syslog(LOG_ERR, "filter failed unexpectedly");
+ return (74);
+ }
+
+ /* Check the resulting score. */
+ thresh = dblenv("QMAIL_CHECKSPAM_THRESH", m.threshold);
+ if (m.score >= thresh) {
+ syslog(LOG_NOTICE, "rejecting: score %g >= %g", m.score, thresh);
+ return (31);
+ }
+ syslog(LOG_NOTICE, "accepting: score %g < %g", m.score, thresh);
+
+ /* Send the remaining stuff on to the real server. */
+ if (split(&fd_m, &fd_e)) return (56);
+ if (writemsg(&m, fd_m, 0) || writeenv(&e, fd_e)) _exit(127);
+ break;
+
+ case EX_TOOBIG:
+ /* Message was too big. We must pass it all on to the real server
+ * before picking up the envelope.
+ */
+ syslog(LOG_NOTICE, "message too large for filtering");
+ if (split(&fd_m, &fd_e)) return (56);
+ if (writemsg(&m, fd_m, 1) || readenv(&e)) _exit(127);
+ logenv(&e);
+ if (writeenv(&e, fd_e)) _exit(127);
+ break;
+
+ default:
+ syslog(LOG_ERR, "failed to read message (rc = %d): %m", rc);
+ return (54);
}
- dup2(fd_m[0], 0);
- dup2(fd_e[0], 1);
- close(fd_m[0]);
- close(fd_e[0]);
- close(fd_m[1]);
- close(fd_e[1]);
- execlp(strenv("QMAIL_CHECKSPAM_QUEUE", "/var/qmail/bin/qmail-queue"),
- (char *)0);
- fprintf(stderr, "failed to exec: %s\n", strerror(errno));
- return (56);
+ return (0);
}
/*----- That's all, folks -------------------------------------------------*/