changeset 178:d6531c702be3

embedded dcc filtering
author carl
date Thu, 04 Oct 2007 22:45:21 -0700
parents a4d313c2460b
children 8b86a894514d
files ChangeLog Makefile.am NEWS dnsbl.conf dnsbl.rc.in sendmail.st src/Makefile.am src/context.cpp src/context.h src/dnsbl.cpp src/dnsbl.h src/tokenizer.cpp test.bash xml/dnsbl.in
diffstat 14 files changed, 311 insertions(+), 160 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sun Sep 30 10:27:14 2007 -0700
+++ b/ChangeLog	Thu Oct 04 22:45:21 2007 -0700
@@ -1,7 +1,11 @@
     $Id$
 
-6.11 2007-09-30
-    Add DCC filtering via dccifd.
+6.11 2007-10-04
+    Add DCC filtering via dccifd. Drop to 60 seconds the time we will
+    keep idle smtp verify sockets around. This needs to be about half
+    the value of confTO_COMMAND configured on the verify targets.
+    Fix potential race condition or buffer overflow caused by static
+    buffer referenced by multiple threads.
 
 6.10 2007-09-23
     Don't whitelist addresses with embedded blanks, or the empty
--- a/Makefile.am	Sun Sep 30 10:27:14 2007 -0700
+++ b/Makefile.am	Thu Oct 04 22:45:21 2007 -0700
@@ -13,9 +13,9 @@
 chkconfig: dnsbl
 	   /usr/bin/getent passwd dnsbl >/dev/null || /usr/sbin/useradd -r -d $(sysconfdir)/dnsbl -M -c "dnsbl pseudo-user" -s /sbin/nologin dnsbl >/dev/null
 	   mv -f $(sysconfdir)/dnsbl/dnsbl /etc/rc.d/init.d
-	   mkdir $(sysconfdir)/dnsbl/autowhite
+	   mkdir -p $(sysconfdir)/dnsbl/autowhite
 	   chown dnsbl:root $(sysconfdir)/dnsbl/autowhite
-	   mkdir $(sysconfdir)/dnsbl/.spamassassin
+	   mkdir -p $(sysconfdir)/dnsbl/.spamassassin
 	   chown dnsbl:root $(sysconfdir)/dnsbl/.spamassassin
 	   /sbin/chkconfig --del dnsbl
 	   /sbin/chkconfig --add dnsbl
--- a/NEWS	Sun Sep 30 10:27:14 2007 -0700
+++ b/NEWS	Thu Oct 04 22:45:21 2007 -0700
@@ -1,6 +1,6 @@
     $Id$
 
-6.11 2007-09-30 Add DCC filtering via dccifd.
+6.11 2007-10-04 Add DCC filtering via dccifd. Fix static buffer referenced by multiple threads.
 6.10 2007-09-23 Don't whitelist addresses with embedded blanks, or the empty path.
 6.09 2007-09-06 Fix memory leak. Update timestamps when receiving from auto-whitelisted sender.
 6.08 2007-08-30 Don't do generic reverse dns filtering on authenticated connections.
--- a/dnsbl.conf	Sun Sep 30 10:27:14 2007 -0700
+++ b/dnsbl.conf	Thu Oct 04 22:45:21 2007 -0700
@@ -17,6 +17,9 @@
         host_limit on 20 "Mail containing excessive host names rejected";
         host_limit soft 20;
         spamassassin 4;
+        require_match       yes;
+        dcc_greylist        yes;
+        dcc_bulk_threshold  50;
     };
 
     // backscatter prevention - don't send bounces for mail that we accepted but could not forward
@@ -50,6 +53,9 @@
         html_limit off;
         host_limit soft 20;
         spamassassin 5;
+        require_match       yes;
+        dcc_greylist        yes;
+        dcc_bulk_threshold  20;
     };
 
     generic "(^|[.-])(ppp|h|host)?([0-9]{1,3}[.-](Red-|dynamic[.-])?){4}"
