# HG changeset patch # User carl # Date 1188180197 25200 # Node ID 97d7da45fe2a03a577a4512350b21282011d1ce6 # Parent c4bce911c27649b8f195ec50809cdf63da9cf42f spamassassin changes diff -r c4bce911c276 -r 97d7da45fe2a AUTHORS --- a/AUTHORS Sat Jul 14 12:25:17 2007 -0700 +++ b/AUTHORS Sun Aug 26 19:03:17 2007 -0700 @@ -7,3 +7,4 @@ Stephen Johnson Jeff Evans G.W. Haywood + Georg C. F. Greve - spamassassin milter diff -r c4bce911c276 -r 97d7da45fe2a Makefile.am --- a/Makefile.am Sat Jul 14 12:25:17 2007 -0700 +++ b/Makefile.am Sun Aug 26 19:03:17 2007 -0700 @@ -5,12 +5,10 @@ CLEANFILES = dnsbl xml/dnsbl xml/Makefile EXTRA_DIST = dnsbl.rc $(hack_DATA) dnsbl.spec $(wildcard xml/h*) $(wildcard xml/M*) $(wildcard xml/d*) -dnsbl: $(srcdir)/dnsbl.rc - rm -f dnsbl - echo "#!" $(BASH) >dnsbl - cat $(srcdir)/dnsbl.rc | \ +dnsbl: dnsbl.rc + cat dnsbl.rc | \ sed -e "s,SBINDIR,$(sbindir),g" | \ - sed -e "s,SYSCONFDIR,$(sysconfdir),g" >>dnsbl + sed -e "s,SYSCONFDIR,$(sysconfdir),g" >dnsbl chkconfig: dnsbl /usr/bin/getent passwd dnsbl >/dev/null || /usr/sbin/useradd -r -d /etc/dnsbl -M -c "dnsbl pseudo-user" -s /sbin/nologin dnsbl diff -r c4bce911c276 -r 97d7da45fe2a configure.in --- a/configure.in Sat Jul 14 12:25:17 2007 -0700 +++ b/configure.in Sun Aug 26 19:03:17 2007 -0700 @@ -1,13 +1,14 @@ AC_PREREQ(2.59) -AC_INIT(dnsbl,6.03,carl@five-ten-sg.com) +AC_INIT(dnsbl,6.04,carl@five-ten-sg.com) AC_CONFIG_SRCDIR([config.h.in]) AC_CONFIG_HEADER([config.h]) AM_INIT_AUTOMAKE($PACKAGE_NAME,$PACKAGE_VERSION) # Checks for programs. -AC_PATH_PROGS(BASH, bash) +AC_PATH_PROG(BASH, bash) +AC_PATH_PROG(SPAMC, spamc) AC_PROG_CXX AC_PROG_CC AC_PROG_CPP @@ -18,7 +19,7 @@ # Checks for header files. AC_HEADER_SYS_WAIT -AC_CHECK_HEADERS([arpa/inet.h netdb.h netinet/in.h sys/ioctl.h sys/socket.h syslog.h unistd.h]) +AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h sys/ioctl.h sys/socket.h syslog.h unistd.h]) # Checks for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL @@ -32,17 +33,20 @@ AC_FUNC_GETPGRP AC_HEADER_STDC AC_FUNC_STAT -AC_CHECK_FUNCS([gethostbyname gethostname memmove memset socket strchr strdup strncasecmp strrchr strstr strtol]) +AC_FUNC_STRFTIME +AC_CHECK_FUNCS([dup2 gethostbyname gethostname memmove memset socket strchr strdup strerror strncasecmp strrchr strstr strtol]) # check for posix threads ACX_PTHREAD AC_CONFIG_FILES([Makefile + dnsbl.rc dnsbl.spec html/Makefile info/Makefile man/Makefile src/Makefile + src/spamass.cpp xml/Makefile xml/dnsbl]) AC_OUTPUT diff -r c4bce911c276 -r 97d7da45fe2a dnsbl.conf --- a/dnsbl.conf Sat Jul 14 12:25:17 2007 -0700 +++ b/dnsbl.conf Sun Aug 26 19:03:17 2007 -0700 @@ -19,6 +19,7 @@ html_limit off; host_limit on 20 "Mail containing excessive host names rejected"; host_limit soft 20; + spamassassin 4; }; // backscatter prevention - don't send bounces for mail that we accepted but could not forward @@ -52,6 +53,7 @@ html_tags { include "html-tags.conf"; }; html_limit off; host_limit soft 20; + spamassassin 5; }; env_to { @@ -100,6 +102,6 @@ # dcc_from { include "/var/dcc/whitecommon"; }; }; - autowhite 90 "my-auto-whitelist"; + # autowhite 90 "my-auto-whitelist"; }; diff -r c4bce911c276 -r 97d7da45fe2a dnsbl.rc --- a/dnsbl.rc Sat Jul 14 12:25:17 2007 -0700 +++ b/dnsbl.rc Sun Aug 26 19:03:17 2007 -0700 @@ -1,4 +1,4 @@ -# -- bash header installed by automake -- +#!/bin/sh # # dnsbl This script controls the dnsbl milter daemon. # diff -r c4bce911c276 -r 97d7da45fe2a dnsbl.rc.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dnsbl.rc.in Sun Aug 26 19:03:17 2007 -0700 @@ -0,0 +1,67 @@ +#!@BASH@ +# +# dnsbl This script controls the dnsbl milter daemon. +# +# +# chkconfig: 2345 79 31 +# description: dnsbl is an interface between MTA and the various dns blocking lists +# processname: dnsbl +# pidfile: /var/run/dnsbl.pid +# Source function library. +# +. /etc/rc.d/init.d/functions + +# Source networking configuration. +. /etc/sysconfig/network + +RETVAL=0 +# See how we were called. +case "$1" in + start) + # Start daemons. + echo -n "Starting dnsbl-milter: " + if [ ! -f /var/lock/subsys/dnsbl ]; then + cd SYSCONFDIR/dnsbl # conf file is here + SBINDIR/dnsbl -d 10 -r /var/run/dnsbl/dnsbl.resolver.sock -p local:/var/run/dnsbl/dnsbl.sock + RETVAL=$? + pid=`pidof -s SBINDIR/dnsbl` + if [ $pid ] + then + success "Starting dnsbl milter:" + touch /var/lock/subsys/dnsbl + echo + else + failure "Starting dnsbl milter:" + echo + fi + else + echo -n "already running! " + failure "dnsbl milter already running!" + echo + fi + + ;; + stop) + # Stop daemons. + echo -n "Shutting down dnsbl-milter: " + killproc dnsbl + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/dnsbl + ;; + restart|reload) + $0 stop + $0 start + RETVAL=$? + ;; + status) + status dnsbl + RETVAL=$? + ;; + *) + echo "Usage: dnsbl {start|stop|restart|status}" + exit 1 +esac +exit $RETVAL + +# $Id$ diff -r c4bce911c276 -r 97d7da45fe2a dnsbl.spec.in --- a/dnsbl.spec.in Sat Jul 14 12:25:17 2007 -0700 +++ b/dnsbl.spec.in Sun Aug 26 19:03:17 2007 -0700 @@ -21,6 +21,7 @@ BuildRequires: sendmail-devel >= 8.12.1 Requires: sendmail >= 8.12.1 Requires: sendmail-cf +Requires: spamassassin Requires: libc.so.6, libgcc_s.so.1, libm.so.6, libpthread.so.0, libstdc++.so.6 @@ -101,6 +102,9 @@ %changelog +* Sun Aug 26 2007 Carl Byington 6.04 +- require spamassassin + * Sat Jul 07 2007 Carl Byington 6.01 - GPL3, auto whitelisting diff -r c4bce911c276 -r 97d7da45fe2a sendmail.st Binary file sendmail.st has changed diff -r c4bce911c276 -r 97d7da45fe2a src/Makefile.am --- a/src/Makefile.am Sat Jul 14 12:25:17 2007 -0700 +++ b/src/Makefile.am Sun Aug 26 19:03:17 2007 -0700 @@ -1,5 +1,5 @@ sbin_PROGRAMS = dnsbl -dnsbl_SOURCES = dnsbl.cpp dnsbl.h context.cpp context.h tokenizer.cpp tokenizer.h scanner.cpp scanner.h includes.h +dnsbl_SOURCES = dnsbl.cpp dnsbl.h spamass.cpp spamass.h context.cpp context.h tokenizer.cpp tokenizer.h scanner.cpp scanner.h includes.h EXTRA_DIST = test.cpp # set the include path found by configure diff -r c4bce911c276 -r 97d7da45fe2a src/context.cpp --- a/src/context.cpp Sat Jul 14 12:25:17 2007 -0700 +++ b/src/context.cpp Sun Aug 26 19:03:17 2007 -0700 @@ -50,6 +50,7 @@ char *token_rbrace; char *token_semi; char *token_soft; +char *token_spamassassin; char *token_substitute; char *token_tld; char *token_cctld; @@ -678,6 +679,7 @@ host_random = (parent) ? parent->host_random : false; tag_limit = (parent) ? parent->tag_limit : 0; tag_limit_message = NULL; + spamassassin_limit = (parent) ? parent->spamassassin_limit : 0; default_rcpt_rate = INT_MAX; } @@ -861,7 +863,10 @@ } -bool CONTEXT::acceptable_content(recorder &memory, char *&msg) { +bool CONTEXT::acceptable_content(recorder &memory, int score, char *&msg) { + if (spamassassin_limit && (score > spamassassin_limit)) { + msg = "Mail rejected by spam assassin"; + } if (memory.excessive_bad_tags(tag_limit)) { msg = tag_limit_message; return false; @@ -953,6 +958,7 @@ else { printf("%s html_limit off; \n", indent); } + printf("%s spamassassin %d; \n", indent, spamassassin_limit); printf("%s }; \n", indent); } else { @@ -1215,6 +1221,10 @@ } if (!tsa(tok, token_semi)) return false; } + else if (have == token_spamassassin) { + me.set_spamassassin_limit(tok.nextint()); + if (!tsa(tok, token_semi)) return false; + } else if (have == token_rbrace) { break; // done } @@ -1533,6 +1543,7 @@ token_rbrace = register_string("}"); token_semi = register_string(";"); token_soft = register_string("soft"); + token_spamassassin = register_string("spamassassin"); token_substitute = register_string("substitute"); token_tld = register_string("tld"); token_unknown = register_string("unknown"); diff -r c4bce911c276 -r 97d7da45fe2a src/context.h --- a/src/context.h Sat Jul 14 12:25:17 2007 -0700 +++ b/src/context.h Sun Aug 26 19:03:17 2007 -0700 @@ -142,6 +142,7 @@ bool host_random; // pick a random selection of host names rather than error for excessive hosts int tag_limit; // limit on bad html tags char * tag_limit_message; // error message for excessive bad html tags + int spamassassin_limit; // max score from spamassassin dnsblp_map dnsbl_names; // name to dnsbl mapping for lists that are available in this context and children dnsblp_list dnsbl_list; // list of dnsbls to be used in this context int default_rcpt_rate; // if not specified per user @@ -190,6 +191,7 @@ void add_cctld(char *cctld) {content_cctlds.insert(cctld);}; void set_host_limit(int limit) {host_limit = limit;}; + void set_spamassassin_limit(int limit) {spamassassin_limit = limit; }; void set_host_message(char *message) {host_limit_message = message;}; void set_host_random(bool random) {host_random = random;}; void set_tag_limit(int limit) {tag_limit = limit;}; @@ -202,6 +204,7 @@ bool get_content_filtering() {return content_filtering;}; int get_host_limit() {return host_limit;}; + int get_spamassassin_limit() {return spamassassin_limit;}; bool get_host_random() {return host_random;}; char* get_content_suffix(); char* get_content_message(); @@ -213,7 +216,7 @@ string_set& get_html_tags(); dnsblp_list& get_dnsbl_list(); - bool acceptable_content(recorder &memory, char *&msg); + bool acceptable_content(recorder &memory, int score, char *&msg); bool ignore_host(char *host); void dump(bool isdefault, int level = 0); @@ -274,6 +277,7 @@ extern char *token_rbrace; extern char *token_semi; extern char *token_soft; +extern char *token_spamassassin; extern char *token_substitute; extern char *token_tld; extern char *token_unknown; diff -r c4bce911c276 -r 97d7da45fe2a src/dnsbl.cpp --- a/src/dnsbl.cpp Sat Jul 14 12:25:17 2007 -0700 +++ b/src/dnsbl.cpp Sun Aug 26 19:03:17 2007 -0700 @@ -13,7 +13,7 @@ -c Check the config, and print a copy to stdout. Don't start the milter or do anything with the socket. -s Stress test by loading and deleting the current config in a loop. --d increase debug level +-d level set the debug level -e f|t Print the results of looking up from address f and to address t in the current config @@ -65,8 +65,10 @@ extern "C" { #include sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr); + sfsistat mlfi_helo(SMFICTX * ctx, char *helohost); sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv); sfsistat mlfi_envrcpt(SMFICTX *ctx, char **argv); + sfsistat mlfi_header(SMFICTX* ctx, char* headerf, char* headerv); sfsistat mlfi_body(SMFICTX *ctx, u_char *data, size_t len); sfsistat mlfi_eom(SMFICTX *ctx); sfsistat mlfi_abort(SMFICTX *ctx); @@ -243,6 +245,7 @@ pthread_mutex_unlock(&config_mutex); get_fd(); ip = 0; + helo = NULL; mailaddr = NULL; queueid = NULL; authenticated = NULL; @@ -264,6 +267,7 @@ bool last = (!pc->reference_count) && (pc != config); pthread_mutex_unlock(&config_mutex); if (last) delete pc; // free this config, since we were the last reference to it + if (helo) free(helo); reset(true); } @@ -274,6 +278,7 @@ discard(env_to); if (memory) delete memory; if (scanner) delete scanner; + if (assassin) delete assassin; if (!final) { mailaddr = NULL; queueid = NULL; @@ -282,6 +287,7 @@ only_whites = true; memory = NULL; scanner = NULL; + assassin = NULL; content_suffix = NULL; content_message = NULL; uribl_suffix = NULL; @@ -428,6 +434,15 @@ } } +void my_syslog(mlfiPriv *priv, string text) { + if (debug_syslog > 3) { + char buf[maxlen]; + strncpy(buf, text.c_str(), sizeof(buf)); + buf[maxlen-1] = '\0'; // ensure null termination + my_syslog(priv, buf); + } +} + void my_syslog(char *text) { my_syslog(NULL, text); } @@ -935,12 +950,24 @@ return SMFIS_CONTINUE; } +sfsistat mlfi_helo(SMFICTX * ctx, char *helohost) +{ + mlfiPriv &priv = *MLFIPRIV; + priv.helo = strdup(helohost); + return SMFIS_CONTINUE; +} + sfsistat mlfi_envfrom(SMFICTX *ctx, char **from) { mlfiPriv &priv = *MLFIPRIV; priv.mailaddr = to_lower_string(from[0]); + priv.queueid = strdup(smfi_getsymval(ctx, "i")); priv.authenticated = smfi_getsymval(ctx, "{auth_authen}"); if (priv.authenticated) priv.authenticated = strdup(priv.authenticated); + priv.assassin = new SpamAssassin; + priv.assassin->mlfi_connect(&priv, priv.ip); + priv.assassin->mlfi_helo(priv.helo); + priv.assassin->mlfi_envfrom(priv.mailaddr, priv.queueid); return SMFIS_CONTINUE; } @@ -949,9 +976,9 @@ DNSBLP rejectlist = NULL; // list that caused the reject mlfiPriv &priv = *MLFIPRIV; CONFIG &dc = *priv.pc; - if (!priv.queueid) priv.queueid = strdup(smfi_getsymval(ctx, "i")); char *rcptaddr = rcpt[0]; char *loto = to_lower_string(rcptaddr); + priv.assassin->mlfi_envrcpt(ctx, loto); // priv.mailaddr sending original message to loto CONTEXT &con = *(dc.find_context(loto)->find_context(priv.mailaddr)); VERIFYP ver = con.find_verify(loto); @@ -1050,11 +1077,30 @@ return SMFIS_CONTINUE; } +sfsistat mlfi_header(SMFICTX* ctx, char* headerf, char* headerv) +{ + mlfiPriv &priv = *MLFIPRIV; + if (priv.authenticated) return SMFIS_CONTINUE; + if (priv.only_whites) return SMFIS_CONTINUE; + priv.assassin->mlfi_header(headerf, headerv); + return SMFIS_CONTINUE; +} + +sfsistat mlfi_eoh(SMFICTX* ctx) +{ + mlfiPriv &priv = *MLFIPRIV; + if (priv.authenticated) return SMFIS_CONTINUE; + if (priv.only_whites) return SMFIS_CONTINUE; + priv.assassin->mlfi_eoh(); + return SMFIS_CONTINUE; +} + sfsistat mlfi_body(SMFICTX *ctx, u_char *data, size_t len) { mlfiPriv &priv = *MLFIPRIV; if (priv.authenticated) return SMFIS_CONTINUE; if (priv.only_whites) return SMFIS_CONTINUE; + priv.assassin->mlfi_body(data, len); priv.scanner->scan(data, len); return SMFIS_CONTINUE; } @@ -1070,6 +1116,7 @@ // process end of message if (priv.authenticated || priv.only_whites) rc = SMFIS_CONTINUE; else { + int score = priv.assassin->mlfi_eom(); // assert env_to not empty char buf[maxlen]; char *msg = NULL; @@ -1079,8 +1126,8 @@ for (context_map::iterator i=priv.env_to.begin(); i!=priv.env_to.end(); i++) { char *rcpt = (*i).first; CONTEXT &con = *((*i).second); - if (!con.acceptable_content(*priv.memory, msg)) { - // bad html tags or excessive hosts + if (!con.acceptable_content(*priv.memory, score, msg)) { + // bad html tags or excessive hosts or high spam assassin score smfi_delrcpt(ctx, rcpt); } else { @@ -1152,11 +1199,11 @@ SMFI_VERSION, // version code -- do not change SMFIF_DELRCPT, // flags mlfi_connect, // connection info filter - NULL, // SMTP HELO command filter + mlfi_helo, // SMTP HELO command filter mlfi_envfrom, // envelope sender filter mlfi_envrcpt, // envelope recipient filter - NULL, // header filter - NULL, // end of header + mlfi_header, // header filter + mlfi_eoh, // end of header mlfi_body, // body block filter mlfi_eom, // end of message mlfi_abort, // message aborted @@ -1192,10 +1239,9 @@ // and reload when needed. // we also clear the SMTP AUTH recipient counts hourly // -void* config_loader(void *arg); +extern "C" {void* config_loader(void *arg);} void* config_loader(void *arg) { int loop = 0; - typedef set configp_set; while (loader_run) { sleep(180); // look for modifications every 3 minutes if (!loader_run) break; @@ -1268,13 +1314,6 @@ void setup_socket(char *sock); void setup_socket(char *sock) { unlink(sock); - // sockaddr_un addr; - // memset(&addr, '\0', sizeof addr); - // addr.sun_family = AF_UNIX; - // strncpy(addr.sun_path, sock, sizeof(addr.sun_path)-1); - // int s = socket(AF_UNIX, SOCK_STREAM, 0); - // bind(s, (sockaddr*)&addr, sizeof(addr)); - // close(s); } diff -r c4bce911c276 -r 97d7da45fe2a src/dnsbl.h --- a/src/dnsbl.h Sat Jul 14 12:25:17 2007 -0700 +++ b/src/dnsbl.h Sun Aug 26 19:03:17 2007 -0700 @@ -10,6 +10,7 @@ #define dnsbl_include #include "context.h" +#include "spamass.h" extern int debug_syslog; @@ -26,6 +27,7 @@ int fd; // to talk to dns resolvers process bool err; // did we get any errors on the resolver socket? int ip; // ip4 address of the smtp client + char *helo; // helo from client map checked; // map of dnsblp to result of (ip listed on that dnsbl) // message specific data char *mailaddr; // envelope from value @@ -41,6 +43,7 @@ char *uribl_suffix; // for uribl body filtering based on hostnames in the body char *uribl_message; // "" string_set *content_host_ignore; // "" + SpamAssassin *assassin; mlfiPriv(); @@ -54,6 +57,7 @@ }; void my_syslog(mlfiPriv *priv, char *text); +void my_syslog(mlfiPriv *priv, string text); void my_syslog(char *text); #endif diff -r c4bce911c276 -r 97d7da45fe2a src/spamass.cpp.in --- /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 + +*/ + +#include "config.h" +#include "dnsbl.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +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; +} + diff -r c4bce911c276 -r 97d7da45fe2a src/spamass.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/spamass.h Sun Aug 26 19:03:17 2007 -0700 @@ -0,0 +1,75 @@ +/* + +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 + +*/ + +#ifndef _SPAMASS_MILTER_H +#define _SPAMASS_MILTER_H + +extern "C" { + #include +} + +#ifdef HAVE_CDEFS_H +#include +#endif + +#include + +using namespace std; + +class mlfiPriv; + +class SpamAssassin { +public: + SpamAssassin(); + ~SpamAssassin(); + + void mlfi_connect(mlfiPriv *priv_, int ip); + void mlfi_helo(char *helohost); + void mlfi_envfrom(char *from, char *qid); + void mlfi_envrcpt(SMFICTX *ctx, char *envrcpt); + void mlfi_header(char* headerf, char* headerv); + void mlfi_eoh(); + void mlfi_body(u_char *bodyp, size_t bodylen); + int mlfi_eom(); + +private: + void Connect(); + void output(const char*buffer, size_t size); + void output(const char*buffer); + void output(string buffer); + void close_output(); + void input(); + int read_pipe(); + void empty_and_close_pipe(); + void closeall(int fd); + void warnmacro(char *macro, char *scope); + +public: + bool error; // spamc died or cannot work + bool running; // running implies (connected and pid) + bool first_recipient; // have we seen any recipients? + + // connection back to main dnsbl priv structure for logging + mlfiPriv *priv; + + // strings owned by main dnsbl + char *queueid; // the sendmail queue id for this message + char *envfrom; // envelope from value for this message + char *helo; // client helo value + int ip4; // ip4 address of smtp client + + // Process handling variables + pid_t pid; + int pipe_io[2][2]; + string spamc_output; + string spamc_input; +}; + +#endif diff -r c4bce911c276 -r 97d7da45fe2a test.bash --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test.bash Sun Aug 26 19:03:17 2007 -0700 @@ -0,0 +1,31 @@ +#!/bin/bash + +#rebuild +make + +# build the test.cf file +make -f Makefile.test test.cf + +# start the milter +pid=/var/run/dnsbl.pid +echo start the milter +mkdir -p /var/run/dnsbl +chmod 700 /var/run/dnsbl +#chown dnsbl:dnsbl /var/run/dnsbl +[ -f "$pid" ] && mv -f $pid $pid.save +#src/dnsbl -d 10 -r /var/run/dnsbl/dnsbl.resolver.sock2 -p local:/var/run/dnsbl/dnsbl.sock2 +#valgrind --leak-check=full --show-reachable=yes src/dnsbl -d 10 -r /var/run/dnsbl/dnsbl.resolver.sock2 -p local:/var/run/dnsbl/dnsbl.sock2 +valgrind src/dnsbl -d 10 -r /var/run/dnsbl/dnsbl.resolver.sock2 -p local:/var/run/dnsbl/dnsbl.sock2 +sleep 5 +P2=`cat $pid` +[ -f "$pid.save" ] && mv -f $pid.save $pid +echo started dnsbl milter as process $P2 + +/usr/lib/sendmail -bd -Ctest.cf -Ldnsbltest +sleep 5 +P3=`head -1 /var/run/sm-test.pid` +echo started sendmail as process $P3 + +echo "eventually, run the following two kill commands" +echo kill -SIGINT $P2 +echo kill -KILL $P3 diff -r c4bce911c276 -r 97d7da45fe2a xml/dnsbl.in --- a/xml/dnsbl.in Sat Jul 14 12:25:17 2007 -0700 +++ b/xml/dnsbl.in Sun Aug 26 19:03:17 2007 -0700 @@ -375,6 +375,11 @@ ignore list, the mail is rejected. + If the spamassassin limit for this filtering context is non-zero, the + message is passed thru spamassassin (via spamc) and if the resulting + score is larger than this limit, the mail is rejected. + + We also scan for excessive bad html tags, and if a <configurable> limit is exceeded, the mail is rejected. @@ -499,7 +504,7 @@ Copyright - Copyright (C) 2005 by 510 Software Group <carl@five-ten-sg.com> + Copyright (C) 2007 by 510 Software Group <carl@five-ten-sg.com> This program is free software; you can redistribute it and/or modify it @@ -567,7 +572,8 @@ CONTENT = "content" ("on" | "off") "{" {CONTENT-ST}+ "}" CONTENT-ST = (FILTER | URIBL | IGNORE | TLD | CCTLD | HTML-TAGS | - HTML-LIMIT | HOST-LIMIT) ";" + HTML-LIMIT | HOST-LIMIT | SPAMASS) ";" +SPAMASS = "spamassassin" INTEGER FILTER = "filter" DNSPREFIX ERROR-MSG2 URIBL = "uribl" DNSPREFIX ERROR-MSG3 IGNORE = "ignore" "{" {HOSTNAME [";"]}+ "}"