[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