@@ -83,8 +89,10 @@
 
     context minimal {
         dnsbl_list sbl;
-        content on {};
+        content on {
         spamassassin 10;
+            dcc_bulk_threshold  many;
+        };
         generic "^$ " " ";      # regex cannot match, to disable generic rdns rejects
         env_to {
         };
--- a/dnsbl.rc.in	Sun Sep 30 10:27:14 2007 -0700
+++ b/dnsbl.rc.in	Thu Oct 04 22:45:21 2007 -0700
@@ -22,7 +22,9 @@
         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
+            DCCIFD=
+            [ -S /var/dcc/dccifd ] && DCCIFD='-b /var/dcc/dccifd'
+            SBINDIR/dnsbl -d 10 $DCCIFD -r /var/run/dnsbl/dnsbl.resolver.sock -p local:/var/run/dnsbl/dnsbl.sock
             RETVAL=$?
             pid=`pidof -s SBINDIR/dnsbl`
             if [ $pid ]
Binary file sendmail.st has changed
--- a/src/Makefile.am	Sun Sep 30 10:27:14 2007 -0700
+++ b/src/Makefile.am	Thu Oct 04 22:45:21 2007 -0700
@@ -1,5 +1,5 @@
 sbin_PROGRAMS = dnsbl
-dnsbl_SOURCES = dnsbl.cpp dnsbl.h spamass.cpp spamass.h context.cpp context.h tokenizer.cpp tokenizer.h scanner.cpp scanner.h includes.h
+dnsbl_SOURCES = dnsbl.cpp dnsbl.h dccifd.cpp dccifd.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	Sun Sep 30 10:27:14 2007 -0700
+++ b/src/context.cpp	Thu Oct 04 22:45:21 2007 -0700
@@ -23,9 +23,12 @@
 
 char *token_autowhite;
 char *token_black;
+char *token_cctld;
 char *token_content;
 char *token_context;
+char *token_dccbulk;
 char *token_dccfrom;
+char *token_dccgrey;
 char *token_dccto;
 char *token_default;
 char *token_dnsbl;
@@ -43,22 +46,24 @@
 char *token_lbrace;
 char *token_mailhost;
 char *token_many;
+char *token_no;
 char *token_off;
+char *token_ok;
 char *token_ok2;
-char *token_ok;
 char *token_on;
 char *token_rate;
 char *token_rbrace;
+char *token_require;
 char *token_semi;
 char *token_soft;
 char *token_spamassassin;
 char *token_substitute;
 char *token_tld;
-char *token_cctld;
 char *token_unknown;
 char *token_uribl;
 char *token_verify;
 char *token_white;
+char *token_yes;
 
 char *token_myhostname;
 #ifndef HOST_NAME_MAX
@@ -74,7 +79,7 @@
 
 string_set	all_strings;	// owns all the strings, only modified by the config loader thread
 const int maxlen = 1000;	// used for snprintf buffers
-const int maxsmtp_age = 120;// smtp verify sockets older than this are ancient
+const int maxsmtp_age =  60;// smtp verify sockets older than this are ancient
 const int maxauto_age = 600;// auto whitelister delay before flushing to file
 extern int	  NULL_SOCKET;
 const time_t  ERROR_SMTP_SOCKET_TIME = 600; // number of seconds between attempts to open a socket to an smtp server
@@ -697,6 +702,9 @@
 	tag_limit			= (parent) ? parent->tag_limit	 : 0;
 	tag_limit_message	= NULL;
 	spamassassin_limit	= (parent) ? parent->spamassassin_limit : 0;
+	require_match		= (parent) ? parent->require_match		: false;
+	dcc_greylist		= (parent) ? parent->dcc_greylist		: false;
+	dcc_bulk_threshold	= (parent) ? parent->dcc_bulk_threshold : 0;
 	default_rcpt_rate	= INT_MAX;
 }
 
@@ -917,13 +925,19 @@
 }
 
 
