[Greylist-users] Any OpenBSD'ers out there?

Bob Beck beck at bofh.cns.ualberta.ca
Sun Feb 22 00:03:03 PST 2004


	If so you may find the following interesting - testing and
feedback welcome. 

	 -Bob

Index: libexec/spamd/Makefile
===================================================================
RCS file: /cvs/src/libexec/spamd/Makefile,v
retrieving revision 1.6
diff -u -r1.6 Makefile
--- libexec/spamd/Makefile	2 Jul 2003 22:44:11 -0000	1.6
+++ libexec/spamd/Makefile	22 Feb 2004 07:10:38 -0000
@@ -1,7 +1,7 @@
 #	$OpenBSD: Makefile,v 1.6 2003/07/02 22:44:11 deraadt Exp $
 
 PROG=	spamd
-SRCS=	spamd.c sdl.c
+SRCS=	spamd.c sdl.c grey.c
 MAN=	spamd.8
 
 CFLAGS+= -Wall -Wstrict-prototypes -ansi
Index: libexec/spamd/grey.c
===================================================================
RCS file: libexec/spamd/grey.c
diff -N libexec/spamd/grey.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libexec/spamd/grey.c	22 Feb 2004 07:10:38 -0000
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 2004 Bob Beck.  All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/fcntl.h>
+#include <sys/wait.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+#include <db.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "grey.h"
+
+extern struct syslog_data sdata;
+extern struct passwd *pw;
+extern FILE * grey;
+extern int debug;
+
+size_t whitecount, whitealloc;
+char **whitelist;
+int pfdev;
+
+DB		*db;
+DBT		dbk, dbd;
+BTREEINFO	btreeinfo;
+
+/* borrowed from dhartmei.. */
+int
+address_valid_v4(const char *a)
+{
+	if (!*a)
+		return (0);
+	while (*a)
+		if ((*a >= '0' && *a <= '9') || *a == '.')
+			a++;
+		else
+			return (0);
+	return (1);
+}
+
+int
+address_valid_v6(const char *a)
+{
+	if (!*a)
+		return (0);
+	while (*a)
+		if ((*a >= '0' && *a <= '9') ||
+		    (*a >= 'a' && *a <= 'f') ||
+		    (*a >= 'A' && *a <= 'F') ||
+		    *a == ':')
+			a++;
+		else
+			return (0);
+	return (1);
+}
+
+int
+configure_pf(char **addrs, int count)
+{
+	char *argv[11]= {"pfctl", "-p", "/dev/pf", "-q", "-t", "spamd-white",
+	    "-T", "replace", "-f" "-", NULL};
+	FILE *pf = NULL;
+	int i, pdes[2];
+	pid_t pid;
+	char *fdpath;
+
+	if (debug)
+		fprintf(stderr, "configure_pf - device on fd %d\n", pfdev);
+	if (pfdev < 1 || pfdev > 63)
+		return(-1);
+	if (asprintf(&fdpath, "/dev/fd/%d", pfdev) == -1)
+		return(-1);
+	argv[2] = fdpath;
+	if (pipe(pdes) != 0) {
+		syslog_r(LOG_INFO, &sdata, "pipe failed (%m)");
+		free(fdpath);
+		return(-1);
+	}
+	switch (pid = fork()) {
+	case -1:
+		syslog_r(LOG_INFO, &sdata, "fork failed (%m)");
+		free(fdpath);
+		return(-1);
+	case 0:
+		/* child */
+		close(pdes[1]);
+		if (pdes[0] != STDIN_FILENO) {
+			dup2(pdes[0], STDIN_FILENO);
+			close(pdes[0]);
+		}
+		execvp(PATH_PFCTL, argv);
+		_exit(1);
+	}
+
+	/* parent */
+	free(fdpath);
+	close(pdes[0]);
+	pf = fdopen(pdes[1], "w");
+	if (pf == NULL) {
+		syslog_r(LOG_INFO, &sdata, "fdopen failed (%m)");
+		return(-1);
+	}
+	for (i = 0; i  < count; i++)
+		if (addrs[i] != NULL) {
+			fprintf(pf, "%s/32\n", addrs[i]);
+			free(addrs[i]);
+			addrs[i] = NULL;
+		}
+	fflush(pf);
+	fclose(pf);
+	close(pdes[1]);
+	waitpid(pid, NULL, 0);
+	return(0);
+}
+
+/* validate, then add to list of addrs to whitelist */
+int
+addwhiteaddr(char *addr)
+{
+	struct in_addr ia;
+
+	if (address_valid_v4(addr)) {
+		if (inet_aton(addr, &ia) == 1) {
+			if (whitecount == whitealloc) {
+				char **tmp;
+
+				tmp = realloc(whitelist,
+				    (whitealloc + 1024) * sizeof(char **));
+				if (tmp == NULL)
+					return(-1);
+				whitelist = tmp;
+				whitealloc += 1024;
+			}
+			whitelist[whitecount] = strdup(addr);
+			if (whitelist[whitecount] == NULL)
+				return(-1);
+			whitecount++;
+		}
+	} else if (address_valid_v6(addr)) {
+		/* XXX deal with v6 later */
+		return(-1);
+	} else
+		return(-1);
+	return(0);
+}
+
+int
+greyscan(char *dbname)
+{
+	struct gdata gd;
+	int r;
+
+	/* walk db, expire, and whitelist */
+	memset(&btreeinfo, 0, sizeof(btreeinfo));
+	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_BTREE, &btreeinfo);
+	if (db == NULL) {
+		syslog_r(LOG_INFO, &sdata, "dbopen failed (%m)");
+		return(-1);
+	}
+	memset(&dbk, 0, sizeof(dbk));
+	memset(&dbd, 0, sizeof(dbd));
+	for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
+	    r = db->seq(db, &dbk, &dbd, R_NEXT)) {
+		char a[128];
+
+		if ((dbk.size < 1) || dbd.size != sizeof(struct gdata)) {
+			db->close(db);
+			return(-1);
+		}
+		memcpy(&gd, dbd.data, sizeof(gd));
+		if (gd.expire < time(NULL)) {
+			/* get rid of entry */
+			if (debug) {
+				memset(a, 0, sizeof(a));
+				memcpy(a, dbk.data, MIN(sizeof(a),
+				    dbk.size));
+				syslog_r(LOG_DEBUG, &sdata,
+				    "deleting %s from %s", a, dbname);
+			}
+			if (db->del(db, &dbk, 0)) {
+				db->sync(db, 0);
+				db->close(db);
+				return(-1);
+			}
+		} else if (gd.pass < time(NULL)) {
+			int tuple = 0;
+			char *cp;
+
+			/*
+			 * remove this tuple-keyed  entry from db
+			 * add address to whitelist
+			 * add an address-keyed entry to db
+			 */
+			memset(a, 0, sizeof(a));
+			memcpy(a, dbk.data, MIN(sizeof(a) - 1, dbk.size));
+			cp = strchr(a, '\n');
+			if (cp != NULL) {
+				tuple = 1;
+				*cp = '\0';
+			}
+			if ((addwhiteaddr(a) == -1) && db->del(db, &dbk, 0))
+				goto bad;
+			if (tuple) {
+				if (db->del(db, &dbk, 0))
+					goto bad;
+				/* re-add entry, keyed only by ip */
+				memset(&dbk, 0, sizeof(dbk));
+				dbk.size = strlen(a);
+				dbk.data = a;
+				memset(&dbd, 0, sizeof(dbd));
+				dbd.size = sizeof(gd);
+				dbd.data = &gd;
+				if (db->put(db, &dbk, &dbd, 0))
+					goto bad;
+				syslog_r(LOG_DEBUG, &sdata,
+				    "whitelisting %s in %s", a, dbname);
+			}
+			if (debug)
+				fprintf(stderr, "whitelisted %s\n", a);
+		}
+	}
+	configure_pf(whitelist, whitecount);
+	db->sync(db, 0);
+	db->close(db);
+	return(0);
+ bad:
+	db->sync(db, 0);
+	db->close(db);
+	return(-1);
+}
+
+int
+greyupdate(char *dbname, char *ip, char *from, char *to)
+{
+	char *key = NULL;
+	struct gdata gd;
+	time_t now;
+	int r;
+
+	/* open with lock, find record, update, close, unlock */
+	memset(&btreeinfo, 0, sizeof(btreeinfo));
+	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_BTREE, &btreeinfo);
+	if (db == NULL)
+		return(-1);
+	if (asprintf(&key, "%s\n%s\n%s", ip, from, to) == -1)
+		goto bad;
+	memset(&dbk, 0, sizeof(dbk));
+	dbk.size = strlen(key);
+	dbk.data = key;
+	memset(&dbd, 0, sizeof(dbd));
+	r = db->get(db, &dbk, &dbd, 0);
+	now = time(NULL);
+	if (r == -1)
+		goto bad;
+	if (r) {
+		/* new entry */
+		memset(&gd, 0, sizeof(gd));
+		gd.first = now;
+		gd.bcount = 1;
+		gd.pass = now + GREYEXP;
+		gd.expire = now + GREYEXP;
+		memset(&dbk, 0, sizeof(dbk));
+		dbk.size = strlen(key);
+		dbk.data = key;
+		memset(&dbd, 0, sizeof(dbd));
+		dbd.size = sizeof(gd);
+		dbd.data = &gd;
+		r = db->put(db, &dbk, &dbd, 0);
+		if (r)
+			goto bad;
+		if (debug)
+			fprintf(stderr, "added %s\n", key);
+	} else {
+		/* existing entry */
+		if (dbd.size != sizeof(gd)) {
+			/* whatever this is, it doesn't belong */
+			db->del(db, &dbk, 0);
+			goto bad;
+		}
+		memcpy(&gd, dbd.data, sizeof(gd));
+		gd.bcount++;
+		if (gd.first + PASSTIME < now) {
+			gd.pass = now;
+			gd.expire = now + WHITEEXP;
+		}
+		memset(&dbk, 0, sizeof(dbk));
+		dbk.size = strlen(key);
+		dbk.data = key;
+		memset(&dbd, 0, sizeof(dbd));
+		dbd.size = sizeof(gd);
+		dbd.data = &gd;
+		r = db->put(db, &dbk, &dbd, 0);
+		if (r)
+			goto bad;
+		if (debug)
+			fprintf(stderr, "updated %s\n", key);
+	}
+	db->close(db);
+	free(key);
+	return(0);
+ bad:
+	if (key != NULL)
+		free(key);
+	db->close(db);
+	return(-1);
+}
+
+int
+greyreader(void)
+{
+	char ip[32], from[MAX_MAIL], to[MAX_MAIL], *buf;
+	size_t len;
+	int state;
+
+	state = 0;
+	if (grey == NULL)
+		errx(-1, "No greylist pipe stream!\n");
+	while ((buf = fgetln(grey, &len))) {
+		if (buf[len - 1] == '\n')
+			buf[len - 1] = '\0';
+		else
+			/* all valid lines end in \n */
+			continue;
+		if (strlen(buf) < 4)
+			continue;
+
+		switch(state) {
+		case 0:
+			if (strncmp(buf, "IP:", 3) != 0)
+				break;
+			strlcpy(ip, buf+3, sizeof(ip));
+			if (address_valid_v4(ip))
+				state = 1;
+			else
+				state = 0;
+			break;
+		case 1:
+			if (strncmp(buf, "FR:", 3) != 0) {
+				state = 0;
+				break;
+			}
+			strlcpy(from, buf+3, sizeof(from));
+			state = 2;
+			break;
+		case 2:
+			if (strncmp(buf, "TO:", 3) != 0) {
+				state = 0;
+				break;
+			}
+			strlcpy(to, buf+3, sizeof(to));
+			if (debug)
+				fprintf(stderr,
+				    "Got Grey IP %s from %s to %s\n",
+				    ip, from, to);
+			greyupdate(PATH_SPAMD_DB, ip, from, to);
+			state = 0;
+			break;
+		}
+	}
+	return (0);
+}
+
+void
+greyscanner(void)
+{
+	while(1) {
+		sleep(DB_SCAN_INTERVAL);
+		greyscan(PATH_SPAMD_DB);
+	}
+	/* NOTREACHED */
+}
+
+int
+greywatcher(void)
+{
+	pid_t pid;
+
+	pfdev = open("/dev/pf", O_RDWR);
+	if (pfdev == -1)
+		err(1, "open of /dev/pf failed");
+
+	/*
+	 * lose root, continue as non-root user
+	 * XXX Should not be _spamd - as it currently is.
+	 */
+	if (pw) {
+		setgroups(1, &pw->pw_gid);
+		setegid(pw->pw_gid);
+		setgid(pw->pw_gid);
+		seteuid(pw->pw_uid);
+		setuid(pw->pw_uid);
+	}
+
+	if (!debug) {
+		if (daemon(1, 1) == -1)
+			err(1, "fork");
+	}
+
+	pid = fork();
+	if (pid == -1)
+		err(1, "fork");
+	if (pid == 0) {
+		/*
+		 * child, talks to jailed spamd over greypipe,
+		 * updates db. has no access to pf.
+		 */
+		close(pfdev);
+		greyreader();
+	} else {
+		/*
+		 * parent, scans db periodically for changes and updates
+		 * pf whitelist table accordingly.
+		 */
+		fclose(grey);
+		greyscanner();
+	}
+	return(0);
+}
Index: libexec/spamd/grey.h
===================================================================
RCS file: libexec/spamd/grey.h
diff -N libexec/spamd/grey.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libexec/spamd/grey.h	22 Feb 2004 07:10:38 -0000
@@ -0,0 +1,18 @@
+
+#define MAX_MAIL 1024 /* XXX this sucks. Fixme */
+#define PASSTIME (60 * 30) /* pass after first retry seen after 30 mins */
+#define GREYEXP (60 * 60 * 4) /* remove grey entries after 4 hours */
+#define WHITEEXP (60 * 60 * 24 * 36) /* remove white entries after 36 days */
+#define PATH_PFCTL "/sbin/pfctl"
+#define DB_SCAN_INTERVAL 60
+#define PATH_SPAMD_DB "/var/db/spamd"
+
+struct gdata {
+	time_t first;  /* when did we see it first */
+	time_t pass;   /* when was it whitelisted */
+	time_t expire; /* when will we get rid of this entry */
+	int bcount;    /* how many times have we blocked it */
+	int pcount;    /* how many good connections have we seen after wl */
+};
+
+extern int greywatcher(void);
Index: libexec/spamd/sdl.c
===================================================================
RCS file: /cvs/src/libexec/spamd/sdl.c,v
retrieving revision 1.10
diff -u -r1.10 sdl.c
--- libexec/spamd/sdl.c	26 Sep 2003 16:07:29 -0000	1.10
+++ libexec/spamd/sdl.c	22 Feb 2004 07:10:38 -0000
@@ -188,13 +188,13 @@
 		break;
 	case AF_INET6:
 		if (((a->addr32[0]) ==
-		     (b->addr32[0] & m->addr32[0])) &&
+		    (b->addr32[0] & m->addr32[0])) &&
 		    ((a->addr32[1]) ==
-		     (b->addr32[1] & m->addr32[1])) &&
+		    (b->addr32[1] & m->addr32[1])) &&
 		    ((a->addr32[2]) ==
-		     (b->addr32[2] & m->addr32[2])) &&
+		    (b->addr32[2] & m->addr32[2])) &&
 		    ((a->addr32[3]) ==
-		     (b->addr32[3] & m->addr32[3])))
+		    (b->addr32[3] & m->addr32[3])))
 			match++;
 		break;
 	}
