changeset 90:962a1f8f1d9f stable-5-4

add verify statement to verify addresses with better mx host
author carl
date Sun, 18 Sep 2005 10:19:58 -0700
parents 946fc1bcfb2c
children ca46fafc6621
files ChangeLog dnsbl.spec.in package.bash sendmail.st src/context.cpp src/context.h src/dnsbl.cpp test.bash test.cf test.mc xml/dnsbl.in
diffstat 11 files changed, 827 insertions(+), 354 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sun Aug 07 11:26:37 2005 -0700
+++ b/ChangeLog	Sun Sep 18 10:19:58 2005 -0700
@@ -1,5 +1,12 @@
     $Id$
 
+5.4 2005-09-18
+    Add verify statement to specify the smtp host to be used to verify
+    envelope from / recipient pairs.
+
+    Authenticated clients are now exempt from all white/black listing
+    in addition to the dnsbl lookups.
+
 5.3 2005-08-07
     Properly quit if the config file has syntax errors on startup.  Send
     mail to root if the config file needs to be reloaded since it was
--- a/dnsbl.spec.in	Sun Aug 07 11:26:37 2005 -0700
+++ b/dnsbl.spec.in	Sun Sep 18 10:19:58 2005 -0700
@@ -1,6 +1,6 @@
 Summary: DNSBL Sendmail Milter
 Name: dnsbl
-Version: 5.3
+Version: 5.4
 Release: 2
 Copyright: GPL
 Group: System Environment/Daemons
--- a/package.bash	Sun Aug 07 11:26:37 2005 -0700
+++ b/package.bash	Sun Sep 18 10:19:58 2005 -0700
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-VER=dnsbl-5.3
+VER=dnsbl-5.4
 mkdir $VER
     target1=/home/httpd/html/510sg/util/dnsbl.tar.gz
     target2=/home/httpd/html/510sg/dnsbl.conf
Binary file sendmail.st has changed
--- a/src/context.cpp	Sun Aug 07 11:26:37 2005 -0700
+++ b/src/context.cpp	Sun Sep 18 10:19:58 2005 -0700
@@ -8,6 +8,16 @@
 
 #include "includes.h"
 
+// needed for socket io
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
 static char* context_version="$Id$";
 
 char *token_black;
@@ -40,10 +50,290 @@
 char *token_substitute;
 char *token_tld;
 char *token_unknown;
+char *token_verify;
 char *token_white;
 
+char *token_myhostname;
+char myhostname[HOST_NAME_MAX+1];
+
+verify_map	verifiers;
 string_set all_strings; 	// owns all the strings, only modified by the config loader thread