-bool CONTEXT::acceptable_content(recorder &memory, int score, string& msg) {
+bool CONTEXT::acceptable_content(recorder &memory, int score, int bulk, string& msg) {
 	if (spamassassin_limit && (score > spamassassin_limit)) {
 		char buf[maxlen];
 		snprintf(buf, sizeof(buf), "Mail rejected - spam assassin score %d", score);
 		msg = string(buf);
 		return false;
 	}
+	if (dcc_bulk_threshold && (bulk > dcc_bulk_threshold)) {
+		char buf[maxlen];
+		snprintf(buf, sizeof(buf), "Mail rejected - dcc score %d", bulk);
+		msg = string(buf);
+		return false;
+	}
 	if (memory.excessive_bad_tags(tag_limit)) {
 		msg = string(tag_limit_message);
 		return false;
@@ -1016,6 +1030,11 @@
 			printf("%s         html_limit off; \n", indent);
 		}
 		printf("%s         spamassassin %d; \n", indent, spamassassin_limit);
+		printf("%s         require_match %s; \n", indent, (require_match) ? "yes" : "no");
+		printf("%s         dcc_greylist  %s; \n", indent, (dcc_greylist)  ? "yes" : "no");
+		if (dcc_bulk_threshold == 0)			printf("%s         dcc_bulk_threshold off; \n", indent);
+		else if (dcc_bulk_threshold == 1000)	printf("%s         dcc_bulk_threshold many; \n", indent);
+		else									printf("%s         dcc_bulk_threshold %d; \n", indent, dcc_bulk_threshold);
 		printf("%s     }; \n", indent);
 		spamass |= (spamassassin_limit != 0);
 		}
@@ -1214,16 +1233,6 @@
 			}
 			if (!tsa(tok, token_semi)) return false;
 		}
-		else if (have == token_cctld) {
-			if (!tsa(tok, token_lbrace)) return false;
-			while (true) {
-				char *have = tok.next();
-				if (!have) break;
-				if (have == token_rbrace) break;  // done
-				me.add_cctld(have);
-			}
-			if (!tsa(tok, token_semi)) return false;
-		}
 		else if (have == token_tld) {
 			if (!tsa(tok, token_lbrace)) return false;
 			while (true) {
@@ -1234,19 +1243,13 @@
 			}
 			if (!tsa(tok, token_semi)) return false;
 		}
-		else if (have == token_html_limit) {
-			have = tok.next();
-			if (have == token_on) {
-				me.set_tag_limit(tok.nextint());
-				me.set_tag_message(tok.next());
-			}
-			else if (have == token_off) {
-				me.set_tag_limit(0);
-				me.set_tag_message(NULL);
-			}
-			else {
-				tok.token_error("on/off", have);
-				return false;
+		else if (have == token_cctld) {
+			if (!tsa(tok, token_lbrace)) return false;
+			while (true) {
+				char *have = tok.next();
+				if (!have) break;
+				if (have == token_rbrace) break;  // done
+				me.add_cctld(have);
 			}
 			if (!tsa(tok, token_semi)) return false;
 		}
@@ -1269,6 +1272,22 @@
 			}
 			if (!tsa(tok, token_semi)) return false;
 		}