@@ -213,8 +213,8 @@
 	struct sdlist *sdl;
 	struct sdentry *sda;
 	struct sdaddr *source = (struct sdaddr *) src;
-	static int sdnewlen = 0;
-	static struct sdlist **sdnew = NULL;
+	int sdnewlen = 0;
+	struct sdlist **sdnew = NULL;
 
 	if (head == NULL)
 		return (NULL);
@@ -229,7 +229,7 @@
 
 					tmp = realloc(sdnew,
 					    (sdnewlen + 128) *
-					     sizeof(struct sdlist *));
+					    sizeof(struct sdlist *));
 					if (tmp == NULL)
 						/*
 						 * XXX out of memory -
Index: libexec/spamd/spamd.8
===================================================================
RCS file: /cvs/src/libexec/spamd/spamd.8,v
retrieving revision 1.42
diff -u -r1.42 spamd.8
--- libexec/spamd/spamd.8	21 Jan 2004 01:55:10 -0000	1.42
+++ libexec/spamd/spamd.8	22 Feb 2004 07:10:38 -0000
@@ -64,6 +64,10 @@
 does not
 .Xr fork 2
 into the background.
+.It Fl g
+Greylisting mode - See GREYLISTING below.
+.It Fl G Ar passtime:greyexp:whiteexp
+Adjust the three time parameters for greylisting, see GREYLISTING below.
 .It Fl n Ar name
 The SMTP version banner that is reported upon initial connection.
 .It Fl p Ar port
@@ -200,6 +204,106 @@
 a connecting address is matched.
 .Xr spamd-setup 8
 is normally used to configure this information.
+.Sh GREYLISTING
+When run in greylisting mode, 
+.Nm 
+will only consider connections from address not blacklisted by
+.Xr spamd-setup 8
+for greylisting. Such connections will not be stuttered at or delayed,
+and will receive the pleasantly innocuous temporary failure of 
+.Bd -literal -offset 4n
+450 Temporary failure, please try again later.
+.Ed
+
+in the SMTP dialogue immediately after the recipient is specified.
+.Nm
+will use the db file in
+.Pa /var/db/spamd
+to track these non-blacklisted connections to
+.Nm
+by connecting IP address, envelope-from, and envelope-to, or "tuple" for 
+short. 
+
+A previously unseen tuple is added to the
+.Pa /var/db/spamd
+database, recording the time an initial connection
+attempt was seen. After
+.Em passtime
+minutes (by default 30) if 
+.Nm
+sees a retried attempt to deliver mail for the same tuple, 
+.Nm
+will whitelist the connecting address by adding it as a 
+whiltelist entry to to
+.Pa /var/db/spamd .
+
+.Nm
+regularly scans the 
+.Pa /var/db/spamd
+database and configures all whitelist addreesses as the 
+.Em spamd-white
+.Xr pf 4
+table. The 
+.Em spamd-white
+table must be used to allow connections to pass to the 
+real MTA as in the following
+.Xr pf.conf 5
+example:
+.Bd -literal -offset 4n
+table <spamd> persist
+table <spamd-white> persist
+rdr pass inet proto tcp from <spamd> to any \\
+    port smtp -> 127.0.0.1 port 8025
+no rdr inet proto tcp from <spamd-white> to any \\
+    port smtp
+rdr pass inet proto tcp from any to any port smtp \\
+    -> 127.0.0.1 port 8025
+.Ed
+
+With this configuration, 
+.Xr spamd-setup 8
+should be used to configure blacklists, in
+.Nm
+and add them to the
+.Em spamd
+.Xr pf 4
+table. These connections will be stuttered at by spamd.
+All other connections not in the 
+.Em spamd-white
+table are redirected to 
+.Nm
+but will not be stuttered at. Such connections will be
+considered for greylisting and eventual whitelisting (by addition
+to the 
+.Em spamd-white
+table so they are not redirected) if they retry mail delivery.
+
+.Nm
+removes tuple entries from the
+.Pa /var/db/spamd
+database if delivery has not been retried within
+.Em greyexp
+hours (by default 4) from the initial time a connection is seen. The
+default is 4 hours as this is the most common setting after which
+MTA's will give up attempting to retry delivery of a message.
+
+.Nm
+removes whitelist entries from the
+.Pa /var/db/spamd
+database if no mail delivery activity has been seen from the 
+whitelisted address by 
+.Xr spamlogd 8
+within
+.Em whiteexp
+hours (by default 864, or 36 days) from the initial time an address
+is whitelisted.  The default is 36 days to allow for the delivery of
+monthly mailing list digests without greylist delays every time.
+.Xr spamlogd 8
+should be used to update the whitelist entries in 
+.Pa /var/db/spamd
+when connections are seen to pass to the real MTA on the 
+.Em smtp
+port.
 .Sh LOGGING
 .Nm
 sends log messages to
@@ -216,6 +320,19 @@
 !spamd
 daemon.err;daemon.warn;daemon.info	/var/log/spamd
 .Ed
+.Sh BUGS
+.Nm
+currently uses the user
+.Em _spamd
+outside a chroot jail when running in greylisting mode, and requires
+the greylisting database in 
+.Pa /var/db/spamd
+to be owned by the 
+.Em _spamd
+user. This is wrong and should change to a distinct user from the
+one used by the chrooted
+.Nm spamd
+process. 
 .Sh FILES
 /etc/spamd.conf
 .Sh SEE ALSO
@@ -225,6 +342,8 @@
 .Xr syslog.conf 5 ,
 .Xr pfctl 8 ,
 .Xr spamd-setup 8 ,
+.Xr spamdb 8 ,
+.Xr spamlog 8 ,
 .Xr syslogd 8
 .Sh HISTORY
 The
Index: libexec/spamd/spamd.c
===================================================================
RCS file: /cvs/src/libexec/spamd/spamd.c,v
retrieving revision 1.52
diff -u -r1.52 spamd.c
--- libexec/spamd/spamd.c	9 Nov 2003 07:35:25 -0000	1.52
+++ libexec/spamd/spamd.c	22 Feb 2004 07:10:38 -0000
@@ -48,6 +48,7 @@
 #include <machine/endian.h>
 
 #include "sdl.h"
+#include "grey.h"
 
 struct con {
 	int fd;
@@ -57,7 +58,8 @@
 	struct sockaddr_in sin;
 	void *ia;
 	char addr[32];
-	char mail[64], rcpt[64];
+	char mail[MAX_MAIL], rcpt[MAX_MAIL];
+	struct sdlist **blacklists;
 
 	/*
 	 * we will do stuttering by changing these to time_t's of
@@ -80,6 +82,8 @@
 	int ol;
 	int data_lines;
 	int data_body;
+	int stutter;
+	int sr;
 } *con;
 
 void     usage(void);
@@ -88,8 +92,8 @@
 void     parse_configs(void);
 void     do_config(void);
 int      append_error_string (struct con *, size_t, char *, int, void *);
-char    *build_reply(struct  con *);
-char    *doreply(struct con *);
+void     build_reply(struct  con *);
+void     doreply(struct con *);
 void     setlog(char *, size_t, char *);
 void     initcon(struct con *, int, struct sockaddr_in *);
 void     closecon(struct con *);
@@ -103,6 +107,12 @@
 char *reply = NULL;
 char *nreply = "450";
 char *spamd = "spamd IP-based SPAM blocker";
+int greypipe[2];
+FILE *grey;
+time_t passtime = PASSTIME;
+time_t greyexp = GREYEXP;
+time_t whiteexp = WHITEEXP;
+struct passwd *pw;
 
 extern struct sdlist *blacklists;
 
@@ -116,18 +126,18 @@
 int maxcon = MAXCON;
 int clients;
 int debug;
+int greylist;
 int verbose;
 int stutter = 1;
 int window;
 #define MAXTIME 400
 
-
 void
 usage(void)
 {
 	fprintf(stderr,
-	    "usage: spamd [-45dv] [-c maxcon] [-n name] [-p port] [-r reply] "
-	    "[-s secs]\n");
+	    "usage: spamd [-45dgv] [-c maxcon] [-G mins:hours:hours] [-n name]"
+	    "[-p port] [-r reply] [-s secs]\n");
 	fprintf(stderr,
 	    "             [-w window]\n");
 	exit(1);
@@ -152,10 +162,9 @@
 		cp->obuf = tmp;
 		cp->obufalloc = 1;
 		return (cp->obuf + off);
-	}	
+	}
 }
 
-
 int
 parse_configline(char *line)
 {
@@ -315,7 +324,6 @@
 	conffd = -1;
 }
 
-
 int
 append_error_string(struct con *cp, size_t off, char *fmt, int af, void *ia)
 {
@@ -404,17 +412,39 @@
 	return (i);
 }
 
-
 char *
+loglists(struct con *cp) {
+	static char matchlists[80];
+	struct sdlist **matches;
+	int s = sizeof(matchlists) - 4;
+
+	matchlists[0] = '\0';
+	matches = cp->blacklists;
+	if (matches == NULL)
+		return(NULL);
+	for (; *matches; matches++) {
+
+		/* don't report an insane amount of lists in the logs.
+		 * just truncate and indicate with ...
+		 */
+		if (strlen(matchlists) + strlen(matches[0]->tag) + 1
+		    >= s)
+			strlcat(matchlists, " ...", sizeof(matchlists));
+		else {
+			strlcat(matchlists, " ", s);
+			strlcat(matchlists, matches[0]->tag, s);
+		}
+	}
+	return matchlists;
+}
+
+void
 build_reply(struct con *cp)
 {
 	struct sdlist **matches;
-	static char matchlists[80];
 	int off = 0;
 
-	matchlists[0] = '\0';
-
-	matches = sdl_lookup(blacklists, cp->af, cp->ia);
+	matches = cp->blacklists;
 	if (matches == NULL) {
 		if (cp->osize)
 			free(cp->obuf);
@@ -423,20 +453,10 @@
 		goto bad;
 	}
 	for (; *matches; matches++) {
-		int used = 0, s = sizeof(matchlists) - 4;
+		int used = 0;
 		char *c = cp->obuf + off;
 		int left = cp->osize - off;
 
-		/* don't report an insane amount of lists in the logs.
-		 * just truncate and indicate with ...
-		 */
-		if (strlen(matchlists) + strlen(matches[0]->tag) + 1
-		    >= s)
-			strlcat(matchlists, " ...", sizeof(matchlists));
-		else {
-			strlcat(matchlists, " ", s);
-			strlcat(matchlists, matches[0]->tag, s);
-		}
 		used = append_error_string(cp, off, matches[0]->string,
 		    cp->af, cp->ia);
 		if (used == -1)
@@ -453,33 +473,37 @@
 			cp->obuf[off] = '\0';
 		}
 	}
