xref: /openbsd-src/libexec/spamlogd/spamlogd.c (revision bc5a8259a456844c67e446bf6bd66575acc64837)
1*bc5a8259Sbeck /*	$OpenBSD: spamlogd.c,v 1.32 2021/07/12 15:09:18 beck Exp $	*/
284d82a5fSderaadt 
31f68c1d4Sbeck /*
4e106bcd2Shenning  * Copyright (c) 2006 Henning Brauer <henning@openbsd.org>
5e106bcd2Shenning  * Copyright (c) 2006 Berk D. Demir.
64020b879Sbeck  * Copyright (c) 2004-2007 Bob Beck.
7e106bcd2Shenning  * Copyright (c) 2001 Theo de Raadt.
8e106bcd2Shenning  * Copyright (c) 2001 Can Erkin Acar.
9e106bcd2Shenning  * All rights reserved
101f68c1d4Sbeck  *
111f68c1d4Sbeck  * Permission to use, copy, modify, and distribute this software for any
121f68c1d4Sbeck  * purpose with or without fee is hereby granted, provided that the above
131f68c1d4Sbeck  * copyright notice and this permission notice appear in all copies.
141f68c1d4Sbeck  *
151f68c1d4Sbeck  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
161f68c1d4Sbeck  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
171f68c1d4Sbeck  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
181f68c1d4Sbeck  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
191f68c1d4Sbeck  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
201f68c1d4Sbeck  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
211f68c1d4Sbeck  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
221f68c1d4Sbeck  */
231f68c1d4Sbeck 
241f68c1d4Sbeck /* watch pf log for mail connections, update whitelist entries. */
251f68c1d4Sbeck 
261f68c1d4Sbeck #include <sys/types.h>
271f68c1d4Sbeck #include <sys/socket.h>
28e106bcd2Shenning #include <sys/ioctl.h>
29b9fc9a72Sderaadt #include <sys/signal.h>
30e106bcd2Shenning 
31e106bcd2Shenning #include <net/if.h>
32e106bcd2Shenning 
331f68c1d4Sbeck #include <netinet/in.h>
34e106bcd2Shenning #include <netinet/ip.h>
351f68c1d4Sbeck #include <arpa/inet.h>
36e106bcd2Shenning 
37e106bcd2Shenning #include <net/pfvar.h>
3868928c43Sderaadt #include <net/if_pflog.h>
39e106bcd2Shenning 
401f68c1d4Sbeck #include <db.h>
411f68c1d4Sbeck #include <err.h>
42e106bcd2Shenning #include <errno.h>
431f68c1d4Sbeck #include <fcntl.h>
4498babcadSbeck #include <netdb.h>
45e106bcd2Shenning #include <pwd.h>
461f68c1d4Sbeck #include <stdio.h>
47e106bcd2Shenning #include <stdarg.h>
481f68c1d4Sbeck #include <stdlib.h>
49a574feadSbeck #include <syslog.h>
501f68c1d4Sbeck #include <string.h>
511f68c1d4Sbeck #include <unistd.h>
524053af61Sbrynet #include <pcap-int.h>
53e106bcd2Shenning #include <pcap.h>
541f68c1d4Sbeck 
551f68c1d4Sbeck #include "grey.h"
5698babcadSbeck #include "sync.h"
571f68c1d4Sbeck 
58e106bcd2Shenning #define MIN_PFLOG_HDRLEN	45
59e106bcd2Shenning #define PCAPSNAP		512
60e106bcd2Shenning #define PCAPTIMO		500	/* ms */
61e106bcd2Shenning #define PCAPOPTZ		1	/* optimize filter */
62e106bcd2Shenning #define PCAPFSIZ		512	/* pcap filter string size */
63e106bcd2Shenning 
64e7066a17Smestre #define SPAMD_USER		"_spamd"
65e7066a17Smestre 
6698babcadSbeck int debug = 1;
6798babcadSbeck int greylist = 1;
6898babcadSbeck FILE *grey = NULL;
6998babcadSbeck 
7098babcadSbeck u_short sync_port;
7198babcadSbeck int syncsend;
72e106bcd2Shenning u_int8_t		 flag_debug = 0;
73e106bcd2Shenning u_int8_t		 flag_inbound = 0;
74e106bcd2Shenning char			*networkif = NULL;
75e106bcd2Shenning char			*pflogif = "pflog0";
76e106bcd2Shenning char			 errbuf[PCAP_ERRBUF_SIZE];
77e106bcd2Shenning pcap_t			*hpcap = NULL;
78a574feadSbeck struct syslog_data	 sdata	= SYSLOG_DATA_INIT;
79607a4d95Sokan time_t			 whiteexp = WHITEEXP;
806d2bfb43Sotto extern char		*__progname;
811f68c1d4Sbeck 
82e106bcd2Shenning void	logmsg(int , const char *, ...);
83e106bcd2Shenning void	sighandler_close(int);
84e106bcd2Shenning int	init_pcap(void);
85e106bcd2Shenning void	logpkt_handler(u_char *, const struct pcap_pkthdr *, const u_char *);
86e106bcd2Shenning int	dbupdate(char *, char *);
87e7066a17Smestre __dead void	usage(void);
88e106bcd2Shenning 
89e106bcd2Shenning void
logmsg(int pri,const char * msg,...)90e106bcd2Shenning logmsg(int pri, const char *msg, ...)
91e106bcd2Shenning {
92e106bcd2Shenning 	va_list	ap;
93e106bcd2Shenning 	va_start(ap, msg);
94e106bcd2Shenning 
95e106bcd2Shenning 	if (flag_debug) {
96e106bcd2Shenning 		vfprintf(stderr, msg, ap);
97e106bcd2Shenning 		fprintf(stderr, "\n");
98e106bcd2Shenning 	} else
99e106bcd2Shenning 		vsyslog_r(pri, &sdata, msg, ap);
100e106bcd2Shenning 
101e106bcd2Shenning 	va_end(ap);
102e106bcd2Shenning }
103e106bcd2Shenning 
104e106bcd2Shenning void
sighandler_close(int signal)105e106bcd2Shenning sighandler_close(int signal)
106e106bcd2Shenning {
107e106bcd2Shenning 	if (hpcap != NULL)
108e106bcd2Shenning 		pcap_breakloop(hpcap);	/* sighdlr safe */
109e106bcd2Shenning }
110e106bcd2Shenning 
1114053af61Sbrynet pcap_t *
pflog_read_live(const char * source,int slen,int promisc,int to_ms,char * ebuf)1124053af61Sbrynet pflog_read_live(const char *source, int slen, int promisc, int to_ms,
1134053af61Sbrynet     char *ebuf)
1144053af61Sbrynet {
1154053af61Sbrynet 	int		fd;
1164053af61Sbrynet 	struct bpf_version bv;
1174053af61Sbrynet 	struct ifreq	ifr;
1184053af61Sbrynet 	u_int		v, dlt = DLT_PFLOG;
1194053af61Sbrynet 	pcap_t		*p;
1204053af61Sbrynet 
1214053af61Sbrynet 	if (source == NULL || slen <= 0)
1224053af61Sbrynet 		return (NULL);
1234053af61Sbrynet 
1244053af61Sbrynet 	p = pcap_create(source, ebuf);
1254053af61Sbrynet 	if (p == NULL)
1264053af61Sbrynet 		return (NULL);
1274053af61Sbrynet 
1284053af61Sbrynet 	/* Open bpf(4) read only */
1294053af61Sbrynet 	if ((fd = open("/dev/bpf", O_RDONLY)) == -1)
1304053af61Sbrynet 		return (NULL);
1314053af61Sbrynet 
1324053af61Sbrynet 	if (ioctl(fd, BIOCVERSION, &bv) == -1) {
1334053af61Sbrynet 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCVERSION: %s",
1344053af61Sbrynet 		    pcap_strerror(errno));
1354053af61Sbrynet 		goto bad;
1364053af61Sbrynet 	}
1374053af61Sbrynet 
1384053af61Sbrynet 	if (bv.bv_major != BPF_MAJOR_VERSION ||
1394053af61Sbrynet 	    bv.bv_minor < BPF_MINOR_VERSION) {
1404053af61Sbrynet 		snprintf(ebuf, PCAP_ERRBUF_SIZE,
1414053af61Sbrynet 		    "kernel bpf filter out of date");
1424053af61Sbrynet 		goto bad;
1434053af61Sbrynet 	}
1444053af61Sbrynet 
1454053af61Sbrynet 	strlcpy(ifr.ifr_name, source, sizeof(ifr.ifr_name));
1464053af61Sbrynet 	if (ioctl(fd, BIOCSETIF, &ifr) == -1) {
1474053af61Sbrynet 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSETIF: %s",
1484053af61Sbrynet 		    pcap_strerror(errno));
1494053af61Sbrynet 		goto bad;
1504053af61Sbrynet 	}
1514053af61Sbrynet 
1524053af61Sbrynet 	if (dlt != (u_int) -1 && ioctl(fd, BIOCSDLT, &dlt)) {
1534053af61Sbrynet 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSDLT: %s",
1544053af61Sbrynet 		    pcap_strerror(errno));
1554053af61Sbrynet 		goto bad;
1564053af61Sbrynet 	}
1574053af61Sbrynet 
1584053af61Sbrynet 	p->fd = fd;
1594053af61Sbrynet 	p->snapshot = slen;
1604053af61Sbrynet 	p->linktype = dlt;
1614053af61Sbrynet 
1624053af61Sbrynet 	/* set timeout */
1634053af61Sbrynet 	if (to_ms != 0) {
1644053af61Sbrynet 		struct timeval to;
1654053af61Sbrynet 		to.tv_sec = to_ms / 1000;
1664053af61Sbrynet 		to.tv_usec = (to_ms * 1000) % 1000000;
1674053af61Sbrynet 		if (ioctl(p->fd, BIOCSRTIMEOUT, &to) == -1) {
1684053af61Sbrynet 			snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSRTIMEOUT: %s",
1694053af61Sbrynet 			    pcap_strerror(errno));
17049f581ecSbrynet 			goto bad;
1714053af61Sbrynet 		}
1724053af61Sbrynet 	}
1734053af61Sbrynet 	if (promisc)
1744053af61Sbrynet 		/* this is allowed to fail */
1754053af61Sbrynet 		ioctl(fd, BIOCPROMISC, NULL);
1764053af61Sbrynet 
1774053af61Sbrynet 	if (ioctl(fd, BIOCGBLEN, &v) == -1) {
1784053af61Sbrynet 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGBLEN: %s",
1794053af61Sbrynet 		    pcap_strerror(errno));
1804053af61Sbrynet 		goto bad;
1814053af61Sbrynet 	}
1824053af61Sbrynet 	p->bufsize = v;
1834053af61Sbrynet 	p->buffer = malloc(p->bufsize);
1844053af61Sbrynet 	if (p->buffer == NULL) {
1854053af61Sbrynet 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s",
1864053af61Sbrynet 		    pcap_strerror(errno));
1874053af61Sbrynet 		goto bad;
1884053af61Sbrynet 	}
1894053af61Sbrynet 	p->activated = 1;
1904053af61Sbrynet 	return (p);
1914053af61Sbrynet 
1924053af61Sbrynet bad:
1934053af61Sbrynet 	pcap_close(p);
1944053af61Sbrynet 	return (NULL);
1954053af61Sbrynet }
1964053af61Sbrynet 
197e106bcd2Shenning int
init_pcap(void)198e106bcd2Shenning init_pcap(void)
199e106bcd2Shenning {
200e106bcd2Shenning 	struct bpf_program	bpfp;
201e106bcd2Shenning 	char	filter[PCAPFSIZ] = "ip and port 25 and action pass "
202e106bcd2Shenning 		    "and tcp[13]&0x12=0x2";
203e106bcd2Shenning 
2044053af61Sbrynet 	if ((hpcap = pflog_read_live(pflogif, PCAPSNAP, 1, PCAPTIMO,
205e106bcd2Shenning 	    errbuf)) == NULL) {
206e106bcd2Shenning 		logmsg(LOG_ERR, "Failed to initialize: %s", errbuf);
207e106bcd2Shenning 		return (-1);
208e106bcd2Shenning 	}
209e106bcd2Shenning 
210e106bcd2Shenning 	if (pcap_datalink(hpcap) != DLT_PFLOG) {
211e106bcd2Shenning 		logmsg(LOG_ERR, "Invalid datalink type");
212e106bcd2Shenning 		pcap_close(hpcap);
213e106bcd2Shenning 		hpcap = NULL;
214e106bcd2Shenning 		return (-1);
215e106bcd2Shenning 	}
216e106bcd2Shenning 
217e106bcd2Shenning 	if (networkif != NULL) {
218e106bcd2Shenning 		strlcat(filter, " and on ", PCAPFSIZ);
219e106bcd2Shenning 		strlcat(filter, networkif, PCAPFSIZ);
220e106bcd2Shenning 	}
221e106bcd2Shenning 
222e106bcd2Shenning 	if (pcap_compile(hpcap, &bpfp, filter, PCAPOPTZ, 0) == -1 ||
223e106bcd2Shenning 	    pcap_setfilter(hpcap, &bpfp) == -1) {
224e106bcd2Shenning 		logmsg(LOG_ERR, "%s", pcap_geterr(hpcap));
225e106bcd2Shenning 		return (-1);
226e106bcd2Shenning 	}
227e106bcd2Shenning 
228e106bcd2Shenning 	pcap_freecode(&bpfp);
229e106bcd2Shenning 
230df69c215Sderaadt 	if (ioctl(pcap_fileno(hpcap), BIOCLOCK) == -1) {
231e106bcd2Shenning 		logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno));
232e106bcd2Shenning 		return (-1);
233e106bcd2Shenning 	}
234e106bcd2Shenning 
235e106bcd2Shenning 	return (0);
236e106bcd2Shenning }
237e106bcd2Shenning 
238e106bcd2Shenning void
logpkt_handler(u_char * user,const struct pcap_pkthdr * h,const u_char * sp)239e106bcd2Shenning logpkt_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
240e106bcd2Shenning {
241e106bcd2Shenning 	sa_family_t		 af;
242e106bcd2Shenning 	u_int8_t		 hdrlen;
243e106bcd2Shenning 	u_int32_t		 caplen = h->caplen;
244e106bcd2Shenning 	const struct ip		*ip = NULL;
245e106bcd2Shenning 	const struct pfloghdr	*hdr;
246e106bcd2Shenning 	char			 ipstraddr[40] = { '\0' };
247e106bcd2Shenning 
248e106bcd2Shenning 	hdr = (const struct pfloghdr *)sp;
249e106bcd2Shenning 	if (hdr->length < MIN_PFLOG_HDRLEN) {
250e106bcd2Shenning 		logmsg(LOG_WARNING, "invalid pflog header length (%u/%u). "
251e106bcd2Shenning 		    "packet dropped.", hdr->length, MIN_PFLOG_HDRLEN);
252e106bcd2Shenning 		return;
253e106bcd2Shenning 	}
254e106bcd2Shenning 	hdrlen = BPF_WORDALIGN(hdr->length);
255e106bcd2Shenning 
256e106bcd2Shenning 	if (caplen < hdrlen) {
257e106bcd2Shenning 		logmsg(LOG_WARNING, "pflog header larger than caplen (%u/%u). "
258e106bcd2Shenning 		    "packet dropped.", hdrlen, caplen);
259e106bcd2Shenning 		return;
260e106bcd2Shenning 	}
261e106bcd2Shenning 
262e106bcd2Shenning 	/* We're interested in passed packets */
263e106bcd2Shenning 	if (hdr->action != PF_PASS)
264e106bcd2Shenning 		return;
265e106bcd2Shenning 
266e106bcd2Shenning 	af = hdr->af;
267e106bcd2Shenning 	if (af == AF_INET) {
268e106bcd2Shenning 		ip = (const struct ip *)(sp + hdrlen);
269e106bcd2Shenning 		if (hdr->dir == PF_IN)
270e106bcd2Shenning 			inet_ntop(af, &ip->ip_src, ipstraddr,
271e106bcd2Shenning 			    sizeof(ipstraddr));
272e106bcd2Shenning 		else if (hdr->dir == PF_OUT && !flag_inbound)
273e106bcd2Shenning 			inet_ntop(af, &ip->ip_dst, ipstraddr,
274e106bcd2Shenning 			    sizeof(ipstraddr));
275e106bcd2Shenning 	}
276e106bcd2Shenning 
277e106bcd2Shenning 	if (ipstraddr[0] != '\0') {
27899953b1cSbeck 		if (hdr->dir == PF_IN)
27999953b1cSbeck 			logmsg(LOG_DEBUG,"inbound %s", ipstraddr);
28099953b1cSbeck 		else
28199953b1cSbeck 			logmsg(LOG_DEBUG,"outbound %s", ipstraddr);
282e106bcd2Shenning 		dbupdate(PATH_SPAMD_DB, ipstraddr);
283e106bcd2Shenning 	}
284e106bcd2Shenning }
285e106bcd2Shenning 
2861f68c1d4Sbeck int
dbupdate(char * dbname,char * ip)2871f68c1d4Sbeck dbupdate(char *dbname, char *ip)
2881f68c1d4Sbeck {
2894020b879Sbeck 	HASHINFO	hashinfo;
290a574feadSbeck 	DBT		dbk, dbd;
291a574feadSbeck 	DB		*db;
2921f68c1d4Sbeck 	struct gdata	gd;
2931f68c1d4Sbeck 	time_t		now;
2941f68c1d4Sbeck 	int		r;
29570333c93Sotto 	struct in_addr	ia;
2961f68c1d4Sbeck 
2971f68c1d4Sbeck 	now = time(NULL);
2984020b879Sbeck 	memset(&hashinfo, 0, sizeof(hashinfo));
2994020b879Sbeck 	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
300e106bcd2Shenning 	if (db == NULL) {
301e106bcd2Shenning 		logmsg(LOG_ERR, "Can not open db %s: %s", dbname,
302e106bcd2Shenning 		    strerror(errno));
3031f68c1d4Sbeck 		return (-1);
304e106bcd2Shenning 	}
30570333c93Sotto 	if (inet_pton(AF_INET, ip, &ia) != 1) {
306e106bcd2Shenning 		logmsg(LOG_NOTICE, "Invalid IP address %s", ip);
3071f68c1d4Sbeck 		goto bad;
3081f68c1d4Sbeck 	}
3091f68c1d4Sbeck 	memset(&dbk, 0, sizeof(dbk));
3101f68c1d4Sbeck 	dbk.size = strlen(ip);
3111f68c1d4Sbeck 	dbk.data = ip;
3121f68c1d4Sbeck 	memset(&dbd, 0, sizeof(dbd));
313e106bcd2Shenning 
3141f68c1d4Sbeck 	/* add or update whitelist entry */
3151f68c1d4Sbeck 	r = db->get(db, &dbk, &dbd, 0);
3161f68c1d4Sbeck 	if (r == -1) {
317e106bcd2Shenning 		logmsg(LOG_NOTICE, "db->get failed (%m)");
3181f68c1d4Sbeck 		goto bad;
3191f68c1d4Sbeck 	}
320e106bcd2Shenning 
3211f68c1d4Sbeck 	if (r) {
3221f68c1d4Sbeck 		/* new entry */
3231f68c1d4Sbeck 		memset(&gd, 0, sizeof(gd));
3241f68c1d4Sbeck 		gd.first = now;
3251f68c1d4Sbeck 		gd.bcount = 1;
3261f68c1d4Sbeck 		gd.pass = now;
327607a4d95Sokan 		gd.expire = now + whiteexp;
3281f68c1d4Sbeck 		memset(&dbk, 0, sizeof(dbk));
3291f68c1d4Sbeck 		dbk.size = strlen(ip);
3301f68c1d4Sbeck 		dbk.data = ip;
3311f68c1d4Sbeck 		memset(&dbd, 0, sizeof(dbd));
3321f68c1d4Sbeck 		dbd.size = sizeof(gd);
3331f68c1d4Sbeck 		dbd.data = &gd;
3341f68c1d4Sbeck 		r = db->put(db, &dbk, &dbd, 0);
3351f68c1d4Sbeck 		if (r) {
336e106bcd2Shenning 			logmsg(LOG_NOTICE, "db->put failed (%m)");
3371f68c1d4Sbeck 			goto bad;
3381f68c1d4Sbeck 		}
3391f68c1d4Sbeck 	} else {
34051210d5cSmillert 		/* XXX - backwards compat */
34151210d5cSmillert 		if (gdcopyin(&dbd, &gd) == -1) {
3421f68c1d4Sbeck 			/* whatever this is, it doesn't belong */
3431f68c1d4Sbeck 			db->del(db, &dbk, 0);
3441f68c1d4Sbeck 			goto bad;
3451f68c1d4Sbeck 		}
3461f68c1d4Sbeck 		gd.pcount++;
347607a4d95Sokan 		gd.expire = now + whiteexp;
3481f68c1d4Sbeck 		memset(&dbk, 0, sizeof(dbk));
3491f68c1d4Sbeck 		dbk.size = strlen(ip);
3501f68c1d4Sbeck 		dbk.data = ip;
3511f68c1d4Sbeck 		memset(&dbd, 0, sizeof(dbd));
3521f68c1d4Sbeck 		dbd.size = sizeof(gd);
3531f68c1d4Sbeck 		dbd.data = &gd;
3541f68c1d4Sbeck 		r = db->put(db, &dbk, &dbd, 0);
3551f68c1d4Sbeck 		if (r) {
356e106bcd2Shenning 			logmsg(LOG_NOTICE, "db->put failed (%m)");
3571f68c1d4Sbeck 			goto bad;
3581f68c1d4Sbeck 		}
3591f68c1d4Sbeck 	}
3601f68c1d4Sbeck 	db->close(db);
36184d82a5fSderaadt 	db = NULL;
3623e552309Sbeck 	if (syncsend)
363607a4d95Sokan 		sync_white(now, now + whiteexp, ip);
3641f68c1d4Sbeck 	return (0);
3651f68c1d4Sbeck  bad:
3661f68c1d4Sbeck 	db->close(db);
36784d82a5fSderaadt 	db = NULL;
3681f68c1d4Sbeck 	return (-1);
3691f68c1d4Sbeck }
3701f68c1d4Sbeck 
371e106bcd2Shenning void
usage(void)3721f68c1d4Sbeck usage(void)
3731f68c1d4Sbeck {
374fbc349adSderaadt 	fprintf(stderr,
375607a4d95Sokan 	    "usage: %s [-DI] [-i interface] [-l pflog_interface] "
376607a4d95Sokan 	    "[-W whiteexp] [-Y synctarget]\n",
37744857a5dShenning 	    __progname);
37884d82a5fSderaadt 	exit(1);
3791f68c1d4Sbeck }
3801f68c1d4Sbeck 
3811f68c1d4Sbeck int
main(int argc,char ** argv)3821f68c1d4Sbeck main(int argc, char **argv)
3831f68c1d4Sbeck {
3840ee1ef6eSokan 	int		 ch;
385e106bcd2Shenning 	struct passwd	*pw;
386e106bcd2Shenning 	pcap_handler	 phandler = logpkt_handler;
38798babcadSbeck 	int syncfd = 0;
38898babcadSbeck 	struct servent *ent;
38998babcadSbeck 	char *sync_iface = NULL;
39098babcadSbeck 	char *sync_baddr = NULL;
391607a4d95Sokan 	const char *errstr;
3921f68c1d4Sbeck 
393e7066a17Smestre 	if (geteuid())
394e7066a17Smestre 		errx(1, "need root privileges");
395e7066a17Smestre 
39698babcadSbeck 	if ((ent = getservbyname("spamd-sync", "udp")) == NULL)
39798babcadSbeck 		errx(1, "Can't find service \"spamd-sync\" in /etc/services");
39898babcadSbeck 	sync_port = ntohs(ent->s_port);
39998babcadSbeck 
400607a4d95Sokan 	while ((ch = getopt(argc, argv, "DIi:l:W:Y:")) != -1) {
4011f68c1d4Sbeck 		switch (ch) {
402e106bcd2Shenning 		case 'D':
403e106bcd2Shenning 			flag_debug = 1;
404a574feadSbeck 			break;
405a574feadSbeck 		case 'I':
406e106bcd2Shenning 			flag_inbound = 1;
407e106bcd2Shenning 			break;
408e106bcd2Shenning 		case 'i':
409e106bcd2Shenning 			networkif = optarg;
4101f68c1d4Sbeck 			break;
41144857a5dShenning 		case 'l':
412e106bcd2Shenning 			pflogif = optarg;
41344857a5dShenning 			break;
414607a4d95Sokan 		case 'W':
415607a4d95Sokan 			/* limit whiteexp to 2160 hours (90 days) */
4160ee1ef6eSokan 			whiteexp = strtonum(optarg, 1, (24 * 90), &errstr);
417607a4d95Sokan 			if (errstr)
418607a4d95Sokan 				usage();
419607a4d95Sokan 			/* convert to seconds from hours */
420607a4d95Sokan 			whiteexp *= (60 * 60);
421607a4d95Sokan 			break;
42298babcadSbeck 		case 'Y':
42398babcadSbeck 			if (sync_addhost(optarg, sync_port) != 0)
42498babcadSbeck 				sync_iface = optarg;
42598babcadSbeck 			syncsend++;
42698babcadSbeck 			break;
4271f68c1d4Sbeck 		default:
4281f68c1d4Sbeck 			usage();
4291f68c1d4Sbeck 		}
4301f68c1d4Sbeck 	}
4311f68c1d4Sbeck 
432e106bcd2Shenning 	signal(SIGINT , sighandler_close);
433e106bcd2Shenning 	signal(SIGQUIT, sighandler_close);
434e106bcd2Shenning 	signal(SIGTERM, sighandler_close);
435e106bcd2Shenning 
436e106bcd2Shenning 	logmsg(LOG_DEBUG, "Listening on %s for %s %s", pflogif,
437e106bcd2Shenning 	    (networkif == NULL) ? "all interfaces." : networkif,
438e106bcd2Shenning 	    (flag_inbound) ? "Inbound direction only." : "");
439e106bcd2Shenning 
440e106bcd2Shenning 	if (init_pcap() == -1)
441e106bcd2Shenning 		err(1, "couldn't initialize pcap");
442e106bcd2Shenning 
44398babcadSbeck 	if (syncsend) {
44498babcadSbeck 		syncfd = sync_init(sync_iface, sync_baddr, sync_port);
44598babcadSbeck 		if (syncfd == -1)
44698babcadSbeck 			err(1, "sync init");
44798babcadSbeck 	}
44898babcadSbeck 
449e106bcd2Shenning 	/* privdrop */
450e7066a17Smestre 	if ((pw = getpwnam(SPAMD_USER)) == NULL)
451e7066a17Smestre 		errx(1, "no such user %s", SPAMD_USER);
452e106bcd2Shenning 
453e106bcd2Shenning 	if (setgroups(1, &pw->pw_gid) ||
454e106bcd2Shenning 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
455e106bcd2Shenning 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
456e106bcd2Shenning 		err(1, "failed to drop privs");
457e106bcd2Shenning 
458e106bcd2Shenning 	if (!flag_debug) {
459e106bcd2Shenning 		if (daemon(0, 0) == -1)
4601f68c1d4Sbeck 			err(1, "daemon");
461a574feadSbeck 		tzset();
462a574feadSbeck 		openlog_r("spamlogd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata);
4637c60fa66Sotto 	}
4641f68c1d4Sbeck 
465222d93a5Smestre 	if (unveil(PATH_SPAMD_DB, "rw") == -1)
466*bc5a8259Sbeck 		err(1, "unveil %s", PATH_SPAMD_DB);
467f87e39afSbeck 	if (syncsend) {
468f87e39afSbeck 		if (pledge("stdio rpath wpath inet flock", NULL) == -1)
469f87e39afSbeck 			err(1, "pledge");
470f87e39afSbeck 	} else {
471f87e39afSbeck 		if (pledge("stdio rpath wpath flock", NULL) == -1)
472f87e39afSbeck 			err(1, "pledge");
473f87e39afSbeck 	}
474f87e39afSbeck 
475e106bcd2Shenning 	pcap_loop(hpcap, -1, phandler, NULL);
476a574feadSbeck 
477e106bcd2Shenning 	logmsg(LOG_NOTICE, "exiting");
478e106bcd2Shenning 	if (!flag_debug)
479e106bcd2Shenning 		closelog_r(&sdata);
480a574feadSbeck 
4811f68c1d4Sbeck 	exit(0);
4821f68c1d4Sbeck }
483