+		else if (have == token_html_limit) {
+			have = tok.next();
+			if (have == token_on) {
+				me.set_tag_limit(tok.nextint());
+				me.set_tag_message(tok.next());
+			}
+			else if (have == token_off) {
+				me.set_tag_limit(0);
+				me.set_tag_message(NULL);
+			}
+			else {
+				tok.token_error("on/off", have);
+				return false;
+			}
+			if (!tsa(tok, token_semi)) return false;
+		}
 		else if (have == token_host_limit) {
 			have = tok.next();
 			if (have == token_on) {
@@ -1296,6 +1315,41 @@
 			me.set_spamassassin_limit(tok.nextint());
 			if (!tsa(tok, token_semi)) return false;
 		}
+		else if (have == token_require) {
+			have = tok.next();
+				 if (have == token_yes) me.set_require(true);
+			else if (have == token_no)	me.set_require(false);
+			else {
+				tok.token_error("yes/no", have);
+				return false;
+			}
+			if (!tsa(tok, token_semi)) return false;
+		}
+		else if (have == token_dccgrey) {
+			have = tok.next();
+				 if (have == token_yes) me.set_grey(true);
+			else if (have == token_no)	me.set_grey(false);
+			else {
+				tok.token_error("yes/no", have);
+				return false;
+			}
+			if (!tsa(tok, token_semi)) return false;
+		}
+		else if (have == token_dccbulk) {
+			have = tok.next();
+				 if (have == token_off) me.set_bulk(0);
+			else if (have == token_many) me.set_bulk(1000);
+			else {
+				char *e;
+				long i = strtol(have, &e, 10);
+				if (*e != '\0') {
+					tok.token_error("integer", have);
+					return false;
+				}
+				me.set_bulk((int)i);
+			}
+			if (!tsa(tok, token_semi)) return false;
+		}
 		else if (have == token_rbrace) {
 			break;	// done
 		}
@@ -1607,7 +1661,9 @@
 	token_cctld 	   = register_string("cctld");
 	token_content	   = register_string("content");
 	token_context	   = register_string("context");
+	token_dccbulk		= register_string("dcc_bulk_threshold");
 	token_dccfrom	   = register_string("dcc_from");
+	token_dccgrey		= register_string("dcc_greylist");
 	token_dccto 	   = register_string("dcc_to");
 	token_default	   = register_string("default");
 	token_dnsbl 	   = register_string("dnsbl");
@@ -1625,12 +1681,14 @@
 	token_lbrace	   = register_string("{");
 	token_mailhost	   = register_string("mail_host");
 	token_many		   = register_string("many");
+	token_no			= register_string("no");
 	token_off		   = register_string("off");
 	token_ok		   = register_string("ok");
 	token_ok2		   = register_string("ok2");
 	token_on		   = register_string("on");
 	token_rate		   = register_string("rate_limit");
 	token_rbrace	   = register_string("}");
+	token_require		= register_string("require_match");
 	token_semi		   = register_string(";");
 	token_soft		   = register_string("soft");
 	token_spamassassin = register_string("spamassassin");
@@ -1640,6 +1698,7 @@
 	token_uribl 	   = register_string("uribl");
 	token_verify	   = register_string("verify");
 	token_white 	   = register_string("white");
+	token_yes			= register_string("yes");
 
 	if (gethostname(myhostname, HOST_NAME_MAX+1) != 0) {
 		strncpy(myhostname, "localhost", HOST_NAME_MAX+1);
--- a/src/context.h	Sun Sep 30 10:27:14 2007 -0700
+++ b/src/context.h	Thu Oct 04 22:45:21 2007 -0700
@@ -147,6 +147,9 @@
 	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
+	bool			require_match;		// require matching context filtering context
+	bool			dcc_greylist;		// should we do dcc greylisting?
+	int 			dcc_bulk_threshold; // off = 0, many = 1000
 	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
@@ -210,7 +213,14 @@
 	bool		set_generic(char *regx, char *msg);
 	char*		generic_match(char *client);
 
+	void		set_require(bool r) 						{require_match		= r; };
+	void		set_grey(bool g)							{dcc_greylist		= g; };
+	void		set_bulk(int b) 							{dcc_bulk_threshold = b; };
+
 	bool			get_content_filtering() 				{return content_filtering; };
+	bool			get_require()							{return require_match;	   };
+	bool			get_grey()								{return dcc_greylist;	   };
+	int 			get_bulk()								{return dcc_bulk_threshold;};
 	int 			get_host_limit()						{return host_limit; 	   };
 	bool			get_host_random()						{return host_random;	   };
 	int 			get_spamassassin_limit()				{return (content_filtering) ? spamassassin_limit : 0;};
@@ -224,7 +234,7 @@
 	string_set& 	get_html_tags();
 	dnsblp_list&	get_dnsbl_list();
 
-	bool		acceptable_content(recorder &memory, int score, string& msg);
+	bool		acceptable_content(recorder &memory, int score, int bulk, string& msg);
 	bool		ignore_host(char *host);
 
 	void		dump(bool isdefault, bool &spamass, int level = 0);
@@ -260,7 +270,9 @@
 extern char *token_cctld;
 extern char *token_content;
 extern char *token_context;
+extern char *token_dccbulk;
 extern char *token_dccfrom;
+extern char *token_dccgrey;
 extern char *token_dccto;
 extern char *token_default;
 extern char *token_dnsbl;
@@ -278,12 +290,14 @@
 extern char *token_lbrace;
 extern char *token_mailhost;
 extern char *token_many;
+extern char *token_no;
 extern char *token_off;
+extern char *token_ok;
 extern char *token_ok2;
-extern char *token_ok;
 extern char *token_on;
 extern char *token_rate;
 extern char *token_rbrace;
+extern char *token_require;
 extern char *token_semi;
 extern char *token_soft;
 extern char *token_spamassassin;
@@ -291,7 +305,9 @@
 extern char *token_tld;
 extern char *token_unknown;
 extern char *token_uribl;
+extern char *token_verify;
 extern char *token_white;
+extern char *token_yes;
 
 extern pthread_mutex_t verifier_mutex;	   // protect the verifier map
 extern pthread_mutex_t whitelister_mutex;  // protect the
--- a/src/dnsbl.cpp	Sun Sep 30 10:27:14 2007 -0700
+++ b/src/dnsbl.cpp	Thu Oct 04 22:45:21 2007 -0700
@@ -255,14 +255,18 @@
 	have_whites 		= false;
 	only_whites 		= true;
 	want_spamassassin	= false;
+	want_dccgrey		= false;
+	want_dccbulk		= false;
+	content_context 	= NULL;
 	memory				= NULL;
 	scanner 			= NULL;
-	assassin			= NULL;
 	content_suffix		= NULL;
 	content_message 	= NULL;
 	uribl_suffix		= NULL;
 	uribl_message		= NULL;
 	content_host_ignore = NULL;
+	assassin			= NULL;
+	dccifd				= NULL;
 }
 
 mlfiPriv::~mlfiPriv() {
@@ -285,6 +289,7 @@
 	if (memory)   delete memory;
 	if (scanner)  delete scanner;
 	if (assassin) delete assassin;
+	if (dccifd)   delete dccifd;
 	if (!final) {
 		mailaddr			= NULL;
 		queueid 			= NULL;
@@ -293,14 +298,18 @@
 		have_whites 		= false;
 		only_whites 		= true;
 		want_spamassassin	= false;
+		want_dccgrey		= false;
+		want_dccbulk		= false;
+		content_context 	= NULL;
 		memory				= NULL;
 		scanner 			= NULL;
-		assassin			= NULL;
 		content_suffix		= NULL;
 		content_message 	= NULL;
 		uribl_suffix		= NULL;
 		uribl_message		= NULL;
 		content_host_ignore = NULL;
+		assassin			= NULL;
+		dccifd				= NULL;
 	}
 }
 
@@ -404,8 +413,8 @@
 void mlfiPriv::need_content_filter(char *rcpt, CONTEXT &con) {
 	register_string(env_to, rcpt, &con);
 	if (!memory) {
-		// first recipient that needs content filtering sets all
-		// the content filtering parameters
+		// first recipient that needs content filtering sets
+		// some of the content filtering parameters
 		memory		  = new recorder(this, con.get_html_tags(), con.get_content_tlds(), con.get_content_cctlds());
 		scanner 	  = new url_scanner(memory);
 		content_suffix		= con.get_content_suffix();
@@ -718,11 +727,12 @@
 //	lookup the domain name part of a hostname on the uribl
 //
 //	if we find part of the hostname on the uribl, return
-//	true and point found to the part of the hostname that we found.
+//	true and point found to the part of the hostname that we found
+//	as a string registered in hosts.
 //	otherwise, return false and preserve the value of found.
 //
-bool uriblookup(mlfiPriv &priv ,char *hostname, char *top, char *&found) ;
-bool uriblookup(mlfiPriv &priv, char *hostname, char *top, char *&found) {
+bool uriblookup(mlfiPriv &priv, string_set &hosts, char *hostname, char *top, char *&found) ;
+bool uriblookup(mlfiPriv &priv, string_set &hosts, char *hostname, char *top, char *&found) {
 	// top is pointer to '.' char at end of base domain, or null for ip address form
 	// so for hostname of www.fred.mydomain.co.uk
 	// top points to-----------------------^
@@ -742,7 +752,7 @@
 			snprintf(tmp, sizeof(tmp), "found %s on %s", hostname, priv.uribl_suffix);
 			my_syslog(tmp);
 		}
-		found = hostname;
+		found = register_string(hosts, hostname);
 		return true;
 	}
 	return false;
@@ -757,11 +767,12 @@
 //	Else, look up three level domain.
 //
 //	if we find part of the hostname on the uribl, return
-//	true and point found to the part of the hostname that we found.
+//	true and point found to the part of the hostname that we found
+//	as a string registered in hosts.
 //	otherwise, return false and preserve the value of found.
 //
-bool check_uribl(mlfiPriv &priv, char *hostname, char *&found) ;
-bool check_uribl(mlfiPriv &priv, char *hostname, char *&found) {
+bool check_uribl(mlfiPriv &priv, string_set &hosts, char *hostname, char *&found) ;
+bool check_uribl(mlfiPriv &priv, string_set &hosts, char *hostname, char *&found) {
 	in_addr ip;
 	if (inet_aton(hostname, &ip)) {
 		const u_char *src = (const u_char *)&ip.s_addr;
@@ -769,9 +780,9 @@
 		if (src[0] == 10)  return false;	// don't do dns lookups on rfc1918 space
 		if ((src[0] == 192) && (src[1] == 168)) return false;
 		if ((src[0] == 172) && (16 <= src[1]) && (src[1] <= 31)) return false;
-		static char adr[sizeof "255.255.255.255"];
+		char adr[sizeof "255.255.255.255   "];
 		snprintf(adr, sizeof(adr), "%u.%u.%u.%u", src[3], src[2], src[1], src[0]);
-		return (uriblookup(priv, adr, NULL, found));
+		return (uriblookup(priv, hosts, adr, NULL, found));
 	}
 
 	char *top, *top2, *top3;
@@ -785,18 +796,18 @@
 			string_set::iterator i = priv.memory->get_cctlds()->find(top2+1);
 			string_set::iterator x = priv.memory->get_cctlds()->end();
 			// if we have a 2-level-cctld, just look at top three levels of the name
-			if (i != x) return uriblookup(priv, hostname, top2, found);
+			if (i != x) return uriblookup(priv, hosts, hostname, top2, found);
 
 			*top2 = '\0';
 			top3 = strrchr(hostname, '.');
 			*top2 = '.';
 
 			// if we have more than 3 levels in the name, look at the top three levels of the name
-			if (top3 && uriblookup(priv, hostname, top2, found)) return true;
+			if (top3 && uriblookup(priv, hosts, hostname, top2, found)) return true;
 			// if that was not found, fall thru to looking at the top two levels
 		}
 		// look at the top two levels of the name
-		return uriblookup(priv, hostname, top, found);
+		return uriblookup(priv, hosts, hostname, top, found);
 	}
 	return false;
 }
@@ -863,7 +874,7 @@
 					return true;
 				}
 				// Check uribl & surbl style list
-				if (priv.uribl_suffix && check_uribl(priv, host, found)) {
+				if (priv.uribl_suffix && check_uribl(priv, hosts, host, found)) {
 					msg = priv.uribl_message;
 					return true;
 				}
@@ -976,6 +987,9 @@
 	if (spamc != spamc_empty) {
 		priv.assassin  = new SpamAssassin(&priv, priv.ip, priv.helo, priv.mailaddr, priv.queueid);
 	}
+	if (dccifd_port) {
+		priv.dccifd = new DccInterface(dccifd_port, &priv, priv.ip, priv.helo, priv.mailaddr, priv.queueid);
+	}
 	return SMFIS_CONTINUE;
 }
 
@@ -994,6 +1008,7 @@
 	}
 
 	if (priv.assassin) priv.assassin->mlfi_envrcpt(ctx, loto);