-	return matchlists;
 bad:
 	/* Out of memory, or no match. give generic reply */
-	asprintf(&cp->obuf,
-	    "%s-Sorry %s\n"
-	    "%s-You are trying to send mail from an address listed by one\n"
-	    "%s or more IP-based registries as being a SPAM source.\n",
-	    nreply, cp->addr, nreply, nreply);
+	if (cp->obuf != NULL && cp->obufalloc)
+		free(cp->obuf);
+	if (cp->blacklists != NULL)
+		asprintf(&cp->obuf,
+		    "%s-Sorry %s\n"
+		    "%s-You are trying to send mail from an address"
+		    "listed by one\n"
+		    "%s or more IP-based registries as being a SPAM source.\n",
+		    nreply, cp->addr, nreply, nreply);
+	else
+		asprintf(&cp->obuf,
+		    "450 Temporary failure, please try again later.\r\n");
 	if (cp->obuf == NULL) {
 		/* we're having a really bad day.. */
 		cp->obufalloc = 0; /* know not to free or mangle */
 		cp->obuf = "450 Try again\n";
 	} else
 		cp->osize = strlen(cp->obuf) + 1;
-	return matchlists;
 }
 
-char *
+void
 doreply(struct con *cp)
 {
 	if (reply) {
 		if (!cp->obufalloc)
 			errx(1, "shouldn't happen");
 		snprintf(cp->obuf, cp->osize, "%s %s\n", nreply, reply);
-		return("");
 	}
-	return (build_reply(cp));
+	build_reply(cp);
 }
 
 void
