changeset 163:97d7da45fe2a

spamassassin changes
author carl
date Sun, 26 Aug 2007 19:03:17 -0700
parents c4bce911c276
children 5809bcdc325b
files AUTHORS Makefile.am configure.in dnsbl.conf dnsbl.rc dnsbl.rc.in dnsbl.spec.in sendmail.st src/Makefile.am src/context.cpp src/context.h src/dnsbl.cpp src/dnsbl.h src/spamass.cpp.in src/spamass.h test.bash xml/dnsbl.in
diffstat 17 files changed, 778 insertions(+), 100 deletions(-) [+]
line wrap: on
line diff
--- 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 <stephen.johnson@arkansas.gov>
     Jeff Evans <jeffe@tricab.com>
     G.W. Haywood <ged@jubileegroup.co.uk>
+    Georg C. F. Greve <greve@gnu.org> - spamassassin milter
--- 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
--- 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
--- 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";
 };
 
--- 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.
 #
--- /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$
--- 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
 
Binary file sendmail.st has changed
--- 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
--- 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");
--- 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;
--- 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 <libmilter/mfapi.h>
 	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<CONFIG *> 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);
 }
 
 
--- 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<DNSBLP, bool> 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
--- /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;
+}
+
--- /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 <greve@gnu.org>
+
+*/
+
+#ifndef _SPAMASS_MILTER_H
+#define _SPAMASS_MILTER_H
+
+extern "C" {
+	#include <libmilter/mfapi.h>
+}
+
+#ifdef HAVE_CDEFS_H
+#include <sys/cdefs.h>
+#endif
+
+#include <list>
+
+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
--- /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
--- 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.
             </para>
             <para>
+                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.
+            </para>
+            <para>
                 We also scan for excessive bad html tags, and if a &lt;configurable&gt;
                 limit is exceeded, the mail is rejected.
             </para>
@@ -499,7 +504,7 @@
         <refsect1 id='copyright.1'>
             <title>Copyright</title>
             <para>
-                Copyright (C) 2005 by 510 Software Group &lt;carl@five-ten-sg.com&gt;
+                Copyright (C) 2007 by 510 Software Group &lt;carl@five-ten-sg.com&gt;
             </para>
             <para>
                 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 [";"]}+ "}"