+	if (priv.dccifd)   priv.dccifd->mlfi_envrcpt(loto);
 	// priv.mailaddr sending original message to loto
 	CONTEXT 	&con = *(dc.find_context(loto)->find_context(priv.mailaddr));
 	VERIFYP 	 ver = con.find_verify(loto);
@@ -1089,6 +1104,14 @@
 	else {
 		free(loto);
 	}
+	// remember first content filtering context
+	if (con.get_content_filtering()) {
+		if (!priv.content_context) priv.content_context = &con;
+		else if (con.get_require() && (priv.content_context != &con)) {
+			smfi_setreply(ctx, "452", "4.2.1", "incompatible filtering contexts");
+			return SMFIS_TEMPFAIL;
+		}
+	}
 	// accept the recipient
 	if (!con.get_content_filtering()) st = white;
 	if (st == oksofar) {
@@ -1097,6 +1120,10 @@
 		priv.only_whites = false;
 		priv.want_spamassassin |= (priv.assassin) &&					// have spam assassin available and
 								  (con.get_spamassassin_limit() != 0);	// want to use it with a non-zero score
+		priv.want_dccgrey	   |= (priv.dccifd) &&						// have dcc interface and
+								  (con.get_grey()); 					// want to use it for greylisting
+		priv.want_dccbulk	   |= (priv.dccifd) &&						// have dcc interface and
+								  (con.get_bulk() != 0);				// want to use it for bulk detection
 	}
 	if (st == white) {
 		priv.have_whites = true;
@@ -1110,6 +1137,7 @@
 	if (priv.authenticated) 	return SMFIS_CONTINUE;
 	if (priv.only_whites)		return SMFIS_CONTINUE;
 	if (priv.want_spamassassin) priv.assassin->mlfi_header(headerf, headerv);
+	if (priv.want_dccgrey || priv.want_dccbulk) priv.dccifd->mlfi_header(ctx, headerf, headerv);
 	return SMFIS_CONTINUE;
 }
 