@@ -508,12 +532,15 @@
 initcon(struct con *cp, int fd, struct sockaddr_in *sin)
 {
 	time_t t;
+	char *tmp;
 
 	time(&t);
 	if (cp->obufalloc) {
 		free(cp->obuf);
 		cp->obuf = NULL;
 	}
+	if (cp->blacklists)
+		free(cp->blacklists);
 	bzero(cp, sizeof(struct con));
 	if (grow_obuf(cp, 0) == NULL)
 		err(1, "malloc");
@@ -521,13 +548,26 @@
 	memcpy(&cp->sin, sin, sizeof(struct sockaddr_in));
 	cp->af = sin->sin_family;
 	cp->ia = (void *) &cp->sin.sin_addr;
+	cp->blacklists = sdl_lookup(blacklists, cp->af, cp->ia);
+	cp->stutter = (greylist && cp->blacklists == NULL) ? 0 : stutter;
+	if (cp->blacklists != NULL)
+		cp->lists = strdup(loglists(cp));
+	else
+		cp->lists = NULL;
 	strlcpy(cp->addr, inet_ntoa(sin->sin_addr), sizeof(cp->addr));
+	tmp = strdup(ctime(&t));
+	if (tmp == NULL)
+		tmp = "some time";
+	else
+		tmp[strlen(tmp) - 1] = '\0'; /* nuke newline */
 	snprintf(cp->obuf, cp->osize,
-	    "220 %s ESMTP %s; %s",
-	    hostname, spamd, ctime(&t));
+	    "220 %s ESMTP %s; %s\r\n",
+	    hostname, spamd, tmp);
+	if (tmp != NULL)
+		free(tmp);
 	cp->op = cp->obuf;
 	cp->ol = strlen(cp->op);