-const int maxlen = 1000;
+const int maxlen = 1000;	// used for snprintf buffers
+const int maxage = 120; 	// smtp verify sockets older than this are ancient
+extern int	  NULL_SOCKET;
+extern time_t ERROR_SOCKET_TIME;	// number of seconds between attempts to open a socket an smtp host for address verification
+
+
+int SMTP::writer() {
+	// log("writer() sees buffer with %s", buffer);
+	// log("writer() sees error %d", (int)error);
+	int rs = 0;
+	if (!error) {
+		int len = strlen(buffer);
+		while (rs < len) {
+			int ws = write(fd, buffer+rs, len-rs);
+			if (ws > 0) {
+				rs += ws;
+			}
+			else {
+				// peer closed the socket!
+				rs = 0;
+				error = true;
+				break;
+			}
+		}
+	}
+	return rs;
+}
+
+
+int SMTP::reader() {
+	// read some bytes terminated by lf or end of buffer.
+	// we may have a multi line response or part thereof in the buffer.
+	// log("reader() sees error %d", (int)error);
+	if (error) return 0;
+	int len = maxlen-1; // room for null terminator
+	while (pending < len) {
+		int ws = read(fd, buffer+pending, len-pending);
+		if (ws > 0) {
+			pending += ws;
+			if (buffer[pending-1] == '\n') break;
+		}
+		else {
+			// peer closed the socket!
+			pending = 0;
+			error = true;
+			break;
+		}
+	}
+	buffer[pending] = '\0';
+	// log("reader() sees buffer with %s", buffer);
+	return pending;
+}
+
+
+int SMTP::read_line() {
+	char *lf = strchr(buffer, '\n');
+	if (!lf) {
+		reader();  // get a lf
+		lf = strchr(buffer, '\n');
+		if (!lf) lf = buffer + pending - 1;
+	}
+	return (lf-buffer)+1;	// number of bytes in this line
+}
+
+
+int SMTP::flush_line(int r) {
+	if (pending > r) memmove(buffer, buffer+r, pending-r);
+	pending -= r;
+}
+
+
+int SMTP::read_response() {
+	pending = 0;
+	buffer[pending] = '\0';
+	while (true) {
+		int r = read_line();
+		// log("read_response() sees line with %s", buffer);
+		// log("read_response() sees line length %d", r);
+		if (r == 0) return 0;	// failed to read any bytes
+		if ((r > 4) && (buffer[3] == '-')) {
+			flush_line(r);
+			continue;
+		}
+		return atoi(buffer);
+	}
+	return 0;
+}
+
+
+int SMTP::cmd(char *c) {
+	if (c) {
+		init();
+		append(c);
+	}
+	append("\r\n");
+	writer();
+	return read_response();
+}
+
+
+int SMTP::helo() {
+	if (read_response() != 220) return 0;
+	init();
+	append("HELO ");
+	append(token_myhostname);
+	return cmd(NULL);
+}
+
+
+int SMTP::rset() {
+	return cmd("RSET");
+}
+
+
+int SMTP::from(char *f) {
+	init();
+	append("MAIL FROM:<");
+	append(f);
+	append(">");
+	return cmd(NULL);
+}
+
+
+int SMTP::rcpt(char *t) {
+	init();
+	append("RCPT TO:<");
+	append(t);
+	append(">");
+	return cmd(NULL);
+}
+
+
+int SMTP::quit() {
+	int rc = cmd("QUIT");
+	shutdown(fd, SHUT_RDWR);
+	close(fd);
+	return rc;
+}
+
+
+//	void SMTP::log(char *m, int v) {
+//		char buf[maxlen];
+//		snprintf(buf, maxlen, m, v);
+//		my_syslog(buf);
+//	}
+//
+//
+//	void SMTP::log(char *m, char *v) {
+//		char buf[maxlen];
+//		snprintf(buf, maxlen, m, v);
+//		my_syslog(buf);
+//	}
+//
+//
+VERIFY::VERIFY(char *h) {
+	host	 = h;
+	last_err = 0;
+	pthread_mutex_init(&mutex, 0);
+}
+
+
+void VERIFY::closer() {
+	bool ok = true;
+	while (ok) {
+		int fd = 0;
+		pthread_mutex_lock(&mutex);
+			if (sockets.empty()) {
+				ok = false;
+			}
+			else {
+				time_t t = times.front();
+				time_t now = time(NULL);
+				if ((now - t) > maxage) {
+					// this socket is ancient, remove it
+					fd = sockets.front();
+					times.pop_front();
+					sockets.pop_front();
+				}
+				else {
+					ok = false;
+				}
+			}
+		pthread_mutex_unlock(&mutex);
+		if (fd) {
+			SMTP s(fd);
+			s.quit();	// closes the fd
+			// s.log("closer() closes ancient %d", fd);
+		}
+	}
+}
+
+
+int  VERIFY::get_socket() {
+	int sock = NULL_SOCKET;
+	pthread_mutex_lock(&mutex);
+		if (!sockets.empty()) {
+			sock = sockets.front();
+			times.pop_front();
+			sockets.pop_front();
+			// SMTP::log("get_socket() %d from cache", sock);
+		}
+	pthread_mutex_unlock(&mutex);
+
+	if (sock == NULL_SOCKET) {
+		time_t now = time(NULL);
+		if ((now - last_err) > ERROR_SOCKET_TIME) {
+			// nothing recent, maybe this time it will work
+			hostent *h = gethostbyname(host);
+			if (h) {
+				sockaddr_in server;
+				server.sin_family = h->h_addrtype;
+				server.sin_port   = htons(25);
+				memcpy(&server.sin_addr, h->h_addr_list[0], h->h_length);
+				sock = socket(PF_INET, SOCK_STREAM, 0);
+				if (sock != NULL_SOCKET) {
+					bool rc = (connect(sock, (sockaddr *)&server, sizeof(server)) == 0);
+					if (!rc) {
+						shutdown(sock, SHUT_RDWR);
+						close(sock);
+						sock = NULL_SOCKET;
+						last_err = now;
+					}
+				}
+				else last_err = now;
+			}
+			else last_err = now;
+		}
+		if (sock != NULL_SOCKET) {
+			SMTP s(sock);
+			if (s.helo() != 250) {
+				put_socket(sock, true);
+				sock = NULL_SOCKET;
+			}
+		}
+	}
+	return sock;
+}
+
+
+void VERIFY::put_socket(int fd, bool err) {
+	if (err) {
+		// SMTP::log("put_socket() %d with error, close it", fd);
+		shutdown(fd, SHUT_RDWR);
+		close(fd);
+		last_err = time(NULL);
+	}
+	else {
+		// SMTP::log("put_socket() %d", fd);
+		pthread_mutex_lock(&mutex);
+			time_t now = time(NULL);
+			times.push_back(now);
+			sockets.push_back(fd);
+		pthread_mutex_unlock(&mutex);
+	}
+}
+
+
+bool VERIFY::ok(char *from, char *to) {
+	if (host == token_myhostname) return true;
+	int fd = get_socket();
+	if (fd == NULL_SOCKET) return true; // cannot verify right now, we have socket errors
+	SMTP s(fd);
+	s.rset();
+	int rc;
+	rc = s.from(from);
+	// s.log("verify::ok from sees %d", rc);
+	if (rc != 250) {
+		put_socket(fd, s.err());
+		return (rc >= 500) ? false : true;
+	}
+	rc = s.rcpt(to);
+	// s.log("verify::ok rcpt sees %d", rc);
+	put_socket(fd, s.err());
+	return (rc >= 500) ? false : true;
+}
+
 
 DNSBL::DNSBL(char *n, char *s, char *m) {
 	name	= n;
@@ -155,6 +445,7 @@
 CONTEXT::CONTEXT(CONTEXTP parent_, char *name_) {
 	parent				= parent_;
 	name				= name_;
+	verify_host 		= NULL;
 	env_from_default	= (parent) ? token_inherit : token_unknown;
 	content_filtering	= (parent) ? parent->content_filtering : false;
 	content_suffix		= NULL;
@@ -203,6 +494,26 @@
 }
 
 
+VERIFYP CONTEXT::find_verify(char *to) {
+	if (verify_host && (verify_host != token_myhostname) && cover_env_to(to)) {
+		verify_map::iterator i = verifiers.find(verify_host);
+		if (i == verifiers.end()) {
+			if (debug_syslog) {
+				char buf[maxlen];
+				snprintf(buf, maxlen, "cannot find struc for %s", verify_host);
+				my_syslog(buf);
+			}
+			return NULL;
+		}
+		VERIFYP v = (*i).second;
+
+		return v;
+	}
+	else if (parent) return parent->find_verify(to);
+	else return NULL;
+}
+
+
 char *CONTEXT::find_from(char *from) {
 	char *rc = token_inherit;
 	string_map::iterator i = env_from.find(from);
@@ -389,6 +700,10 @@
 	}
 	printf("%s     }; \n", indent);
 
+	if (verify_host) {
+		printf("%s     verify %s; \n", indent, verify_host);
+	}
+
 	for (context_map::iterator i=children.begin(); i!=children.end(); i++) {
 		CONTEXTP c = (*i).second;
 		c->dump(level+1);
@@ -682,6 +997,17 @@
 
 ////////////////////////////////////////////////
 //
+bool parse_verify(TOKEN &tok, CONFIG &dc, CONTEXT &me);
+bool parse_verify(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
+	char *host = tok.next();
+	if (!tsa(tok, token_semi)) return false;
+	me.set_verify(host);
+	add_verify_host(host);
+}
+
+
+////////////////////////////////////////////////
+//
 bool parse_envfrom(TOKEN &tok, CONFIG &dc, CONTEXT &me);
 bool parse_envfrom(TOKEN &tok, CONFIG &dc, CONTEXT &me) {
 	char *st = tok.next();
@@ -782,6 +1108,9 @@
 		else if (have == token_envto) {
 			if (!parse_envto(tok, dc, *con)) return false;
 		}
+		else if (have == token_verify) {
+			if (!parse_verify(tok, dc, *con)) return false;
+		}
 		else if (have == token_envfrom) {
 			if (!parse_envfrom(tok, dc, *con)) return false;
 		}
@@ -827,6 +1156,33 @@
 
 
 ////////////////////////////////////////////////
+// setup a new smtp verify host
+//
+void add_verify_host(char *host) {
+	verify_map::iterator i = verifiers.find(host);
+	if (i == verifiers.end()) {
+		VERIFYP v = new VERIFY(host);
+		verifiers[host] = v;
+	}
+}
+
+
+////////////////////////////////////////////////
+// thread to check for verify hosts with old sockets that we can close
+//
+void* verify_closer(void *arg) {
+	while (true) {
+		sleep(maxage);
+		for (verify_map::iterator i=verifiers.begin(); i!=verifiers.end(); i++) {
+			VERIFYP v = (*i).second;
+			v->closer();
+		}
+	}
+	return NULL;
+}
+
+
+////////////////////////////////////////////////
 // init the tokens
 //
 void token_init() {
@@ -860,5 +1216,12 @@
 	token_substitute = register_string("substitute");
 	token_tld		 = register_string("tld");
 	token_unknown	 = register_string("unknown");
+	token_verify	 = register_string("verify");
 	token_white 	 = register_string("white");
+
+	if (gethostname(myhostname, HOST_NAME_MAX+1) != 0) {
+		strncpy(myhostname, "localhost", HOST_NAME_MAX+1);
 }
+	myhostname[HOST_NAME_MAX] = '\0'; // ensure null termination
+	token_myhostname = register_string(myhostname);
+}
--- a/src/context.h	Sun Aug 07 11:26:37 2005 -0700
+++ b/src/context.h	Sun Sep 18 10:19:58 2005 -0700
@@ -12,18 +12,63 @@
 
 class DNSBL;
 class CONTEXT;
+class VERIFY;
 class recorder;
 
 typedef map<char *, char *, ltstr>        string_map;
 typedef set<int>                          int_set;
+typedef list<int>						  fd_list;
+typedef list<time_t>					  time_list;
 typedef list<char *>                      string_list;
 typedef DNSBL *                           DNSBLP;
+typedef VERIFY *						  VERIFYP;
 typedef list<DNSBLP>                      dnsblp_list;
 typedef map<char *, DNSBLP, ltstr>        dnsblp_map;
 typedef CONTEXT *                         CONTEXTP;
 typedef list<CONTEXTP>                    context_list;
 typedef map<char *, CONTEXTP, ltstr>      context_map;
 typedef map<char *, int, ltstr>           ns_mapper;
+typedef map<char *, VERIFYP, ltstr> 	  verify_map;
+
+class SMTP {
+	static const int maxlen = 1000;
+	int  fd;
+	bool error;
+	int  pending;		// unread bytes in buffer, not including the null terminator
+	char buffer[maxlen];
+public:
+	SMTP(int f) 			{fd = f; error = false;};
+	void init() 			{pending = 0; buffer[0] = '\0';};
+	void append(char *c)	{strncat(buffer, c, max(0, maxlen-1-(int)strlen(c)));};
+	bool err()				{return error;};
+	int  writer();
+	int  reader();
+	int  read_line();
+	int  read_response();
+	int  flush_line(int r);
+	int  cmd(char *c);
+	int  helo();
+	int  rset();
+	int  from(char *f);
+	int  rcpt(char *t);
+	int  quit();
+	// static void log(char *m, int v);
+	// static void log(char *m, char *v);
+};
+
+class VERIFY {
+	char			*host;		// host to be used to verify recipient addresses
+	time_t			last_err;	// time of last socket error
+	pthread_mutex_t mutex;		// protect the lists of sockets and timestamps
+	fd_list 		sockets;	// open sockets, ready to be used
+	time_list		times;		// last timestamp when this socket was used
+public:
+	VERIFY(char *h);
+	void closer();			// if the oldest socket is ancient, close it
+	int  get_socket();
+	void put_socket(int fd, bool err);
+	bool ok(char *from, char *to);
+};
 
 struct DNSBL {
     char    *name;      // nickname for this dns based list
@@ -37,7 +82,8 @@
     CONTEXTP        parent;
     char *          name;
     context_map     children;           // map child context names to their contexts
-    string_set      env_to;             //
+	string_set		env_to; 			// this context applies to these envelope recipients
+	char *			verify_host;		// use this smtp host to verify email addresses
     string_map      env_from;           // map senders to white/black/unknown
     context_map     env_from_context;   // map senders to a child context
     char *          env_from_default;   // default value for senders that are not found in the map white/black/unknown/inherit
@@ -65,6 +111,10 @@
     bool        allow_env_to(char *to)                      {return (parent) ? parent->cover_env_to(to) : true;};
     bool        cover_env_to(char *to);
 
+	void		set_verify(char *host)						{verify_host = host;};
+	char*		get_verify()								{return verify_host;};
+	VERIFYP 	find_verify(char *to);
+
     void        add_to(char *to)                            {env_to.insert(to);};
     void        add_from(char *from, char *status)          {env_from[from] = status;};
     void        add_from_context(char *from, CONTEXTP con)  {env_from_context[from] = con;};
@@ -159,6 +209,9 @@
 extern char *token_unknown;
 extern char *token_white;
 
+extern char *token_myhostname;
+
+extern verify_map	verifiers;		// map of smtp hosts to verify structures, owns all the verify structures
 extern string_set all_strings;      // owns all the strings, only modified by the config loader thread
 
 void discard(string_set &s);
@@ -166,6 +219,8 @@
 char* register_string(char *name);
 CONFIG *parse_config(char *fn);
 bool load_conf(CONFIG &dc, char *fn);
+void  add_verify_host(char *host);
+void* verify_closer(void *arg);
 void token_init();
 
 #endif
--- a/src/dnsbl.cpp	Sun Aug 07 11:26:37 2005 -0700
+++ b/src/dnsbl.cpp	Sun Sep 18 10:19:58 2005 -0700
@@ -17,13 +17,6 @@
 -e f|t	 Print the results of looking up from address f and to address
 		 t in the current config
 
-
-TODO:
-
-1) Add option for using smtp connections to verify addresses from backup
-mx machines. This allows the backup mx to learn the valid addresses
-on the primary machine.
-
 */
 
 
@@ -98,7 +91,7 @@
 int    NULL_SOCKET		 = -1;
 char  *resolver_port	 = NULL;		 // unix domain socket to talk to the dns resolver process
 int    resolver_socket	 = NULL_SOCKET;  // socket used to listen for resolver requests
-time_t ERROR_SOCKET_TIME = 60;			 // number of seconds between attempts to open the spam filter socket
+time_t ERROR_SOCKET_TIME = 60;			 // number of seconds between attempts to open a socket to the dns resolver process
 time_t last_error_time;
 int    resolver_sock_count = 0; 		 // protected with fd_pool_mutex
 int    resolver_pool_size  = 0; 		 // protected with fd_pool_mutex
@@ -430,7 +423,7 @@
 	while (true) {
 		// read a question
 		int rs = 0;
-		while (true) {
+		while (rs < maxq) {
 			int ns = read(socket, question+rs, maxq-rs);
 			if (ns > 0) {
 				rs += ns;
@@ -447,6 +440,7 @@
 				return;
 			}
 		}
+		question[rs-1] = '\0';  // ensure null termination
 
 		// find the answer
 #ifdef NS_PACKETSZ
@@ -627,7 +621,6 @@
 //
 bool check_dnsbl(mlfiPriv &priv, dnsblp_list &dnsbll, DNSBLP &rejectlist);
 bool check_dnsbl(mlfiPriv &priv, dnsblp_list &dnsbll, DNSBLP &rejectlist) {
-	if (priv.authenticated) return false;
 	for (dnsblp_list::iterator i=dnsbll.begin(); i!=dnsbll.end(); i++) {
 		DNSBLP dp = *i; 	// non null by construction
 		bool st;
@@ -805,16 +798,20 @@
 	char *rcptaddr	= rcpt[0];
 	char *loto		= to_lower_string(rcptaddr);
 	CONTEXT    &con = *(dc.find_context(loto)->find_context(priv.mailaddr));
+	VERIFYP 	ver = con.find_verify(loto);
 	if (debug_syslog > 1) {
 		char buf[maxlen];
 		char msg[maxlen];
 		snprintf(msg, sizeof(msg), "from <%s> to <%s> using context %s", priv.mailaddr, loto, con.get_full_name(buf,maxlen));
 		my_syslog(&priv, msg);
 	}
+	free(loto);
 	char *fromvalue = con.find_from(priv.mailaddr);
-	free(loto);
 	status st;
-	if (fromvalue == token_black) {
+	if (priv.authenticated) {
+		st = white;
+	}
+	else if (fromvalue == token_black) {
 		st = black;
 	}
 	else if (fromvalue == token_white) {
@@ -834,12 +831,21 @@
 		smfi_setreply(ctx, "550", "5.7.1", buf);
 		return SMFIS_REJECT;
 	}
-	else if (st == black) {
+	if (st == black) {
 		// reject the recipient based on blacklisting either from or to
 		smfi_setreply(ctx, "550", "5.7.1", "no such user");
 		return SMFIS_REJECT;
 	}
-	else {
+	if (ver && (st != white)) {
+		// try to verify this from/to pair of addresses since it is not explicitly whitelisted
+		char *loto = to_lower_string(rcptaddr);
+		bool rc = ver->ok(priv.mailaddr, loto);
+		free(loto);
+		if (!rc) {
+			smfi_setreply(ctx, "550", "5.7.1", "no such user");
+			return SMFIS_REJECT;
+		}
+	}
 		// accept the recipient
 		if (!con.get_content_filtering()) st = white;
 		if (st == oksofar) {
@@ -852,7 +858,6 @@
 		}
 		return SMFIS_CONTINUE;
 	}
-}
 
 sfsistat mlfi_body(SMFICTX *ctx, u_char *data, size_t len)
 {
@@ -1055,7 +1060,7 @@
 	fprintf(stderr, "-c will load and dump the config to stdout\n");
 	fprintf(stderr, "-s will stress test the config loading code by repeating the load/free cycle\n");
 	fprintf(stderr, "        in an infinte loop.\n");
-	fprintf(stderr, "-d will set the syslog message level, currently 0 to 3");
+	fprintf(stderr, "-d will set the syslog message level, currently 0 to 3\n");
 	fprintf(stderr, "-e will print the results of looking up the from and to addresses in the\n");
 	fprintf(stderr, "        current config. The | character is used to separate the from and to\n");
 	fprintf(stderr, "        addresses in the argument to the -e switch\n");
@@ -1358,6 +1363,10 @@
 		my_syslog("failed to create config loader thread");
 	if (pthread_detach(tid))
 		my_syslog("failed to detach config loader thread");
+	if (pthread_create(&tid, 0, verify_closer, 0))
+		my_syslog("failed to create verify closer thread");
+	if (pthread_detach(tid))
+		my_syslog("failed to detach verify closer thread");
 
 	time_t starting = time(NULL);
 	int rc = smfi_main();
--- a/test.bash	Sun Aug 07 11:26:37 2005 -0700
+++ b/test.bash	Sun Sep 18 10:19:58 2005 -0700
@@ -33,10 +33,7 @@
     fi
 fi
 
-if [ "$1" == "comp" ]; then
-    exit
-fi
-
+if [ "$1" == "test" ]; then
 # build the test.cf file
 make test.cf
 
@@ -60,3 +57,4 @@
 
 echo eventually "'"kill -KILL $P2 $P3"'"
 
+fi
--- a/test.cf	Sun Aug 07 11:26:37 2005 -0700
+++ b/test.cf	Sun Sep 18 10:19:58 2005 -0700
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 1998-2002 Sendmail, Inc. and its suppliers.
+# Copyright (c) 1998-2004 Sendmail, Inc. and its suppliers.
 #	All rights reserved.
 # Copyright (c) 1983, 1995 Eric P. Allman.  All rights reserved.
 # Copyright (c) 1988, 1993
@@ -16,8 +16,8 @@
 #####
 #####		SENDMAIL CONFIGURATION FILE
 #####
-##### built by root@ns.five-ten-sg.com on Mon Jan 3 13:23:43 PST 2005
-##### in /usr/src/rh8/gpl/dnsbl
+##### built by root@ns.five-ten-sg.com on Sat Sep 17 18:06:39 PDT 2005
+##### in /usr/usr/cvs/gpl/dnsbl
 ##### using /usr/share/sendmail-cf/ as configuration include directory
 #####
 ######################################################################
@@ -140,6 +140,7 @@
 # ... define this only if sendmail cannot automatically determine your domain
 #Dj$w.Foo.COM
 
+# host/domain names ending with a token in class P are canonical
 CP.
 
 # "Smart" relay host (may be null)
@@ -172,7 +173,7 @@
 # macro storage map
 Kmacro macro
 # possible values for TLS_connection in access map
-C{tls}VERIFY ENCR
+C{Tls}VERIFY ENCR
 
 
 
@@ -212,7 +213,7 @@
 Kgenerics hash /etc/mail/genericstable.db
 
 # Configuration version number
-DZ8.12.8
+DZ8.13.1
 
 
 ###############
@@ -385,10 +386,12 @@
 #O Timeout.queuereturn.normal=5d
 #O Timeout.queuereturn.urgent=2d
 #O Timeout.queuereturn.non-urgent=7d
+#O Timeout.queuereturn.dsn=5d
 O Timeout.queuewarn=4h
 #O Timeout.queuewarn.normal=4h
 #O Timeout.queuewarn.urgent=1h
 #O Timeout.queuewarn.non-urgent=12h
+#O Timeout.queuewarn.dsn=4h
 #O Timeout.hoststatus=30m
 #O Timeout.resolver.retrans=5s
 #O Timeout.resolver.retrans.first=5s
@@ -410,7 +413,7 @@
 O SuperSafe=True
 
 # status file
-O StatusFile=/usr/src/rh8/gpl/dnsbl/sendmail.st
+O StatusFile=/usr/usr/cvs/gpl/dnsbl/sendmail.st
 
 # time zone handling:
 #  if undefined, use system default
@@ -427,6 +430,9 @@
 # fallback MX host
 #O FallbackMXhost=fall.back.host.net
 
+# fallback smart host
+#O FallbackSmartHost=fall.back.host.net
+
 # if we are the best MX host for a site, try it directly instead of config err
 #O TryNullMXList=False
 
@@ -436,6 +442,9 @@
 # load average at which we refuse connections
 O RefuseLA=8
 
+# log interval when refusing connections for this long
+#O RejectLogInterval=3h
+
 # load average at which we delay connections; 0 means no limit
 #O DelayLA=0
 
@@ -445,6 +454,9 @@
 # maximum number of new connections per second
 O ConnectionRateThrottle=1
 
+# Width of the window 
+#O ConnectionRateWindowSize=60s
+
 # work recipient factor
 #O RecipientFactor=30000
 
@@ -517,7 +529,7 @@
 #O RunAsUser=sendmail
 
 # maximum number of recipients per SMTP envelope
-#O MaxRecipientsPerMessage=100
+#O MaxRecipientsPerMessage=0
 
 # limit the rate recipients per SMTP envelope are accepted
 # once the threshold number of recipients have been rejected
@@ -562,9 +574,15 @@
 # lookup type to find information about local mailboxes
 #O MailboxDatabase=pw
 
+# override compile time flag REQUIRES_DIR_FSYNC
+#O RequiresDirfsync=true
+
 # list of authentication mechanisms
 O AuthMechanisms=LOGIN PLAIN
 
+# Authentication realm
+#O AuthRealm
+
 # default authentication information for outgoing connections
 #O DefaultAuthInfo=/etc/mail/default-auth-info
 
@@ -586,6 +604,7 @@
 O Milter.macros.helo={tls_version}, {cipher}, {cipher_bits}, {cert_subject}, {cert_issuer}
 O Milter.macros.envfrom=i, {auth_type}, {auth_authen}, {auth_ssf}, {auth_author}, {mail_mailer}, {mail_host}, {mail_addr}
 O Milter.macros.envrcpt={rcpt_mailer}, {rcpt_host}, {rcpt_addr}
+O Milter.macros.eom={msg_id}
 
 # CA directory
 #O CACertPath
@@ -599,6 +618,8 @@
 #O ClientCertFile
 # Client private key
 #O ClientKeyFile
+# File containing certificate revocation lists 
+#O CRLFile
 # DHParameters (only required if DSA/DH is used)
 #O DHParameters
 # Random data source (required for systems without /dev/urandom under OpenSSL)
@@ -858,7 +879,7 @@
 
 # handle numeric address spec
 R$* < @ [ $+ ] > $*	$: $>ParseLocal $1 < @ [ $2 ] > $3	numeric internet spec
-R$* < @ [ $+ ] > $*	$1 < @ [ $2 ] : $S > $3		Add smart host to path
+R$* < @ [ $+ ] > $*	$: $1 < @ [ $2 ] : $S > $3	Add smart host to path
 R$* < @ [ $+ ] : > $*		$#esmtp $@ [$2] $: $1 < @ [$2] > $3	no smarthost: send
 R$* < @ [ $+ ] : $- : $*> $*	$#$3 $@ $4 $: $1 < @ [$2] > $5	smarthost with mailer
 R$* < @ [ $+ ] : $+ > $*	$#esmtp $@ $3 $: $1 < @ [$2] > $4	smarthost without mailer
@@ -973,7 +994,8 @@
 SMailerToTriple=95
 R< > $*				$@ $1			strip off null relay
 R< error : $-.$-.$- : $+ > $* 	$#error $@ $1.$2.$3 $: $4
-R< error : $- $+ > $*		$#error $@ $(dequote $1 $) $: $2
+R< error : $- : $+ > $*		$#error $@ $(dequote $1 $) $: $2
+R< error : $+ > $*		$#error $: $1
 R< local : $* > $*		$>CanonLocal < $1 > $2
 R< $~[ : $+ @ $+ > $*<$*>$*	$# $1 $@ $3 $: $2<@$3>	use literal user
 R< $~[ : $+ > $*		$# $1 $@ $2 $: $3	try qualified mailer
@@ -1168,6 +1190,7 @@
 R<$={Accept}> <$*>	$@ $1				return value of lookup
 R<REJECT> <$*>		$#error $@ 5.7.1 $: "550 Access denied"
 R<DISCARD> <$*>		$#discard $: discard
+R<QUARANTINE:$+> <$*>	$#error $@ quarantine $: $1
 R<ERROR:$-.$-.$-:$+> <$*>	$#error $@ $1.$2.$3 $: $4
 R<ERROR:$+> <$*>		$#error $: $1
 R<$* <TMPF>> <$*>		$#error $@ 4.3.0 $: "451 Temporary system failure. Please try again later."
@@ -1249,6 +1272,7 @@
 R<PERM> $*		$#error $@ 5.1.8 $: "553 Domain of sender address " $&f " does not exist"
 R<$={Accept}> $*	$# $1		accept from access map
 R<DISCARD> $*		$#discard $: discard
+R<QUARANTINE:$+> $*	$#error $@ quarantine $: $1
 R<REJECT> $*		$#error $@ 5.7.1 $: "550 Access denied"
 R<ERROR:$-.$-.$-:$+> $*		$#error $@ $1.$2.$3 $: $4
 R<ERROR:$+> $*		$#error $: $1
@@ -1352,7 +1376,7 @@
 
 # check client name: first: did it resolve?
 R$*			$: < $&{client_resolve} >
-R<TEMP>			$#TEMP $@ 4.7.1 $: "450 Relaying temporarily denied. Cannot resolve PTR record for " $&{client_addr}
+R<TEMP>			$#TEMP $@ 4.4.0 $: "450 Relaying temporarily denied. Cannot resolve PTR record for " $&{client_addr}
 R<FORGED>		$#error $@ 5.7.1 $: "550 Relaying denied. IP name possibly forged " $&{client_name}
 R<FAIL>			$#error $@ 5.7.1 $: "550 Relaying denied. IP name lookup failed " $&{client_name}
 R$*			$: <@> $&{client_name}
@@ -1375,16 +1399,18 @@
 R$* <@ $* >		$@ $1 <@ $2 >
 R$+			$@ $1 <@ $j >
 
-SDelay_TLS_Client
+SDelay_TLS_Clt
 # authenticated?
 R$*			$: $1 $| $>"tls_client" $&{verify} $| MAIL
 R$* $| $#$+		$#$2
+R$* $| $*		$# $1
 R$*			$# $1
 
-SDelay_TLS_Client2
+SDelay_TLS_Clt2
 # authenticated?
 R$*			$: $1 $| $>"tls_client" $&{verify} $| MAIL
 R$* $| $#$+		$#$2
+R$* $| $*		$@ $1
 R$*			$@ $1
 
 # call all necessary rulesets
@@ -1394,7 +1420,7 @@
 R$+			$: $1 $| $>checkrcpt $1
 R$+ $| $#error $*	$#error $2
 R$+ $| $#discard $*	$#discard $2
-R$+ $| $#$*		$@ $>"Delay_TLS_Client" $2
+R$+ $| $#$*		$@ $>"Delay_TLS_Clt" $2
 R$+ $| $*		$: <?> $>FullAddr $>CanonAddr $1
 R<?> $+ < @ $=w >	$: <> $1 < @ $2 > $| <F: $1@$2 > <D: $2 > <U: $1@>
 R<?> $+ < @ $* >	$: <> $1 < @ $2 > $| <F: $1@$2 > <D: $2 >
@@ -1402,7 +1428,7 @@
 R<> $* $| <$+>		$: <@> $1 $| $>SearchList <! Spam> $| <$2> <>
 R<@> $* $| $*		$: $2 $1		reverse result
 # is the recipient a spam friend?
-R<FRIEND> $+		$@ $>"Delay_TLS_Client2" SPAMFRIEND
+R<FRIEND> $+		$@ $>"Delay_TLS_Clt2" SPAMFRIEND
 R<$*> $+		$: $2
 R$*			$: $1 $| $>checkmail <$&f>
 R$* $| $#$*		$#$2
@@ -1506,10 +1532,10 @@
 ######################################################################
 
 # class with valid marks for SearchList
-C{src}E F D U 
+C{Src}E F D U 
 SSearchList
 # just call the ruleset with the name of the tag... nice trick...
-R<$+> $| <$={src}:$*> <$*>	$: <$1> $| <$4> $| $>$2 <$3> <?> <$1> <>
+R<$+> $| <$={Src}:$*> <$*>	$: <$1> $| <$4> $| $>$2 <$3> <?> <$1> <>
 R<$+> $| <> $| <?> <>		$@ <?>
 R<$+> $| <$+> $| <?> <>		$@ $>SearchList <$1> $| <$2>
 R<$+> $| <$*> $| <$+> <>	$@ <$3>
@@ -1530,7 +1556,7 @@
 R$@ $| $*		$#error $@ 5.7.1 $: "550 not authenticated"
 R$* $| $&{auth_authen}		$@ identical
 R$* $| <$&{auth_authen}>	$@ identical
-R$* $| $*		$: $1 $| $>"Local_trust_auth" $1
+R$* $| $*		$: $1 $| $>"Local_trust_auth" $2
 R$* $| $#$*		$#$2
 R$*			$#error $@ 5.7.1 $: "550 " $&{auth_authen} " not allowed to act as " $&{auth_author}
 
@@ -1624,16 +1650,16 @@
 STLS_connection
 R$* $| <$*>$*			$: $1 $| <$2>
 # create the appropriate error codes
-R$* $| <PERM + $={tls} $*>	$: $1 $| <503:5.7.0> <$2 $3>
-R$* $| <TEMP + $={tls} $*>	$: $1 $| <403:4.7.0> <$2 $3>
-R$* $| <$={tls} $*>		$: $1 $| <403:4.7.0> <$2 $3>
+R$* $| <PERM + $={Tls} $*>	$: $1 $| <503:5.7.0> <$2 $3>
+R$* $| <TEMP + $={Tls} $*>	$: $1 $| <403:4.7.0> <$2 $3>
+R$* $| <$={Tls} $*>		$: $1 $| <403:4.7.0> <$2 $3>
 # deal with TLS handshake failures: abort
 RSOFTWARE $| <$-:$+> $* 	$#error $@ $2 $: $1 " TLS handshake failed."
 RSOFTWARE $| $* 		$#error $@ 4.7.0 $: "403 TLS handshake failed."
 R$* $| <$*> <VERIFY>		$: <$2> <VERIFY> <> $1
 R$* $| <$*> <VERIFY + $+>	$: <$2> <VERIFY> <$3> $1
-R$* $| <$*> <$={tls}:$->$*	$: <$2> <$3:$4> <> $1
-R$* $| <$*> <$={tls}:$- + $+>$*	$: <$2> <$3:$4> <$5> $1
+R$* $| <$*> <$={Tls}:$->$*	$: <$2> <$3:$4> <> $1
+R$* $| <$*> <$={Tls}:$- + $+>$*	$: <$2> <$3:$4> <$5> $1
 R$* $| $*			$@ OK
 # authentication required: give appropriate error
 # other side did authenticate (via STARTTLS)
--- a/test.mc	Sun Aug 07 11:26:37 2005 -0700
+++ b/test.mc	Sun Sep 18 10:19:58 2005 -0700
@@ -33,7 +33,7 @@
 define(`confTO_IDENT', `0')
 define(`MAIL_SETTINGS_DIR', `/etc/mail/')
 define(`PROCMAIL_MAILER_PATH',`/usr/bin/procmail')
-define(`STATUS_FILE', /usr/src/rh8/gpl/dnsbl/sendmail.st)
+define(`STATUS_FILE', /usr/usr/cvs/gpl/dnsbl/sendmail.st)
 define(`MILTER', 1)
 DAEMON_OPTIONS(`port=26')dnl
 FEATURE(always_add_domain)
--- a/xml/dnsbl.in	Sun Aug 07 11:26:37 2005 -0700
+++ b/xml/dnsbl.in	Sun Sep 18 10:19:58 2005 -0700
@@ -2,7 +2,7 @@
 
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
-<title>DNSBL Sendmail milter - Version 5.3</title>
+<title>DNSBL Sendmail milter - Version 5.4</title>
 </head>
 
 <center>Introduction</center>
@@ -89,8 +89,9 @@
 <hr> <center>Filtering Procedure</center>
 
 <p>If the client has authenticated with sendmail, the mail is accepted,
-the dns lists are not checked, and the body content is not scanned.
-Otherwise, we follow these steps for each recipient.
+the filtering contexts are not used, the dns lists are not checked, and
+the body content is not scanned.  Otherwise, we follow these steps for
+each recipient.
 
 <ol>
 
@@ -111,6 +112,14 @@
 point to a child context.  If such an entry is found, we switch to that
 child filtering context.
 
+<br><br><li>If the filtering context specifies a verification host, and
+the envelope to email address is covered by this filtering context, and
+the verification host is not our own hostname, we open an smtp
+conversation with that verification host.  The current envelope from and
+recipient to values are passed to that verification host.  If we receive
+anything other than a 250 response those commands, we reject the current
+recipient with "no such user".
+
 <br><br><li>We lookup [1) the full envelope from email address, 2) the
 domain name part of the envelope from address, 3) the user@ part of the
 envelope from address] in the filtering context env_from statement.