@@ -1119,6 +1147,7 @@
 	if (priv.authenticated) 	return SMFIS_CONTINUE;
 	if (priv.only_whites)		return SMFIS_CONTINUE;
 	if (priv.want_spamassassin) priv.assassin->mlfi_eoh();
+	if (priv.want_dccgrey || priv.want_dccbulk) priv.dccifd->mlfi_eoh();
 	return SMFIS_CONTINUE;
 }
 
@@ -1128,6 +1157,7 @@
 	if (priv.authenticated) 	return SMFIS_CONTINUE;
 	if (priv.only_whites)		return SMFIS_CONTINUE;
 	if (priv.want_spamassassin) priv.assassin->mlfi_body(data, len);
+	if (priv.want_dccgrey || priv.want_dccbulk) priv.dccifd->mlfi_body(data, len);
 	priv.scanner->scan(data, len);
 	return SMFIS_CONTINUE;
 }
@@ -1143,8 +1173,18 @@
 	// process end of message
 	if (priv.authenticated || priv.only_whites) rc = SMFIS_CONTINUE;
 	else {
+		// assert env_to not empty, it contains the
+		// non-whitelisted folks that want content filtering
 		int score = (priv.want_spamassassin) ? priv.assassin->mlfi_eom() : 0;
-		// assert env_to not empty
+		bool greylist = false;
+		int  dccbulk  = 0;
+		if (priv.want_dccgrey || priv.want_dccbulk) priv.dccifd->mlfi_eom(greylist, dccbulk);
+
+		if (priv.want_dccgrey && greylist) {
+			smfi_setreply(ctx, "452", "4.2.1", "temporary greylist embargoed");
+			rc = SMFIS_TEMPFAIL;
+		}
+		else {
 		char buf[maxlen];
 		string msg;
 		string_set alive;
@@ -1153,8 +1193,9 @@
 		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, score, msg)) {
-				// bad html tags or excessive hosts or high spam assassin score
+				if (!con.acceptable_content(*priv.memory, score, dccbulk, msg)) {
+					// bad html tags or excessive hosts or
+					// high spam assassin score or dcc bulk threshold exceedeed
 				smfi_delrcpt(ctx, rcpt);
 			}
 			else {
@@ -1200,6 +1241,7 @@
 			rc = SMFIS_CONTINUE;
 		}
 	}