-	cp->w = t + stutter;
+	cp->w = t + cp->stutter;
 	cp->s = t;
 	strlcpy(cp->rend, "\n", sizeof cp->rend);
 	clients++;
@@ -539,8 +579,10 @@
 	time_t t;
 
 	time(&t);
-	syslog_r(LOG_INFO, &sdata, "%s: disconnected after %ld seconds.",
-	    cp->addr, (long)(t - cp->s));
+	syslog_r(LOG_INFO, &sdata, "%s: disconnected after %ld seconds.%s%s",
+	    cp->addr, (long)(t - cp->s),
+	    ((cp->lists == NULL) ? "" : " lists:"),
+	    ((cp->lists == NULL) ? "": cp->lists));
 	if (debug > 0)
 		printf("%s connected for %ld seconds.\n", cp->addr,
 		    (long)(t - cp->s));
@@ -548,6 +590,10 @@
 		free(cp->lists);
 		cp->lists = NULL;
 	}
+	if (cp->blacklists != NULL) {
+		free(cp->blacklists);
+		cp->blacklists = NULL;
+	}
 	if (cp->osize > 0 && cp->obufalloc) {
 		free(cp->obuf);
 		cp->obuf = NULL;
@@ -582,12 +628,12 @@
 		    match(cp->ibuf, "EHLO")) {
 			snprintf(cp->obuf, cp->osize,
 			    "250 Hello, spam sender. "
-			    "Pleased to be wasting your time.\n");
+			    "Pleased to be wasting your time.\r\n");
 			cp->op = cp->obuf;
 			cp->ol = strlen(cp->op);
 			cp->laststate = cp->state;
 			cp->state = 2;
-			cp->w = t + stutter;
+			cp->w = t + cp->stutter;
 			break;
 		}
 		goto mail;
@@ -605,12 +651,12 @@
 			setlog(cp->mail, sizeof cp->mail, cp->ibuf);
 			snprintf(cp->obuf, cp->osize,
 			    "250 You are about to try to deliver spam. "
-			    "Your time will be spent, for nothing.\n");
+			    "Your time will be spent, for nothing.\r\n");
 			cp->op = cp->obuf;
 			cp->ol = strlen(cp->op);
 			cp->laststate = cp->state;
 			cp->state = 4;
-			cp->w = t + stutter;
+			cp->w = t + cp->stutter;
 			break;
 		}
 		goto rcpt;
@@ -628,15 +674,33 @@
 			setlog(cp->rcpt, sizeof(cp->rcpt), cp->ibuf);
 			snprintf(cp->obuf, cp->osize,
 			    "250 This is hurting you more than it is "
-			    "hurting me.\n");
+			    "hurting me.\r\n");
 			cp->op = cp->obuf;
 			cp->ol = strlen(cp->op);
 			cp->laststate = cp->state;
 			cp->state = 6;
-			cp->w = t + stutter;
-			if (cp->mail[0] && cp->rcpt[0])
-				syslog_r(LOG_INFO, &sdata, "%s: %s -> %s",
+			cp->w = t + cp->stutter;
+			if (cp->mail[0] && cp->rcpt[0]) {
+				if (verbose)
+					syslog_r(LOG_DEBUG, &sdata,
+					    "(%s) %s: %s -> %s",
+					    cp->blacklists ? "BLACK" : "GREY",
+					    cp->addr, cp->mail,
+					    cp->rcpt);
+				if(debug)
+				  fprintf(stderr, "(%s) %s: %s -> %s\n",
+				    cp->blacklists ? "BLACK" : "GREY",
 				    cp->addr, cp->mail, cp->rcpt);
+				if (greylist && cp->blacklists == NULL) {
+					/* send this info to the greylister */
+					fprintf(grey, "IP:%s\nFR:%s\nTO:%s\n",
+					    cp->addr, cp->mail, cp->rcpt);
+					fflush(grey);
+					cp->laststate = cp->state;
+					cp->state = 98;
+					goto done;
+				}
+			}
 			break;
 		}
 		goto spam;
@@ -654,23 +718,23 @@
 		if (match(cp->ibuf, "DATA")) {
 			snprintf(cp->obuf, cp->osize,
 			    "354 Enter spam, end with \".\" on a line by "
-			    "itself\n");
+			    "itself\r\n");
 			cp->state = 60;
 		} else {
 			snprintf(cp->obuf, cp->osize,
-			    "500 5.5.1 Command unrecognized\n");
+			    "500 5.5.1 Command unrecognized\r\n");
 			cp->state = cp->laststate;
 		}
 		cp->ip = cp->ibuf;
 		cp->il = sizeof(cp->ibuf) - 1;
 		cp->op = cp->obuf;
 		cp->ol = strlen(cp->op);
-		cp->w = t + stutter;
+		cp->w = t + cp->stutter;
 		break;
 	case 60:
 		if (!strcmp(cp->ibuf, ".") ||
 		    (cp->data_body && ++cp->data_lines >= 10)) {
-		        cp->laststate = cp->state;
+			cp->laststate = cp->state;
 			cp->state = 98;
 			goto done;
 		}
@@ -679,8 +743,8 @@
 		if (verbose && cp->data_body && *cp->ibuf)
 			syslog_r(LOG_DEBUG, &sdata, "%s: Body: %s", cp->addr,
 			    cp->ibuf);
-		else if (verbose && (match(cp->ibuf, "FROM:") || 
-		     match(cp->ibuf, "TO:") || match(cp->ibuf, "SUBJECT:")))
+		else if (verbose && (match(cp->ibuf, "FROM:") ||
+		    match(cp->ibuf, "TO:") || match(cp->ibuf, "SUBJECT:")))
 			syslog_r(LOG_INFO, &sdata, "%s: %s", cp->addr,
 			    cp->ibuf);
 		cp->ip = cp->ibuf;
@@ -689,13 +753,10 @@
 		break;
 	done:
 	case 98:
-		cp->lists = strdup(doreply(cp));
-		if (cp->lists != NULL)
-			syslog_r(LOG_INFO, &sdata, "%s: matched lists: %s",
-			    cp->addr, cp->lists);
+		doreply(cp);
 		cp->op = cp->obuf;
 		cp->ol = strlen(cp->op);
-		cp->w = t + stutter;
+		cp->w = t + cp->stutter;
 		cp->laststate = cp->state;
 		cp->state = 99;
 		break;
