diff options
author | 2007-03-04 03:19:41 +0000 | |
---|---|---|
committer | 2007-03-04 03:19:41 +0000 | |
commit | 98babcad66c249fea6b5d43e49c6cf1927a97ae9 (patch) | |
tree | 94c6609acec1749d2732e65c243b1be8673095ca | |
parent | Kill a cardbus dependency which breaked kernel compilation e.g. for (diff) | |
download | wireguard-openbsd-98babcad66c249fea6b5d43e49c6cf1927a97ae9.tar.xz wireguard-openbsd-98babcad66c249fea6b5d43e49c6cf1927a97ae9.zip |
Database synchronizaton for spamd/spamlogd
This adds an HMAC protected synchronization protocol for use by spamd and
spamlogd.
- spamd can receive updates from other hosts for GREY, WHITE, and TRAPPED db
entries, and will update the local /var/db/spamd accordingly.
- spamd can send updates when it makes changes to the GREY or TRAPPED
entries in the db to other hosts running spamd. (Note it does not send
WHITE entries because the other spamd will see the GREY changes and have
complete information to make appropritate decisions)
- spamlogd can send updates for WHITE db entries that it performs on the local
db to other hosts running spamd, which will then apply them on remote hosts.
note that while this diff provides synchronization for changes made to the
spamd db by the daemons, it does *not* provide for sychonizing changes
to the spamd db made manually with the spamdb command.
Synchronization protocol and most of the work by reyk@,
with a bunch of the spamd, and spamlogd stuff by me.
testing mostly at the U of A, running happily there under big load.
ok reyk@ jmc@
-rw-r--r-- | etc/services | 3 | ||||
-rw-r--r-- | libexec/spamd/Makefile | 7 | ||||
-rw-r--r-- | libexec/spamd/grey.c | 140 | ||||
-rw-r--r-- | libexec/spamd/grey.h | 3 | ||||
-rw-r--r-- | libexec/spamd/spamd.8 | 71 | ||||
-rw-r--r-- | libexec/spamd/spamd.c | 46 | ||||
-rw-r--r-- | libexec/spamd/sync.c | 571 | ||||
-rw-r--r-- | libexec/spamd/sync.h | 87 | ||||
-rw-r--r-- | libexec/spamlogd/Makefile | 7 | ||||
-rw-r--r-- | libexec/spamlogd/spamlogd.8 | 23 | ||||
-rw-r--r-- | libexec/spamlogd/spamlogd.c | 37 |
11 files changed, 974 insertions, 21 deletions
diff --git a/etc/services b/etc/services index 6f3eff49494..f5335bee688 100644 --- a/etc/services +++ b/etc/services @@ -1,4 +1,4 @@ -# $OpenBSD: services,v 1.64 2006/05/30 19:25:41 deraadt Exp $ +# $OpenBSD: services,v 1.65 2007/03/04 03:19:41 beck Exp $ # # Network services, Internet style # @@ -273,6 +273,7 @@ postgresql 5432/tcp # PostgreSQL canna 5680/tcp # Kana->Kanji server icb 7326/tcp # Internet Citizen's Band spamd 8025/tcp # spamd(8) +spamd-sync 8025/udp # spamd(8) synchronization spamd-cfg 8026/tcp # spamd(8) configuration hunt 26740/udp # hunt(6) # diff --git a/libexec/spamd/Makefile b/libexec/spamd/Makefile index 0c77d87d6f5..0ac6f1923dc 100644 --- a/libexec/spamd/Makefile +++ b/libexec/spamd/Makefile @@ -1,9 +1,12 @@ -# $OpenBSD: Makefile,v 1.8 2005/05/24 22:23:04 millert Exp $ +# $OpenBSD: Makefile,v 1.9 2007/03/04 03:19:41 beck Exp $ PROG= spamd -SRCS= spamd.c sdl.c grey.c +SRCS= spamd.c sdl.c grey.c sync.c MAN= spamd.8 CFLAGS+= -Wall -Wstrict-prototypes +LDADD+= -lcrypto +DPADD+= ${LIBCRYPTO} + .include <bsd.prog.mk> diff --git a/libexec/spamd/grey.c b/libexec/spamd/grey.c index 34e79671950..89623125b62 100644 --- a/libexec/spamd/grey.c +++ b/libexec/spamd/grey.c @@ -1,4 +1,4 @@ -/* $OpenBSD: grey.c,v 1.29 2007/02/23 22:40:50 beck Exp $ */ +/* $OpenBSD: grey.c,v 1.30 2007/03/04 03:19:41 beck Exp $ */ /* * Copyright (c) 2004-2006 Bob Beck. All rights reserved. @@ -41,6 +41,7 @@ #include <netdb.h> #include "grey.h" +#include "sync.h" extern time_t passtime, greyexp, whiteexp, trapexp; extern struct syslog_data sdata; @@ -50,6 +51,7 @@ extern pid_t jail_pid; extern FILE * trapcfg; extern FILE * grey; extern int debug; +extern int syncsend; size_t whitecount, whitealloc; size_t trapcount, trapalloc; @@ -580,7 +582,98 @@ trapcheck(DB *db, char *to) } int -greyupdate(char *dbname, char *helo, char *ip, char *from, char *to) +twupdate(char *dbname, char *what, char *ip, char *source, char *expires) { + /* we got a TRAP or WHITE update from someone else */ + HASHINFO hashinfo; + DBT dbk, dbd; + DB *db; + struct gdata gd; + time_t now, expire; + int r, spamtrap; + + now = time(NULL); + /* expiry times have to be in the future */ + expire = strtonum(expires, now, UINT_MAX, NULL); + if (expire == 0) + return(-1); + + if (strcmp(what, "TRAP") == 0) + spamtrap = 1; + else if (strcmp(what, "WHITE") == 0) + spamtrap = 0; + else + return(-1); + + memset(&hashinfo, 0, sizeof(hashinfo)); + db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo); + if (db == NULL) + return(-1); + + memset(&dbk, 0, sizeof(dbk)); + dbk.size = strlen(ip); + dbk.data = ip; + memset(&dbd, 0, sizeof(dbd)); + r = db->get(db, &dbk, &dbd, 0); + if (r == -1) + goto bad; + if (r) { + /* new entry */ + memset(&gd, 0, sizeof(gd)); + gd.first = now; + gd.pcount = spamtrap ? -1 : 0; + gd.expire = expire; + 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); + db->sync(db, 0); + if (r) + goto bad; + if (debug) + fprintf(stderr, "added %s %s\n", + spamtrap ? "trap entry for" : "", ip); + } else { + /* existing entry */ + if (dbd.size != sizeof(gd)) { + /* whatever this is, it doesn't belong */ + db->del(db, &dbk, 0); + db->sync(db, 0); + goto bad; + } + memcpy(&gd, dbd.data, sizeof(gd)); + if (spamtrap) { + gd.pcount = -1; + gd.bcount++; + } else + gd.pcount++; + 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); + db->sync(db, 0); + if (r) + goto bad; + if (debug) + fprintf(stderr, "updated %s\n", ip); + } + db->close(db); + syslog_r(LOG_DEBUG, &sdata, "Update from %s for %s %s, expires %s", + source, what, ip, expires); + return(0); + bad: + db->close(db); + return(-1); + +} + +int +greyupdate(char *dbname, char *helo, char *ip, char *from, char *to, int sync) { HASHINFO hashinfo; DBT dbk, dbd; @@ -676,6 +769,14 @@ greyupdate(char *dbname, char *helo, char *ip, char *from, char *to) key = NULL; db->close(db); db = NULL; + + /* Entry successfully update, sent out sync message */ + if (syncsend && sync) { + if (spamtrap) + sync_trapped(now, now + expire, ip); + else + sync_update(now, helo, ip, from, to); + } return(0); bad: free(key); @@ -686,11 +787,32 @@ greyupdate(char *dbname, char *helo, char *ip, char *from, char *to) } int +twread(char * buf) { + if ((strncmp(buf, "WHITE:", 6) == 0) || + (strncmp(buf, "TRAP:", 5) == 0)) { + char **ap, *argv[5]; + int argc = 0; + for (ap = argv; ap < &argv[4] && + (*ap = strsep(&buf, ":")) != NULL;) { + if (**ap != '\0') + ap++; + argc++; + } + *ap = NULL; + if (argc != 4) + return(-1); + twupdate(PATH_SPAMD_DB, argv[0], argv[1], argv[2], argv[3]); + return 0; + } else + return -1; +} + +int greyreader(void) { char ip[32], helo[MAX_MAIL], from[MAX_MAIL], to[MAX_MAIL], *buf; size_t len; - int state; + int state, sync; struct addrinfo hints, *res; memset(&hints, 0, sizeof(hints)); @@ -700,6 +822,7 @@ greyreader(void) hints.ai_flags = AI_NUMERICHOST; state = 0; + sync = 1; if (grey == NULL) { syslog_r(LOG_ERR, &sdata, "No greylist pipe stream!\n"); exit(1); @@ -717,8 +840,17 @@ greyreader(void) if (strlen(buf) < 4) continue; + if (strcmp(buf, "SYNC") == 0) { + sync = 0; + continue; + } + switch (state) { case 0: + if (twread(buf) == 0) { + state = 0; + break; + } if (strncmp(buf, "HE:", 3) != 0) { state = 0; break; @@ -754,7 +886,7 @@ greyreader(void) fprintf(stderr, "Got Grey HELO %s, IP %s from %s to %s\n", helo, ip, from, to); - greyupdate(PATH_SPAMD_DB, helo, ip, from, to); + greyupdate(PATH_SPAMD_DB, helo, ip, from, to, sync); state = 0; break; } diff --git a/libexec/spamd/grey.h b/libexec/spamd/grey.h index 5567d86ab60..b355818ac07 100644 --- a/libexec/spamd/grey.h +++ b/libexec/spamd/grey.h @@ -1,4 +1,4 @@ -/* $OpenBSD: grey.h,v 1.7 2007/02/27 02:10:58 beck Exp $ */ +/* $OpenBSD: grey.h,v 1.8 2007/03/04 03:19:41 beck Exp $ */ /* * Copyright (c) 2004 Bob Beck. All rights reserved. @@ -35,3 +35,4 @@ struct gdata { }; extern int greywatcher(void); +extern int greyupdate(char *, char *, char *, char *, char *, int); diff --git a/libexec/spamd/spamd.8 b/libexec/spamd/spamd.8 index 4203c1c30c3..ae74ac2d593 100644 --- a/libexec/spamd/spamd.8 +++ b/libexec/spamd/spamd.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: spamd.8,v 1.84 2007/03/02 08:14:59 jmc Exp $ +.\" $OpenBSD: spamd.8,v 1.85 2007/03/04 03:19:41 beck Exp $ .\" .\" Copyright (c) 2002 Theo de Raadt. All rights reserved. .\" @@ -46,6 +46,8 @@ .Op Fl S Ar secs .Op Fl s Ar secs .Op Fl w Ar window +.Op Fl Y Ar synctarget +.Op Fl y Ar synclisten .Ek .Sh DESCRIPTION .Nm @@ -187,6 +189,17 @@ along with the message body and SMTP dialogue being logged at level. .It Fl w Ar window Set the socket receive buffer to this many bytes, adjusting the window size. +.It Fl Y Ar synctarget +Add a target to receive synchronisation messages; see +.Sx SYNCHRONISATION +below. +This option can be specified multiple times. +.It Fl y Ar synclisten +Listen for incoming synchronisation messages on the specified address +or interface; see +.Sx SYNCHRONISATION +below. +This option can be specified only once. .El .Pp When run in default mode, @@ -443,10 +456,66 @@ section can be used to log connection details to a dedicated file: !spamd daemon.err;daemon.warn;daemon.info /var/log/spamd .Ed +.Sh SYNCHRONISATION +.Nm +supports realtime synchronisation of greylisting states between +a number of +.Nm +daemons running on multiple machines. +To enable synchronisation, use the command line options +.Fl y +and +.Fl Y . +.Pp +If the +.Fl y +option is specified, +.Nm +will listen for incoming synchronisation messages to synchronize +the local +.Pa /var/db/spamd +database. +The +.Fl Y +option will add a target for outgoing messages. +A target can be either an IPv4 address for unicast messages or a +network interface and optional TTL value for multicast messages +to the group 224.0.1.240. +If the multicast TTL is not specified, a default value of 1 is used. +.Pp +The following example will accept incoming multicast and unicast +synchronisation messages, and send outgoing multicast messages through +the network interface +.Ar em0 : +.Bd -literal -offset indent +# /usr/libexec/spamd -y em0 -Y em0 +.Ed +.Pp +The second example will increase the multicast TTL to a value of 2, +add the unicast targets +.Ar foo.somewhere.org +and +.Ar bar.somewhere.org , +and accept incoming unicast messages from +.Ar foo.somewhere.org +only. +.Bd -literal -offset indent +# /usr/libexec/spamd -y foo.somewhere.org -Y em0:2 \e + -Y foo.somewhere.org -Y bar.somewhere.org +.Ed +.Pp +If the file +.Pa /etc/spamd.key +exists, +.Nm +will calculate the message-digest fingerprint (checksum) for the file +and use it as a shared key to authenticate the synchronisation messages. .Sh FILES .Bl -tag -width "/etc/mail/spamd.alloweddomainsXX" -compact .It /etc/mail/spamd.conf Default configuration file. +.It /etc/mail/spamd.key +Authentication key for synchronisation messages. .It /etc/mail/spamd.alloweddomains Optional required suffixes for greytrapping. .It /var/db/spamd diff --git a/libexec/spamd/spamd.c b/libexec/spamd/spamd.c index 846298e6eb4..075f63a2c49 100644 --- a/libexec/spamd/spamd.c +++ b/libexec/spamd/spamd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: spamd.c,v 1.92 2007/02/27 23:03:09 deraadt Exp $ */ +/* $OpenBSD: spamd.c,v 1.93 2007/03/04 03:19:41 beck Exp $ */ /* * Copyright (c) 2002 Theo de Raadt. All rights reserved. @@ -47,6 +47,7 @@ #include "sdl.h" #include "grey.h" +#include "sync.h" struct con { int fd; @@ -115,6 +116,7 @@ time_t trapexp = TRAPEXP; struct passwd *pw; pid_t jail_pid = -1; u_short cfg_port; +u_short sync_port; extern struct sdlist *blacklists; @@ -125,7 +127,7 @@ size_t cbs, cbu; time_t t; -#define MAXCON 800 +#define MAXCON 1800 int maxcon = MAXCON; int maxblack = MAXCON; int blackcount; @@ -136,6 +138,8 @@ int grey_stutter = 10; int verbose; int stutter = 1; int window; +int syncrecv; +int syncsend; #define MAXTIME 400 void @@ -148,7 +152,8 @@ usage(void) "[-G passtime:greyexp:whiteexp]\n" "\t[-h hostname] [-l address] [-n name] [-p port] " "[-r reply] [-S secs]\n" - "\t[-s secs] [-w window]\n", __progname); + "\t[-s secs] [-w window] [-Y synctarget] [-y synclisten]\n", + __progname); exit(1); } @@ -965,13 +970,15 @@ main(int argc, char *argv[]) fd_set *fdsr = NULL, *fdsw = NULL; struct sockaddr_in sin; struct sockaddr_in lin; - int ch, s, s2, conflisten = 0, i, omax = 0, one = 1; + int ch, s, s2, conflisten = 0, syncfd = 0, i, omax = 0, one = 1; socklen_t sinlen; u_short port; struct servent *ent; struct rlimit rlp; char *bind_address = NULL; const char *errstr; + char *sync_iface = NULL; + char *sync_baddr = NULL; tzset(); openlog_r("spamd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata); @@ -982,11 +989,15 @@ main(int argc, char *argv[]) if ((ent = getservbyname("spamd-cfg", "tcp")) == NULL) errx(1, "Can't find service \"spamd-cfg\" in /etc/services"); cfg_port = ntohs(ent->s_port); + if ((ent = getservbyname("spamd-sync", "udp")) == NULL) + errx(1, "Can't find service \"spamd-sync\" in /etc/services"); + sync_port = ntohs(ent->s_port); if (gethostname(hostname, sizeof hostname) == -1) err(1, "gethostname"); - while ((ch = getopt(argc, argv, "45l:c:B:p:bdG:h:r:s:S:n:vw:")) != -1) { + while ((ch = + getopt(argc, argv, "45l:c:B:p:bdG:h:r:s:S:n:vw:y:Y:")) != -1) { switch (ch) { case '4': nreply = "450"; @@ -1060,12 +1071,25 @@ main(int argc, char *argv[]) if (window <= 0) usage(); break; + case 'Y': + if (sync_addhost(optarg, sync_port) != 0) + sync_iface = optarg; + syncsend++; + break; + case 'y': + sync_baddr = optarg; + syncrecv++; + break; default: usage(); break; } } + setproctitle("[priv]%s%s", + greylist ? " (greylist)" : "", + (syncrecv || syncsend) ? " (sync)" : ""); + if (!greylist) maxblack = maxcon; else if (maxblack > maxcon) @@ -1128,6 +1152,12 @@ main(int argc, char *argv[]) if (bind(conflisten, (struct sockaddr *)&lin, sizeof lin) == -1) err(1, "bind local"); + if (syncsend || syncrecv) { + syncfd = sync_init(sync_iface, sync_baddr, sync_port); + if (syncfd == -1) + err(1, "sync init"); + } + pw = getpwnam("_spamd"); if (!pw) pw = getpwnam("nobody"); @@ -1220,6 +1250,8 @@ jail: int writers; max = MAX(s, conflisten); + if (syncrecv) + max = MAX(max, syncfd); max = MAX(max, conffd); max = MAX(max, trapfd); @@ -1277,6 +1309,8 @@ jail: FD_SET(conffd, fdsr); if (trapfd != -1) FD_SET(trapfd, fdsr); + if (syncrecv) + FD_SET(syncfd, fdsr); if (writers == 0) { tvp = NULL; @@ -1339,6 +1373,8 @@ jail: do_config(); if (trapfd != -1 && FD_ISSET(trapfd, fdsr)) read_configline(trapcfg); + if (syncrecv && FD_ISSET(syncfd, fdsr)) + sync_recv(); } exit(1); } diff --git a/libexec/spamd/sync.c b/libexec/spamd/sync.c new file mode 100644 index 00000000000..70d0e6f4d73 --- /dev/null +++ b/libexec/spamd/sync.c @@ -0,0 +1,571 @@ +/* $OpenBSD: sync.c,v 1.1 2007/03/04 03:19:41 beck Exp $ */ + +/* + * Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org> + * + * 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/param.h> +#include <sys/stdint.h> +#include <sys/file.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <sys/resource.h> +#include <sys/uio.h> +#include <sys/ioctl.h> +#include <sys/queue.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <getopt.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sha1.h> +#include <syslog.h> + +#include <netdb.h> + +#include <openssl/hmac.h> + +#include "sdl.h" +#include "grey.h" +#include "sync.h" + +extern struct syslog_data sdata; +extern int debug; +extern FILE *grey; +extern int greylist; + +u_int32_t sync_counter; +int syncfd; +int sendmcast; +struct sockaddr_in sync_in; +struct sockaddr_in sync_out; +static char *sync_key; + +struct sync_host { + LIST_ENTRY(sync_host) h_entry; + + char *h_name; + struct sockaddr_in h_addr; +}; +LIST_HEAD(synchosts, sync_host) sync_hosts = LIST_HEAD_INITIALIZER(sync_hosts); + +void sync_send(struct iovec *, int); +void sync_addr(time_t, time_t, char *, u_int16_t); + +int +sync_addhost(const char *name, u_short port) +{ + struct addrinfo hints, *res, *res0; + struct sync_host *shost; + struct sockaddr_in *addr = NULL; + + bzero(&hints, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo(name, NULL, &hints, &res0) != 0) + return (EINVAL); + for (res = res0; res != NULL; res = res->ai_next) { + if (addr == NULL && res->ai_family == AF_INET) { + addr = (struct sockaddr_in *)res->ai_addr; + break; + } + } + if (addr == NULL) { + freeaddrinfo(res0); + return (EINVAL); + } + if ((shost = (struct sync_host *) + calloc(1, sizeof(struct sync_host))) == NULL) { + freeaddrinfo(res0); + return (ENOMEM); + } + if ((shost->h_name = strdup(name)) == NULL) { + free(shost); + freeaddrinfo(res0); + return (ENOMEM); + } + + shost->h_addr.sin_family = AF_INET; + shost->h_addr.sin_port = htons(port); + shost->h_addr.sin_addr.s_addr = addr->sin_addr.s_addr; + freeaddrinfo(res0); + + LIST_INSERT_HEAD(&sync_hosts, shost, h_entry); + + if (debug) + fprintf(stderr, "added spam sync host %s " + "(address %s, port %d)\n", shost->h_name, + inet_ntoa(shost->h_addr.sin_addr), port); + + return (0); +} + +int +sync_init(const char *iface, const char *baddr, u_short port) +{ + int one = 1; + u_int8_t ttl; + struct ifreq ifr; + struct ip_mreq mreq; + struct sockaddr_in *addr; + char ifnam[IFNAMSIZ], *ttlstr; + const char *errstr; + struct in_addr ina; + + if (iface != NULL) + sendmcast++; + + bzero(&ina, sizeof(ina)); + if (baddr != NULL) { + if (inet_pton(AF_INET, baddr, &ina) != 1) { + ina.s_addr = htonl(INADDR_ANY); + if (iface == NULL) + iface = baddr; + else if (iface != NULL && strcmp(baddr, iface) != 0) { + fprintf(stderr, "multicast interface does " + "not match"); + return (-1); + } + } + } + + sync_key = SHA1File(SPAM_SYNC_KEY, NULL); + if (sync_key == NULL) { + if (errno != ENOENT) { + fprintf(stderr, "failed to open sync key: %s\n", + strerror(errno)); + return (-1); + } + /* Use empty key by default */ + sync_key = ""; + } + + syncfd = socket(AF_INET, SOCK_DGRAM, 0); + if (syncfd == -1) + return (-1); + + if (setsockopt(syncfd, SOL_SOCKET, SO_REUSEADDR, &one, + sizeof(one)) == -1) + goto fail; + + bzero(&sync_out, sizeof(sync_out)); + sync_out.sin_family = AF_INET; + sync_out.sin_len = sizeof(sync_out); + sync_out.sin_addr.s_addr = ina.s_addr; + if (baddr == NULL && iface == NULL) + sync_out.sin_port = 0; + else + sync_out.sin_port = htons(port); + + if (bind(syncfd, (struct sockaddr *)&sync_out, sizeof(sync_out)) == -1) + goto fail; + + /* Don't use multicast messages */ + if (iface == NULL) + return (syncfd); + + strlcpy(ifnam, iface, sizeof(ifnam)); + ttl = SPAM_SYNC_MCASTTTL; + if ((ttlstr = strchr(ifnam, ':')) != NULL) { + *ttlstr++ = '\0'; + ttl = (u_int8_t)strtonum(ttlstr, 1, UINT8_MAX, &errstr); + if (errstr) { + fprintf(stderr, "invalid multicast ttl %s: %s", + ttlstr, errstr); + goto fail; + } + } + + bzero(&ifr, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name)); + if (ioctl(syncfd, SIOCGIFADDR, &ifr) == -1) + goto fail; + + bzero(&sync_in, sizeof(sync_in)); + addr = (struct sockaddr_in *)&ifr.ifr_addr; + sync_in.sin_family = AF_INET; + sync_in.sin_len = sizeof(sync_in); + sync_in.sin_addr.s_addr = addr->sin_addr.s_addr; + sync_in.sin_port = htons(port); + + bzero(&mreq, sizeof(mreq)); + sync_out.sin_addr.s_addr = inet_addr(SPAM_SYNC_MCASTADDR); + mreq.imr_multiaddr.s_addr = inet_addr(SPAM_SYNC_MCASTADDR); + mreq.imr_interface.s_addr = sync_in.sin_addr.s_addr; + + if (setsockopt(syncfd, IPPROTO_IP, + IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) { + fprintf(stderr, "failed to add multicast membership to %s: %s", + SPAM_SYNC_MCASTADDR, strerror(errno)); + goto fail; + } + if (setsockopt(syncfd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, + sizeof(ttl)) < 0) { + fprintf(stderr, "failed to set multicast ttl to " + "%u: %s\n", ttl, strerror(errno)); + setsockopt(syncfd, IPPROTO_IP, + IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); + goto fail; + } + + if (debug) + printf("using multicast spam sync %smode " + "(ttl %u, group %s, port %d)\n", + sendmcast ? "" : "receive ", + ttl, inet_ntoa(sync_out.sin_addr), port); + + return (syncfd); + + fail: + close(syncfd); + return (-1); +} + +void +sync_recv(void) +{ + struct spam_synchdr *hdr; + struct sockaddr_in addr; + struct spam_synctlv_hdr *tlv; + struct spam_synctlv_grey *sg; + struct spam_synctlv_addr *sd; + u_int8_t buf[SPAM_SYNC_MAXSIZE]; + u_int8_t hmac[2][SPAM_SYNC_HMAC_LEN]; + struct in_addr ip; + char *from, *to, *helo; + u_int8_t *p; + socklen_t addr_len; + ssize_t len; + u_int hmac_len; + time_t expire; + + bzero(&addr, sizeof(addr)); + bzero(buf, sizeof(buf)); + + addr_len = sizeof(addr); + if ((len = recvfrom(syncfd, buf, sizeof(buf), 0, + (struct sockaddr *)&addr, &addr_len)) < 1) + return; + if (addr.sin_addr.s_addr != htonl(INADDR_ANY) && + bcmp(&sync_in.sin_addr, &addr.sin_addr, + sizeof(addr.sin_addr)) == 0) + return; + + /* Ignore invalid or truncated packets */ + hdr = (struct spam_synchdr *)buf; + if (hdr->sh_version != SPAM_SYNC_VERSION || + hdr->sh_af != AF_INET || + len < ntohs(hdr->sh_length)) + goto trunc; + len = ntohs(hdr->sh_length); + + /* Compute and validate HMAC */ + bcopy(hdr->sh_hmac, hmac[0], SPAM_SYNC_HMAC_LEN); + bzero(hdr->sh_hmac, SPAM_SYNC_HMAC_LEN); + HMAC(EVP_sha1(), sync_key, strlen(sync_key), buf, len, + hmac[1], &hmac_len); + if (bcmp(hmac[0], hmac[1], SPAM_SYNC_HMAC_LEN) != 0) + goto trunc; + + if (debug) + fprintf(stderr, + "%s(sync): received packet of %d bytes\n", + inet_ntoa(addr.sin_addr), (int)len); + + p = (u_int8_t *)(hdr + 1); + while (len) { + tlv = (struct spam_synctlv_hdr *)p; + + if (len < ntohs(tlv->st_length)) + goto trunc; + + switch (ntohs(tlv->st_type)) { + case SPAM_SYNC_GREY: + sg = (struct spam_synctlv_grey *)tlv; + if ((sizeof(*sg) + + ntohs(sg->sg_from_length) + + ntohs(sg->sg_to_length) + + ntohs(sg->sg_helo_length)) > + ntohs(tlv->st_length)) + goto trunc; + + ip.s_addr = (u_int32_t)ntohl(sg->sg_ip); + from = (char *)(sg + 1); + to = from + ntohs(sg->sg_from_length); + helo = to + ntohs(sg->sg_to_length); + if (debug) { + fprintf(stderr, "%s(sync): " + "received grey entry ", + inet_ntoa(addr.sin_addr)); + fprintf(stderr, "helo %s ip %s " + "from %s to %s\n", + helo, inet_ntoa(ip), from, to); + } + if (greylist) { + /* send this info to the greylister */ + fprintf(grey, + "SYNC\nHE:%s\nIP:%s\nFR:%s\nTO:%s\n", + helo, inet_ntoa(ip), from, to); + fflush(grey); + } + break; + case SPAM_SYNC_WHITE: + sd = (struct spam_synctlv_addr *)tlv; + if (sizeof(*sd) != ntohs(tlv->st_length)) + goto trunc; + + ip.s_addr = (u_int32_t)ntohl(sd->sd_ip); + expire = ntohl(sd->sd_expire); + if (debug) { + fprintf(stderr, "%s(sync): " + "received white entry ", + inet_ntoa(addr.sin_addr)); + fprintf(stderr, "ip %s ", inet_ntoa(ip)); + } + if (greylist) { + /* send this info to the greylister */ + fprintf(grey, "WHITE:%s:", inet_ntoa(ip)); + fprintf(grey, "%s:%u\n", + inet_ntoa(addr.sin_addr), expire); + fflush(grey); + } + break; + case SPAM_SYNC_TRAPPED: + sd = (struct spam_synctlv_addr *)tlv; + if (sizeof(*sd) != ntohs(tlv->st_length)) + goto trunc; + + ip.s_addr = (u_int32_t)ntohl(sd->sd_ip); + expire = ntohl(sd->sd_expire); + if (debug) { + fprintf(stderr, "%s(sync): " + "received trapped entry ", + inet_ntoa(addr.sin_addr)); + fprintf(stderr, "ip %s ", inet_ntoa(ip)); + } + if (greylist) { + /* send this info to the greylister */ + fprintf(grey, "TRAP:%s:", inet_ntoa(ip)); + fprintf(grey, "%s:%u\n", + inet_ntoa(addr.sin_addr), expire); + fflush(grey); + } + break; + case SPAM_SYNC_END: + goto done; + default: + printf("invalid type: %d\n", ntohs(tlv->st_type)); + goto trunc; + } + len -= ntohs(tlv->st_length); + p = ((u_int8_t *)tlv) + ntohs(tlv->st_length); + } + + done: + return; + + trunc: + if (debug) + fprintf(stderr, "%s(sync): truncated or invalid packet\n", + inet_ntoa(addr.sin_addr)); +} + +void +sync_send(struct iovec *iov, int iovlen) +{ + struct sync_host *shost; + struct msghdr msg; + + /* setup buffer */ + bzero(&msg, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = iovlen; + + if (sendmcast) { + if (debug) + fprintf(stderr, "sending multicast sync message\n"); + msg.msg_name = &sync_out; + msg.msg_namelen = sizeof(sync_out); + sendmsg(syncfd, &msg, 0); + } + + LIST_FOREACH(shost, &sync_hosts, h_entry) { + if (debug) + fprintf(stderr, "sending sync message to %s (%s)\n", + shost->h_name, inet_ntoa(shost->h_addr.sin_addr)); + msg.msg_name = &shost->h_addr; + msg.msg_namelen = sizeof(shost->h_addr); + sendmsg(syncfd, &msg, 0); + } +} + +void +sync_update(time_t now, char *helo, char *ip, char *from, char *to) +{ + struct iovec iov[6]; + struct spam_synchdr hdr; + struct spam_synctlv_grey sg; + struct spam_synctlv_hdr end; + u_int16_t fromlen, tolen, helolen; + int i = 0; + HMAC_CTX ctx; + u_int hmac_len; + + if (debug) + fprintf(stderr, + "sync grey update helo %s ip %s from %s to %s\n", + helo, ip, from, to); + + bzero(&hdr, sizeof(hdr)); + bzero(&sg, sizeof(sg)); + + fromlen = strlen(from) + 1; + tolen = strlen(to) + 1; + helolen = strlen(helo) + 1; + + HMAC_CTX_init(&ctx); + HMAC_Init(&ctx, sync_key, strlen(sync_key), EVP_sha1()); + + /* Add SPAM sync packet header */ + hdr.sh_version = SPAM_SYNC_VERSION; + hdr.sh_af = AF_INET; + hdr.sh_counter = sync_counter++; + hdr.sh_length = htons(sizeof(hdr) + + sizeof(sg) + fromlen + tolen + helolen + sizeof(end)); + iov[i].iov_base = &hdr; + iov[i].iov_len = sizeof(hdr); + HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len); + i++; + + /* Add single SPAM sync greylisting entry */ + sg.sg_type = htons(SPAM_SYNC_GREY); + sg.sg_length = htons(sizeof(sg) + fromlen + helolen + tolen); + sg.sg_timestamp = htonl(now); + sg.sg_ip = htonl((u_int32_t)inet_addr(ip)); + sg.sg_from_length = htons(fromlen); + sg.sg_to_length = htons(tolen); + sg.sg_helo_length = htons(helolen); + iov[i].iov_base = &sg; + iov[i].iov_len = sizeof(sg); + HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len); + i++; + + iov[i].iov_base = from; + iov[i].iov_len = fromlen; + HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len); + i++; + + iov[i].iov_base = to; + iov[i].iov_len = tolen; + HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len); + i++; + + iov[i].iov_base = helo; + iov[i].iov_len = helolen; + HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len); + i++; + + /* Add end marker */ + end.st_type = htons(SPAM_SYNC_END); + end.st_length = htons(sizeof(end)); + iov[i].iov_base = &end; + iov[i].iov_len = sizeof(end); + HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len); + i++; + + HMAC_Final(&ctx, hdr.sh_hmac, &hmac_len); + + /* Send message to the target hosts */ + sync_send(iov, i); +} + +void +sync_addr(time_t now, time_t expire, char *ip, u_int16_t type) +{ + struct iovec iov[3]; + struct spam_synchdr hdr; + struct spam_synctlv_addr sd; + struct spam_synctlv_hdr end; + int i = 0; + HMAC_CTX ctx; + u_int hmac_len; + + if (debug) + fprintf(stderr, "sync trapped %s\n", ip); + + bzero(&hdr, sizeof(hdr)); + bzero(&sd, sizeof(sd)); + + HMAC_CTX_init(&ctx); + HMAC_Init(&ctx, sync_key, strlen(sync_key), EVP_sha1()); + + /* Add SPAM sync packet header */ + hdr.sh_version = SPAM_SYNC_VERSION; + hdr.sh_af = AF_INET; + hdr.sh_counter = sync_counter++; + hdr.sh_length = htons(sizeof(hdr) + sizeof(sd) + sizeof(end)); + iov[i].iov_base = &hdr; + iov[i].iov_len = sizeof(hdr); + HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len); + i++; + + /* Add single SPAM sync address entry */ + sd.sd_type = htons(type); + sd.sd_length = htons(sizeof(sd)); + sd.sd_timestamp = htonl(now); + sd.sd_expire = htonl(expire); + sd.sd_ip = htonl((u_int32_t)inet_addr(ip)); + iov[i].iov_base = &sd; + iov[i].iov_len = sizeof(sd); + HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len); + i++; + + /* Add end marker */ + end.st_type = htons(SPAM_SYNC_END); + end.st_length = htons(sizeof(end)); + iov[i].iov_base = &end; + iov[i].iov_len = sizeof(end); + HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len); + i++; + + HMAC_Final(&ctx, hdr.sh_hmac, &hmac_len); + + /* Send message to the target hosts */ + sync_send(iov, i); +} + +void +sync_white(time_t now, time_t expire, char *ip) +{ + if (debug) + fprintf(stderr, "sync white address %s\n", ip); + sync_addr(now, expire, ip, SPAM_SYNC_WHITE); +} + +void +sync_trapped(time_t now, time_t expire, char *ip) +{ + if (debug) + fprintf(stderr, "sync trapped address %s\n", ip); + sync_addr(now, expire, ip, SPAM_SYNC_TRAPPED); +} diff --git a/libexec/spamd/sync.h b/libexec/spamd/sync.h new file mode 100644 index 00000000000..5c952561dc1 --- /dev/null +++ b/libexec/spamd/sync.h @@ -0,0 +1,87 @@ +/* $OpenBSD: sync.h,v 1.1 2007/03/04 03:19:41 beck Exp $ */ + +/* + * Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org> + * + * 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. + */ + +#ifndef _SPAMD_SYNC +#define _SPAMD_SYNC + +/* + * spamd(8) synchronisation protocol. + * + * This protocol has been designed for realtime synchronisation between + * multiple machines running spamd(8), ie. in front of a MX and a backup MX. + * It is a simple Type-Length-Value based protocol, it allows easy + * extension with future subtypes and bulk transfers by sending multiple + * entries at once. The unencrypted messages will be authenticated using + * HMAC-SHA1. + * + * the spamd(8) synchronisation protocol is not intended to be used as + * a public SPAM sender database or distribution between vendors. + */ + +#define SPAM_SYNC_VERSION 1 +#define SPAM_SYNC_MCASTADDR "224.0.1.240" /* XXX choose valid address */ +#define SPAM_SYNC_MCASTTTL IP_DEFAULT_MULTICAST_TTL +#define SPAM_SYNC_HMAC_LEN 20 /* SHA1 */ +#define SPAM_SYNC_MAXSIZE 1408 +#define SPAM_SYNC_KEY "/etc/mail/spamd.key" + +struct spam_synchdr { + u_int8_t sh_version; + u_int8_t sh_af; + u_int32_t sh_counter; + u_int16_t sh_length; + u_int8_t sh_hmac[SPAM_SYNC_HMAC_LEN]; + u_int16_t sh_pad[1]; +} __packed; + +struct spam_synctlv_hdr { + u_int16_t st_type; + u_int16_t st_length; +} __packed; + +struct spam_synctlv_grey { + u_int16_t sg_type; + u_int16_t sg_length; + u_int32_t sg_timestamp; + u_int32_t sg_ip; + u_int16_t sg_from_length; + u_int16_t sg_to_length; + u_int16_t sg_helo_length; +} __packed; + +struct spam_synctlv_addr { + u_int16_t sd_type; + u_int16_t sd_length; + u_int32_t sd_timestamp; + u_int32_t sd_expire; + u_int32_t sd_ip; +} __packed; + +#define SPAM_SYNC_END 0x0000 +#define SPAM_SYNC_GREY 0x0001 +#define SPAM_SYNC_WHITE 0x0002 +#define SPAM_SYNC_TRAPPED 0x0003 + +extern int sync_init(const char *, const char *, u_short); +extern int sync_addhost(const char *, u_short); +extern void sync_recv(void); +extern void sync_update(time_t, char *, char *, char *, char *); +extern void sync_white(time_t, time_t, char *); +extern void sync_trapped(time_t, time_t, char *); + +#endif /* _SPAMD_SYNC */ diff --git a/libexec/spamlogd/Makefile b/libexec/spamlogd/Makefile index ed0070a7f1f..fca36dea9a9 100644 --- a/libexec/spamlogd/Makefile +++ b/libexec/spamlogd/Makefile @@ -1,11 +1,12 @@ -# $OpenBSD: Makefile,v 1.5 2006/11/26 11:31:14 deraadt Exp $ +# $OpenBSD: Makefile,v 1.6 2007/03/04 03:19:41 beck Exp $ PROG= spamlogd -SRCS= spamlogd.c +SRCS= spamlogd.c sync.c MAN= spamlogd.8 CFLAGS+= -Wall -Wstrict-prototypes -I${.CURDIR}/../spamd -LDADD+= -lpcap +LDADD+= -lpcap -lcrypto DPADD+= ${LIBPCAP} +.PATH: ${.CURDIR}/../spamd .include <bsd.prog.mk> diff --git a/libexec/spamlogd/spamlogd.8 b/libexec/spamlogd/spamlogd.8 index 199198d4467..52e86c2c0dd 100644 --- a/libexec/spamlogd/spamlogd.8 +++ b/libexec/spamlogd/spamlogd.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: spamlogd.8,v 1.10 2007/02/27 16:04:26 jmc Exp $ +.\" $OpenBSD: spamlogd.8,v 1.11 2007/03/04 03:19:41 beck Exp $ .\" .\" Copyright (c) 2004 Bob Beck. All rights reserved. .\" @@ -25,6 +25,7 @@ .Op Fl DI .Op Fl i Ar interface .Op Fl l Ar pflog_interface +.Op Fl Y Ar synctarget .Sh DESCRIPTION .Nm manipulates the @@ -76,6 +77,11 @@ Specify a interface to listen for connection notifications. The default is to watch for connections logged on .Dq pflog0 . +.It Fl Y Ar synctarget +Add a target to receive synchronisation messages; see +.Sx SYNCHRONISATION +below. +This option can be specified multiple times. .El .Pp It is important to be sure to log any connections to and from your real @@ -102,6 +108,21 @@ using facility .Nm will log each connection it sees at level .Dv LOG_DEBUG . +.Sh SYNCHRONISATION +.Nm +supports realtime synchronisation of whitelist states by sending +the information it updates to a +a number of +.Xr spamd 8 +daemons running on multiple machines. +To enable synchronisation, use the command line +.Fl Y +to specify the machines to which +.Nm +will send messages when it updates the state information. +For more information see +.Xr spamd 8 +.Pp .Sh FILES /var/db/spamd .Sh SEE ALSO diff --git a/libexec/spamlogd/spamlogd.c b/libexec/spamlogd/spamlogd.c index 5e1f316df77..c54e2f4bae0 100644 --- a/libexec/spamlogd/spamlogd.c +++ b/libexec/spamlogd/spamlogd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: spamlogd.c,v 1.15 2007/01/04 21:41:37 beck Exp $ */ +/* $OpenBSD: spamlogd.c,v 1.16 2007/03/04 03:19:41 beck Exp $ */ /* * Copyright (c) 2006 Henning Brauer <henning@openbsd.org> @@ -41,6 +41,7 @@ #include <err.h> #include <errno.h> #include <fcntl.h> +#include <netdb.h> #include <pwd.h> #include <stdio.h> #include <stdarg.h> @@ -51,6 +52,7 @@ #include <pcap.h> #include "grey.h" +#include "sync.h" #define MIN_PFLOG_HDRLEN 45 #define PCAPSNAP 512 @@ -58,6 +60,12 @@ #define PCAPOPTZ 1 /* optimize filter */ #define PCAPFSIZ 512 /* pcap filter string size */ +int debug = 1; +int greylist = 1; +FILE *grey = NULL; + +u_short sync_port; +int syncsend; u_int8_t flag_debug = 0; u_int8_t flag_inbound = 0; char *networkif = NULL; @@ -260,6 +268,10 @@ dbupdate(char *dbname, char *ip) } db->close(db); db = NULL; + if (syncsend) { + syslog_r(LOG_DEBUG, &sdata, "sync_white %s,", ip); + sync_white(now, now + WHITEEXP, ip); + } return (0); bad: db->close(db); @@ -270,7 +282,7 @@ dbupdate(char *dbname, char *ip) void usage(void) { - fprintf(stderr, "usage: %s [-DI] [-i interface] [-l pflog_interface]\n", + fprintf(stderr, "usage: %s [-DI] [-i interface] [-l pflog_interface] [-Y synctarget ]\n", __progname); exit(1); } @@ -281,8 +293,16 @@ main(int argc, char **argv) int ch; struct passwd *pw; pcap_handler phandler = logpkt_handler; + int syncfd = 0; + struct servent *ent; + char *sync_iface = NULL; + char *sync_baddr = NULL; + + if ((ent = getservbyname("spamd-sync", "udp")) == NULL) + errx(1, "Can't find service \"spamd-sync\" in /etc/services"); + sync_port = ntohs(ent->s_port); - while ((ch = getopt(argc, argv, "DIi:l:")) != -1) { + while ((ch = getopt(argc, argv, "DIi:l:Y:")) != -1) { switch (ch) { case 'D': flag_debug = 1; @@ -296,6 +316,11 @@ main(int argc, char **argv) case 'l': pflogif = optarg; break; + case 'Y': + if (sync_addhost(optarg, sync_port) != 0) + sync_iface = optarg; + syncsend++; + break; default: usage(); /* NOTREACHED */ @@ -313,6 +338,12 @@ main(int argc, char **argv) if (init_pcap() == -1) err(1, "couldn't initialize pcap"); + if (syncsend) { + syncfd = sync_init(sync_iface, sync_baddr, sync_port); + if (syncfd == -1) + err(1, "sync init"); + } + /* privdrop */ pw = getpwnam("_spamd"); if (pw == NULL) |