+	}
 	// reset for a new message on the same connection
 	mlfi_abort(ctx);
 	return rc;
--- a/src/dnsbl.h	Sun Sep 30 10:27:14 2007 -0700
+++ b/src/dnsbl.h	Thu Oct 04 22:45:21 2007 -0700
@@ -11,6 +11,7 @@
 
 #include "context.h"
 #include "spamass.h"
+#include "dccifd.h"
 
 extern int debug_syslog;
 
@@ -36,7 +37,10 @@
 	char			*client_name;			// fully qualified host name of the smtp client
 	bool			have_whites;			// have at least one whitelisted recipient? need to accept content and remove all non-whitelisted recipients if it fails
 	bool			only_whites;			// every recipient is whitelisted?
-	bool			want_spamassassin;		// at least one non-whitelisted recipients has a non zero spamassassin limit
+	bool			want_spamassassin;		// at least one non-whitelisted recipient has a non zero spamassassin limit
+	bool			want_dccgrey;			// at least one non-whitelisted recipient wants dcc greylisting
+	bool			want_dccbulk;			// at least one non-whitelisted recipient wants dcc bulk filtering
+	CONTEXT 		*content_context;		// first non-whitelisted recipient with a content filtering context
 	context_map 	env_to; 				// map each non-whitelisted recipient to their filtering context
 	recorder		*memory;				// memory for the content scanner
 	url_scanner 	*scanner;				// object to handle body scanning