@@ -716,9 +777,9 @@
 
 	if (cp->r) {
 		n = read(cp->fd, cp->ip, cp->il);
-		if (n == 0) {
+		if (n == 0)
 			closecon(cp);
-		} else if (n == -1) {
+		else if (n == -1) {
 			if (debug > 0)
 				perror("read()");
 			closecon(cp);
@@ -750,7 +811,7 @@
 	int n;
 
 	if (cp->w) {
-		if (*cp->op == '\n') {
+		if (*cp->op == '\n' && !cp->sr) {
 			/* insert \r before \n */
 			n = write(cp->fd, "\r", 1);
 			if (n == 0) {
@@ -763,10 +824,14 @@
 				goto handled;
 			}
 		}
-		n = write(cp->fd, cp->op, one ? 1 : cp->ol);
-		if (n == 0) {
+		if (*cp->op == '\r')
+			cp->sr = 1;
+		else
+			cp->sr = 0;
+		n = write(cp->fd, cp->op, (one && cp->stutter) ? 1 : cp->ol);
+		if (n == 0)
 			closecon(cp);
-		} else if (n == -1) {
+		else if (n == -1) {
 			if (debug > 0 && errno != EPIPE)
 				perror("write()");
 			closecon(cp);
@@ -776,7 +841,7 @@
 		}
 	}
 handled:
-	cp->w = t + stutter;
+	cp->w = t + cp->stutter;
 	if (cp->ol == 0) {
 		cp->w = 0;
 		nextstate(cp);
@@ -789,12 +854,12 @@
 	fd_set *fdsr = NULL, *fdsw = NULL;
 	struct sockaddr_in sin;
 	struct sockaddr_in lin;
-	struct passwd *pw;
-	int ch, s, s2, conflisten = 0, i, omax = 0;
-	int sinlen, one = 1;
+	int ch, s, s2, conflisten = 0, i, omax = 0, one = 1;
+	socklen_t sinlen;
 	u_short port, cfg_port;
 	struct servent *ent;
 	struct rlimit rlp;
+	pid_t pid;
 
 	tzset();
 	openlog_r("spamd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata);
@@ -809,7 +874,7 @@
 	if (gethostname(hostname, sizeof hostname) == -1)
 		err(1, "gethostname");
 
-	while ((ch = getopt(argc, argv, "45c:p:dr:s:n:vw:")) != -1) {
+	while ((ch = getopt(argc, argv, "45c:p:dgG:r:s:n:vw:")) != -1) {
 		switch (ch) {
 		case '4':
 			nreply = "450";
@@ -830,6 +895,20 @@
 		case 'd':
 			debug = 1;
 			break;
+		case 'g':
+			greylist = 1;
+			break;
+		case 'G':
+			if (sscanf(optarg, "%d:%d:%d", &passtime, &greyexp,
+			    &whiteexp) != 3)
+				usage();
+			/* convert to seconds from minutes */
+			passtime *= 60;
+			/* convert to seconds from hours */
+			whiteexp *= (60 * 60);
+			/* convert to seconds from hours */
+			greyexp *= (60 * 60);
+			break;
 		case 'r':
 			reply = optarg;
 			break;
@@ -855,7 +934,7 @@
 		}
 	}
 
-	rlp.rlim_cur = rlp.rlim_max = maxcon + 7;
+	rlp.rlim_cur = rlp.rlim_max = maxcon + 15;
 	if (setrlimit(RLIMIT_NOFILE, &rlp) == -1)
 		err(1, "setrlimit");
 
@@ -918,6 +997,31 @@
 	if (!pw)
 		pw = getpwnam("nobody");
 
+	if (greylist) {
+		/* open pipe to talk to greylister */
+		if (pipe(greypipe) == -1)
+			err(1, "pipe");
+
+		pid = fork();
+		if (pid == -1)
+			err(1, "fork");
+		if (pid != 0) {
+			/* parent - run greylister */
+			close(greypipe[1]);
+			grey = fdopen(greypipe[0], "r");
+			if (grey == NULL)
+				err(1, "fdopen");
+			return(greywatcher());
+			/* NOTREACHED */
+		} else {
+			/* child - continue */
+			close(greypipe[0]);
+			grey = fdopen(greypipe[1], "w");
+			if (grey == NULL)
+				err(1, "fdopen");
+		}
+	}
+
 	if (chroot("/var/empty") == -1 || chdir("/") == -1) {
 		syslog(LOG_ERR, "cannot chdir to /var/empty.");
 		exit(1);
@@ -1039,15 +1143,20 @@
 				close(s2);
 			else {
 				initcon(&con[i], s2, &sin);
-				syslog_r(LOG_INFO, &sdata, "%s: connected (%d)",
-				    con[i].addr, clients);
+				syslog_r(LOG_INFO, &sdata,
+				    "%s: connected (%d)%s%s",
+				    con[i].addr, clients,
+				    ((con[i].lists == NULL) ? "" :
+				    ", lists:"),
+				    ((con[i].lists == NULL) ? "":
+				    con[i].lists));
 			}
 		}
 		if (FD_ISSET(conflisten, fdsr)) {
 			sinlen = sizeof(lin);
 			conffd = accept(conflisten, (struct sockaddr *)&lin,
 			    &sinlen);
-			if (conffd == -1) 
+			if (conffd == -1)
 				/* accept failed, they may try again */
 				continue;
 			else if (ntohs(lin.sin_port) >= IPPORT_RESERVED) {
Index: libexec/spamlogd/Makefile
===================================================================
RCS file: libexec/spamlogd/Makefile
diff -N libexec/spamlogd/Makefile
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libexec/spamlogd/Makefile	22 Feb 2004 07:16:06 -0000
@@ -0,0 +1,9 @@
+#	$OpenBSD: Makefile,v 1.6 2003/07/02 22:44:11 deraadt Exp $
+
+PROG=	spamlogd
+SRCS=	spamlogd.c
+MAN=	spamlogd.8
+
+CFLAGS+= -Wall -Wstrict-prototypes -ansi -I ${.CURDIR}/../spamd
+
+.include <bsd.prog.mk>
Index: libexec/spamlogd/spamlogd.8
===================================================================
RCS file: libexec/spamlogd/spamlogd.8
diff -N libexec/spamlogd/spamlogd.8
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libexec/spamlogd/spamlogd.8	22 Feb 2004 07:12:22 -0000
@@ -0,0 +1,91 @@
+.\"	$OpenBSD$
+.\"
+.\" Copyright (c) 2004 Bob Beck.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd Feb 16, 2004
+.Dt SPAMLOGD 8
+.Os
+.Sh NAME
+.Nm spamlogd
+.Nd spamd whitelist updating daemon
+.Sh SYNOPSIS
+.Nm spamlogd
+.Bk -words
+.Op Fl i Ar interface
+.Op Fl I Ar interface
+.Ek
+.Sh DESCRIPTION
+.Nm
+manipulates the 
+.Xr spamd 8
+database in
+.Pa /var/db/spamd
+used for
+.Xr spamd 8
+greylisting.
+.Nm
+updates the
+.Pa /var/db/spamd
+whitelist entries whenever a connection
+to port 25 is logged to the 
+.Xr pflog 4
+interface.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl i Ar interface
+specify a pflog interface to listen on, defaults to 
+.Pa pflog0
+.It Fl I Ar interface
+specify a network interface on which packets must arrive. The default
+is to watch for connections logged from any interfaces. 
+.El
+
+It is important to be sure to log any connections to your real 
+MTA in order for
+.Nm
+to update the whitelist entries. An example
+.Xr pf.conf 8
+configuration for logging such connections is as follows:
+.Bd -literal -offset 4n
+$EXT_IF = "fxp0"
+$MAILHOSTS = "{129.128.11.10, 129.128.11.43}"
+pass in log on $EXT_IF inet proto tcp from any to $MAILHOSTS \\
+	port smtp keep state
+.Ed
+.Sh FILES
+/var/db/spamd
+.Sh SEE ALSO
+.Xr pflog 4 ,
+.Xr pflogd 8 ,
+.Xr spamd 8 ,
+.Xr spamdb 8 ,
+.Xr spamd.conf 5 ,
+.Xr spamd-setup 8 ,
+.Xr tcpdump 8
+.Sh HISTORY
+The
+.Nm
+command
+appeared in
+.Ox 3.5 .
Index: libexec/spamlogd/spamlogd.c
===================================================================
RCS file: libexec/spamlogd/spamlogd.c
diff -N libexec/spamlogd/spamlogd.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libexec/spamlogd/spamlogd.c	22 Feb 2004 07:29:29 -0000
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2004 Bob Beck.  All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* watch pf log for mail connections, update whitelist entries. */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/fcntl.h>
+#include <sys/wait.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+#include <db.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "grey.h"
+#define PATH_TCPDUMP "/usr/sbin/tcpdump"
+
+extern struct passwd *pw;
+extern FILE * grey;
+extern int debug;
+
+size_t whitecount, whitealloc;
+char **whitelist;
+int pfdev;
+
+DB		*db;
+DBT		dbk, dbd;
+BTREEINFO	btreeinfo;
+
+
+/* borrowed from dhartmei.. */
+static int
+address_valid_v4(const char *a)
+{
+	if (!*a)
+		return (0);
+	while (*a)
+		if ((*a >= '0' && *a <= '9') || *a == '.')
+			a++;
+		else
+			return (0);
+	return (1);
+}
+
+int
+dbupdate(char *dbname, char *ip)
+{
+	struct gdata gd;
+	time_t now;
+	int r;
+
+	now = time(NULL);
+	memset(&btreeinfo, 0, sizeof(btreeinfo));
+	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_BTREE, &btreeinfo);
+	if (db == NULL)
+		return(-1);
+	if (!address_valid_v4(ip)) {
+		warnx("invalid ip address %s\n", ip);
+		goto bad;
+	}
+	memset(&dbk, 0, sizeof(dbk));
+	dbk.size = strlen(ip);
+	dbk.data = ip;
+	memset(&dbd, 0, sizeof(dbd));
+	/* add or update whitelist entry */
+	r = db->get(db, &dbk, &dbd, 0);
+	if (r == -1) {
+		warnx("db->get failed (%s)",
+		    strerror(errno));
+		goto bad;
+	}
+	if (r) {
+		/* new entry */
+		memset(&gd, 0, sizeof(gd));
+		gd.first = now;
+		gd.bcount = 1;
+		gd.pass = now;
+		gd.expire = now + WHITEEXP;
+		memset(&dbk, 0, sizeof(dbk));
+		dbk.size = strlen(ip);
+		dbk.data = ip;
+		memset(&dbd, 0, sizeof(dbd));
+		dbd.size = sizeof(gd);
+		dbd.data = &gd;
+		r = db->put(db, &dbk, &dbd, 0);
+		if (r) {
+			warnx("db->put failed (%s)",
+			    strerror(errno));
+			goto bad;
+		}
+	} else {
+		if (dbd.size != sizeof(gd)) {
+			/* whatever this is, it doesn't belong */
+			db->del(db, &dbk, 0);
+			goto bad;
+		}
+		memcpy(&gd, dbd.data, sizeof(gd));
+		gd.pcount++;
+		gd.expire = now + WHITEEXP;
+		memset(&dbk, 0, sizeof(dbk));
+		dbk.size = strlen(ip);
+		dbk.data = ip;
+		memset(&dbd, 0, sizeof(dbd));
+		dbd.size = sizeof(gd);
+		dbd.data = &gd;
+		r = db->put(db, &dbk, &dbd, 0);
+		if (r) {
+			warnx("db->put failed (%s)",
+			    strerror(errno));
+			goto bad;
+		}
+	}
+	db->sync(db, 0);
+	db->close(db);
+	return (0);
+ bad:
+	db->sync(db, 0);
+	db->close(db);
+	return(-1);
+}
+
+static int
+usage(void)
+{
+	fprintf(stderr, "usage: spamlogd [-i pflogif] [-I netif]\n");
+	exit(-1);
+}
+
+char *targv[18] = {
+	"tcpdump", "-l",  "-n", "-e", "-i", "pflog0", "-q",
+	"-t", "inbound", "and", "port", "25", "and", "action", "pass",
+	NULL, NULL, NULL
+};
+
+int
+main(int argc, char **argv)
+{
+	int ch, p[2];
+	char *buf, *lbuf;
+	size_t len;
+	pid_t pid;
+	FILE *f;
+
+	while ((ch = getopt(argc, argv, "i:I:")) != -1) {
+		switch (ch) {
+		case 'i':
+			targv[5] = optarg;
+			break;
+		case 'I':
+			targv[15] = "on";
+			targv[16] = optarg;
+			break;
+		default:
+			usage();
+			break;
+		}
+	}
+
+	if (daemon(1, 1) == -1)
+		err(1, "fork");
+	if (pipe(p) == -1)
+		err(1, "pipe");
+	switch (pid = fork()) {
+	case -1:
+		err(1, "fork");
+	case 0:
+		/* child */
+		close(p[0]);
+		close(STDERR_FILENO);
+		if (dup2(p[1], STDOUT_FILENO) == -1)
+			err(1, "dup2");
+		close(p[1]);
+		execvp(PATH_TCPDUMP, targv);
+		_exit(1);
+	}
+
+	/* parent */
+	f = fdopen(p[0], "r");
+	if (f == NULL)
+		err(1, "fdopen");
+	lbuf = NULL;
+	while ((buf = fgetln(f, &len))) {
+		char *cp;
+
+		if (buf[len - 1] == '\n')
+			buf[len - 1] = '\0';
+		else {
+			if ((lbuf = (char *)malloc(len + 1)) == NULL)
+				err(1, NULL);
+			memcpy(lbuf, buf, len);
+			lbuf[len] = '\0';
+			buf = lbuf;
+		}
+		if ((cp = (strchr(buf, '>'))) != NULL) {
+			/* XXX replace this grot with an sscanf */
+			while (*cp != '.' && cp >= buf) {
+				*cp = '\0';
+				cp--;
+			}
+			*cp ='\0';
+			while (*cp != ' ' && cp >= buf)
+				cp--;
+			cp++;
+			dbupdate(PATH_SPAMD_DB, cp);
+		}
+		if (lbuf != NULL) {
+			free(lbuf);
+			lbuf = NULL;
+		}
+	}
+	return(0);
+}
Index: usr.sbin/spamdb/Makefile
===================================================================
RCS file: usr.sbin/spamdb/Makefile
diff -N usr.sbin/spamdb/Makefile
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ usr.sbin/spamdb/Makefile	22 Feb 2004 07:18:05 -0000
@@ -0,0 +1,9 @@
+#	$OpenBSD: Makefile,v 1.6 2003/07/02 22:44:11 deraadt Exp $
+
+PROG=	spamdb
+SRCS=	spamdb.c
+MAN=	spamdb.8
+
+CFLAGS+= -Wall -Wstrict-prototypes -ansi -I ${.CURDIR}/../../libexec/spamd
+
+.include <bsd.prog.mk>
Index: usr.sbin/spamdb/spamdb.8
===================================================================
RCS file: usr.sbin/spamdb/spamdb.8
diff -N usr.sbin/spamdb/spamdb.8
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ usr.sbin/spamdb/spamdb.8	22 Feb 2004 07:17:17 -0000
@@ -0,0 +1,68 @@
+.\"	$OpenBSD$
+.\"
+.\" Copyright (c) 2004 Bob Beck.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd Feb 16, 2004
+.Dt SPAMDB 8
+.Os
+.Sh NAME
+.Nm spamdb
+.Nd spamd database tool
+.Sh SYNOPSIS
+.Nm spamdb
+.Bk -words
+.Op Fl a Ar ip
+.Op Fl d Ar ip
+.Ek
+.Sh DESCRIPTION
+.Nm
+manipulates the spamd database in
+.Pa /var/db/spamd
+used for
+.Xr spamd 8
+greylisting.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a Ar ip
+add or update a whitelist entry for ip "ip". updates time last seen
+to now.
+.It Fl d Ar ip
+delete a whitelist entry for ip "ip".
+.El
+If invoked without any arguments,
+.Nm
+lists the contents of the database
+in a text format.
+.Sh FILES
+/var/db/spamd
+.Sh SEE ALSO
+.Xr spamd 8 ,
+.Xr spamd.conf 5 ,
+.Xr spamd-setup 8 ,
+.Sh HISTORY
+The
+.Nm
+command
+appeared in
+.Ox 3.5 .
Index: usr.sbin/spamdb/spamdb.c
===================================================================
RCS file: usr.sbin/spamdb/spamdb.c
diff -N usr.sbin/spamdb/spamdb.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ usr.sbin/spamdb/spamdb.c	22 Feb 2004 07:26:00 -0000
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2004 Bob Beck.  All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/fcntl.h>
+#include <sys/wait.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+#include <db.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "grey.h"
+#define PATH_SPAMD_DB "/var/db/spamd"
+
+extern struct passwd *pw;
+extern FILE * grey;
+extern int debug;
+
+size_t whitecount, whitealloc;
+char **whitelist;
+int pfdev;
+
+DB		*db;
+DBT		dbk, dbd;
+BTREEINFO	btreeinfo;
+
+/* borrowed from dhartmei.. */
+static int
+address_valid_v4(const char *a)
+{
+	if (!*a)
+		return (0);
+	while (*a)
+		if ((*a >= '0' && *a <= '9') || *a == '.')
+			a++;
+		else
+			return (0);
+	return (1);
+}
+
+int
+dbupdate(char *dbname, char *ip, int add)
+{
+	struct gdata gd;
+	time_t now;
+	int r;
+
+	now = time(NULL);
+	memset(&btreeinfo, 0, sizeof(btreeinfo));
+	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_BTREE, &btreeinfo);
+	if (db == NULL)
+		return(-1);
+	if (!address_valid_v4(ip)) {
+		warnx("invalid ip address %s\n", ip);
+		goto bad;
+	}
+	memset(&dbk, 0, sizeof(dbk));
+	dbk.size = strlen(ip);
+	dbk.data = ip;
+	memset(&dbd, 0, sizeof(dbd));
+	if (!add) {
+		/* remove whitelist entry */
+		r = db->get(db, &dbk, &dbd, 0);
+		if (r == -1) {
+			warnx("db->get failed (%s)",
+			    strerror(errno));
+			goto bad;
+		}
+		if (r) {
+			warnx("no entry for %s", ip);
+			goto bad;
+		} else if (db->del(db, &dbk, 0)) {
+			warnx("db->del failed (%s)",
+			    strerror(errno));
+			goto bad;
+		}
+	} else {
+		/* add or update whitelist entry */
+		r = db->get(db, &dbk, &dbd, 0);
+		if (r == -1) {
+			warnx("db->get failed (%s)",
+			    strerror(errno));
+			goto bad;
+		}
+		if (r) {
+			/* new entry */
+			memset(&gd, 0, sizeof(gd));
+			gd.first = now;
+			gd.bcount = 1;
+			gd.pass = now;
+			gd.expire = now + WHITEEXP;
+			memset(&dbk, 0, sizeof(dbk));
+			dbk.size = strlen(ip);
+			dbk.data = ip;
+			memset(&dbd, 0, sizeof(dbd));
+			dbd.size = sizeof(gd);
+			dbd.data = &gd;
+			r = db->put(db, &dbk, &dbd, 0);
+			if (r) {
+				warnx("db->put failed (%s)",
+				    strerror(errno));
+				goto bad;
+			}
+		} else {
+			if (dbd.size != sizeof(gd)) {
+				/* whatever this is, it doesn't belong */
+				db->del(db, &dbk, 0);
+				goto bad;
+			}
+			memcpy(&gd, dbd.data, sizeof(gd));
+			gd.pcount++;
+			gd.expire = now + WHITEEXP;
+			memset(&dbk, 0, sizeof(dbk));
+			dbk.size = strlen(ip);
+			dbk.data = ip;
+			memset(&dbd, 0, sizeof(dbd));
+			dbd.size = sizeof(gd);
+			dbd.data = &gd;
+			r = db->put(db, &dbk, &dbd, 0);
+			if (r) {
+				warnx("db->put failed (%s)",
+				    strerror(errno));
+				goto bad;
+			}
+		}
+	}
+	db->sync(db, 0);
+	db->close(db);
+	return (0);
+ bad:
+	db->sync(db, 0);
+	db->close(db);
+	return(-1);
+}
+
+
+
+int
+dblist(char *dbname)
+{
+	struct gdata gd;
+	int r;
+
+	/* walk db, list in text format */
+	memset(&btreeinfo, 0, sizeof(btreeinfo));
+	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_BTREE, &btreeinfo);
+	if (db == NULL)
+		err(1, "dbopen");
+	memset(&dbk, 0, sizeof(dbk));
+	memset(&dbd, 0, sizeof(dbd));
+	for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
+	    r = db->seq(db, &dbk, &dbd, R_NEXT)) {
+		char *a, *cp;
+
+		if ((dbk.size < 1) || dbd.size != sizeof(struct gdata)) {
+			db->close(db);
+			errx(1, "bogus size db entry - bad db file?");
+			return(-1);
+		}
+		memcpy(&gd, dbd.data, sizeof(gd));
+		a = calloc(dbk.size + 1, sizeof(char));
+		memcpy(a, dbk.data, dbk.size);
+		cp = strchr(a, '\n');
+		if (cp == NULL)
+			/* this is a whitelist entry */
+			printf("WHITE:%s:%d:%d:%d:%d:%d\n", a, gd.first,
+			    gd.pass, gd.expire, gd.bcount, gd.pcount);
+		else {
+			char *from, *to;
+
+			/* greylist entry */
+			*cp = '\0';
+			from = cp + 1;
+			to = strchr(from, '\n');
+			if (to == NULL) {
+				warnx("No from part in grey key %s", a);
+				goto bad;
+			}
+			*to = '\0';
+			to++;
+			printf("GREY:%s:%s:%s:%d:%d:%d:%d:%d\n",
+			    a, from, to, gd.first, gd.pass, gd.expire,
+			    gd.bcount, gd.pcount);
+		}
+	}
+	db->sync(db, 0);
+	db->close(db);
+	return(0);
+ bad:
+	db->sync(db, 0);
+	db->close(db);
+	errx(1, "incorrect db format entry");
+	/* NOTREACHED */
+	return(-1);
+}
+
+
+static int
+usage(void)
+{
+	fprintf(stderr, "usage: spamdb [-a ip] [-d ip]\n");
+	exit(-1);
+}
+
+int
+main(int argc, char **argv)
+{
+	int ch, action = 0;
+	char *ip = NULL;
+
+	while ((ch = getopt(argc, argv, "a:d:")) != -1) {
+		switch (ch) {
+		case 'a':
+			action = 1;
+			ip = optarg;
+			break;
+		case 'd':
+			action = 2;
+			ip = optarg;
+			break;
+		default:
+			usage();
+			break;
+		}
+	}
+
+	switch (action) {
+	case 0:
+		dblist("/var/db/spamd");
+		break;
+	case 1:
+		dbupdate("/var/db/spamd", ip, 1);
+		break;
+	case 2:
+		dbupdate("/var/db/spamd", ip, 0);
+		break;
+	default:
+		errx(-1, "bad action");
+	}
+	return(0);
+}


More information about the Greylist-users mailing list