@@ -46,6 +50,7 @@
 	char			*uribl_message; 		// ""
 	string_set		*content_host_ignore;	// ""
 	SpamAssassin	*assassin;
+	DccInterface	*dccifd;
 
 
 	mlfiPriv();
--- a/src/tokenizer.cpp	Sun Sep 30 10:27:14 2007 -0700
+++ b/src/tokenizer.cpp	Thu Oct 04 22:45:21 2007 -0700
@@ -373,7 +373,7 @@
 	}
 	if (streams.empty()) return NULL;
 	const int PENDING_LIMIT = 1000;
-	static u_char buffer[PENDING_LIMIT];
+	u_char buffer[PENDING_LIMIT];
 	int count = 0;
 	state st = s_init;
 	while (true) {
--- a/test.bash	Sun Sep 30 10:27:14 2007 -0700
+++ b/test.bash	Thu Oct 04 22:45:21 2007 -0700
@@ -13,9 +13,12 @@
 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
+DCCIFD=
+[ -S /var/dcc/dccifd ] && DCCIFD='-b /var/dcc/dccifd'
+#valgrind --leak-check=full --show-reachable=yes src/dnsbl -d 10 $DCCIFD -r /var/run/dnsbl/dnsbl.resolver.sock2 -p local:/var/run/dnsbl/dnsbl.sock2
+valgrind --leak-check=full src/dnsbl -d 10 $DCCIFD -r /var/run/dnsbl/dnsbl.resolver.sock2 -p local:/var/run/dnsbl/dnsbl.sock2
+rm -f tracelog*
+#strace -e trace=open,close,read,write -o tracelog -f -ff -x src/dnsbl -d 10 $DCCIFD -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
--- a/xml/dnsbl.in	Sun Sep 30 10:27:14 2007 -0700
+++ b/xml/dnsbl.in	Thu Oct 04 22:45:21 2007 -0700
@@ -588,8 +588,8 @@
 
 CONTENT    = "content" ("on" | "off") "{" {CONTENT-ST}+ "}"
 CONTENT-ST = (FILTER | URIBL | IGNORE | TLD | CCTLD | HTML-TAGS |
-              HTML-LIMIT | HOST-LIMIT | SPAMASS) ";"
-SPAMASS    = "spamassassin" INTEGER
+              HTML-LIMIT | HOST-LIMIT | SPAMASS | REQUIRE | DCCGREY   |
+              DCCBULK) ";"
 FILTER     = "filter" DNSPREFIX ERROR-MSG2
 URIBL      = "uribl"  DNSPREFIX ERROR-MSG3
 IGNORE     = "ignore"     "{" {HOSTNAME [";"]}+ "}"
@@ -608,6 +608,10 @@
 
 HOST-LIMIT = "host_limit" ("on" INTEGER ERROR-MSG | "off" |
                                                     "soft" INTEGER)
+SPAMASS    = "spamassassin"         INTEGER
+REQUIRE    = "require_match"        ("yes" | "no")
+DCCGREY    = "dcc_greylist"         ("yes" | "no")
+DCCBULK    = "dcc_bulk_threshold"   (INTEGER | "many" | "off")
 
 ENV-TO     = "env_to"     "{" {(TO-ADDR | DCC-TO)}+ "}"
 TO-ADDR    = ADDRESS [";"]
@@ -653,6 +657,9 @@
         host_limit on 20 "Mail containing excessive host names rejected";
         host_limit soft 20;
         spamassassin 4;
+        require_match       yes;
+        dcc_greylist        yes;
+        dcc_bulk_threshold  50;
     };
 
     // backscatter prevention - don't send bounces for mail that we accepted but could not forward
@@ -686,6 +693,9 @@
         html_limit off;
         host_limit soft 20;
         spamassassin 5;
+        require_match       yes;
+        dcc_greylist        yes;
+        dcc_bulk_threshold  20;
     };
 
     generic "(^|[.-])(ppp|h|host)?([0-9]{1,3}[.-](Red-|dynamic[.-])?){4}"
@@ -719,8 +729,10 @@
 
     context minimal {
         dnsbl_list sbl;
-        content on {};
+        content on {
         spamassassin 10;
+            dcc_bulk_threshold  many;
+        };
         generic "^$ " " ";      # regex cannot match, to disable generic rdns rejects
         env_to {
         };