xref: /openbsd/libexec/spamlogd/spamlogd.c (revision 09467b48)
1 /*	$OpenBSD: spamlogd.c,v 1.31 2019/07/25 17:32:33 brynet Exp $	*/
2 
3 /*
4  * Copyright (c) 2006 Henning Brauer <henning@openbsd.org>
5  * Copyright (c) 2006 Berk D. Demir.
6  * Copyright (c) 2004-2007 Bob Beck.
7  * Copyright (c) 2001 Theo de Raadt.
8  * Copyright (c) 2001 Can Erkin Acar.
9  * All rights reserved
10  *
11  * Permission to use, copy, modify, and distribute this software for any
12  * purpose with or without fee is hereby granted, provided that the above
13  * copyright notice and this permission notice appear in all copies.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  */
23 
24 /* watch pf log for mail connections, update whitelist entries. */
25 
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <sys/ioctl.h>
29 #include <sys/signal.h>
30 
31 #include <net/if.h>
32 
33 #include <netinet/in.h>
34 #include <netinet/ip.h>
35 #include <arpa/inet.h>
36 
37 #include <net/pfvar.h>
38 #include <net/if_pflog.h>
39 
40 #include <db.h>
41 #include <err.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <netdb.h>
45 #include <pwd.h>
46 #include <stdio.h>
47 #include <stdarg.h>
48 #include <stdlib.h>
49 #include <syslog.h>
50 #include <string.h>
51 #include <unistd.h>
52 #include <pcap-int.h>
53 #include <pcap.h>
54 
55 #include "grey.h"
56 #include "sync.h"
57 
58 #define MIN_PFLOG_HDRLEN	45
59 #define PCAPSNAP		512
60 #define PCAPTIMO		500	/* ms */
61 #define PCAPOPTZ		1	/* optimize filter */
62 #define PCAPFSIZ		512	/* pcap filter string size */
63 
64 #define SPAMD_USER		"_spamd"
65 
66 int debug = 1;
67 int greylist = 1;
68 FILE *grey = NULL;
69 
70 u_short sync_port;
71 int syncsend;
72 u_int8_t		 flag_debug = 0;
73 u_int8_t		 flag_inbound = 0;
74 char			*networkif = NULL;
75 char			*pflogif = "pflog0";
76 char			 errbuf[PCAP_ERRBUF_SIZE];
77 pcap_t			*hpcap = NULL;
78 struct syslog_data	 sdata	= SYSLOG_DATA_INIT;
79 time_t			 whiteexp = WHITEEXP;
80 extern char		*__progname;
81 
82 void	logmsg(int , const char *, ...);
83 void	sighandler_close(int);
84 int	init_pcap(void);
85 void	logpkt_handler(u_char *, const struct pcap_pkthdr *, const u_char *);
86 int	dbupdate(char *, char *);
87 __dead void	usage(void);
88 
89 void
90 logmsg(int pri, const char *msg, ...)
91 {
92 	va_list	ap;
93 	va_start(ap, msg);
94 
95 	if (flag_debug) {
96 		vfprintf(stderr, msg, ap);
97 		fprintf(stderr, "\n");
98 	} else
99 		vsyslog_r(pri, &sdata, msg, ap);
100 
101 	va_end(ap);
102 }
103 
104 void
105 sighandler_close(int signal)
106 {
107 	if (hpcap != NULL)
108 		pcap_breakloop(hpcap);	/* sighdlr safe */
109 }
110 
111 pcap_t *
112 pflog_read_live(const char *source, int slen, int promisc, int to_ms,
113     char *ebuf)
114 {
115 	int		fd;
116 	struct bpf_version bv;
117 	struct ifreq	ifr;
118 	u_int		v, dlt = DLT_PFLOG;
119 	pcap_t		*p;
120 
121 	if (source == NULL || slen <= 0)
122 		return (NULL);
123 
124 	p = pcap_create(source, ebuf);
125 	if (p == NULL)
126 		return (NULL);
127 
128 	/* Open bpf(4) read only */
129 	if ((fd = open("/dev/bpf", O_RDONLY)) == -1)
130 		return (NULL);
131 
132 	if (ioctl(fd, BIOCVERSION, &bv) == -1) {
133 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCVERSION: %s",
134 		    pcap_strerror(errno));
135 		goto bad;
136 	}
137 
138 	if (bv.bv_major != BPF_MAJOR_VERSION ||
139 	    bv.bv_minor < BPF_MINOR_VERSION) {
140 		snprintf(ebuf, PCAP_ERRBUF_SIZE,
141 		    "kernel bpf filter out of date");
142 		goto bad;
143 	}
144 
145 	strlcpy(ifr.ifr_name, source, sizeof(ifr.ifr_name));
146 	if (ioctl(fd, BIOCSETIF, &ifr) == -1) {
147 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSETIF: %s",
148 		    pcap_strerror(errno));
149 		goto bad;
150 	}
151 
152 	if (dlt != (u_int) -1 && ioctl(fd, BIOCSDLT, &dlt)) {
153 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSDLT: %s",
154 		    pcap_strerror(errno));
155 		goto bad;
156 	}
157 
158 	p->fd = fd;
159 	p->snapshot = slen;
160 	p->linktype = dlt;
161 
162 	/* set timeout */
163 	if (to_ms != 0) {
164 		struct timeval to;
165 		to.tv_sec = to_ms / 1000;
166 		to.tv_usec = (to_ms * 1000) % 1000000;
167 		if (ioctl(p->fd, BIOCSRTIMEOUT, &to) == -1) {
168 			snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSRTIMEOUT: %s",
169 			    pcap_strerror(errno));
170 			goto bad;
171 		}
172 	}
173 	if (promisc)
174 		/* this is allowed to fail */
175 		ioctl(fd, BIOCPROMISC, NULL);
176 
177 	if (ioctl(fd, BIOCGBLEN, &v) == -1) {
178 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGBLEN: %s",
179 		    pcap_strerror(errno));
180 		goto bad;
181 	}
182 	p->bufsize = v;
183 	p->buffer = malloc(p->bufsize);
184 	if (p->buffer == NULL) {
185 		snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s",
186 		    pcap_strerror(errno));
187 		goto bad;
188 	}
189 	p->activated = 1;
190 	return (p);
191 
192 bad:
193 	pcap_close(p);
194 	return (NULL);
195 }
196 
197 int
198 init_pcap(void)
199 {
200 	struct bpf_program	bpfp;
201 	char	filter[PCAPFSIZ] = "ip and port 25 and action pass "
202 		    "and tcp[13]&0x12=0x2";
203 
204 	if ((hpcap = pflog_read_live(pflogif, PCAPSNAP, 1, PCAPTIMO,
205 	    errbuf)) == NULL) {
206 		logmsg(LOG_ERR, "Failed to initialize: %s", errbuf);
207 		return (-1);
208 	}
209 
210 	if (pcap_datalink(hpcap) != DLT_PFLOG) {
211 		logmsg(LOG_ERR, "Invalid datalink type");
212 		pcap_close(hpcap);
213 		hpcap = NULL;
214 		return (-1);
215 	}
216 
217 	if (networkif != NULL) {
218 		strlcat(filter, " and on ", PCAPFSIZ);
219 		strlcat(filter, networkif, PCAPFSIZ);
220 	}
221 
222 	if (pcap_compile(hpcap, &bpfp, filter, PCAPOPTZ, 0) == -1 ||
223 	    pcap_setfilter(hpcap, &bpfp) == -1) {
224 		logmsg(LOG_ERR, "%s", pcap_geterr(hpcap));
225 		return (-1);
226 	}
227 
228 	pcap_freecode(&bpfp);
229 
230 	if (ioctl(pcap_fileno(hpcap), BIOCLOCK) == -1) {
231 		logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno));
232 		return (-1);
233 	}
234 
235 	return (0);
236 }
237 
238 void
239 logpkt_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
240 {
241 	sa_family_t		 af;
242 	u_int8_t		 hdrlen;
243 	u_int32_t		 caplen = h->caplen;
244 	const struct ip		*ip = NULL;
245 	const struct pfloghdr	*hdr;
246 	char			 ipstraddr[40] = { '\0' };
247 
248 	hdr = (const struct pfloghdr *)sp;
249 	if (hdr->length < MIN_PFLOG_HDRLEN) {
250 		logmsg(LOG_WARNING, "invalid pflog header length (%u/%u). "
251 		    "packet dropped.", hdr->length, MIN_PFLOG_HDRLEN);
252 		return;
253 	}
254 	hdrlen = BPF_WORDALIGN(hdr->length);
255 
256 	if (caplen < hdrlen) {
257 		logmsg(LOG_WARNING, "pflog header larger than caplen (%u/%u). "
258 		    "packet dropped.", hdrlen, caplen);
259 		return;
260 	}
261 
262 	/* We're interested in passed packets */
263 	if (hdr->action != PF_PASS)
264 		return;
265 
266 	af = hdr->af;
267 	if (af == AF_INET) {
268 		ip = (const struct ip *)(sp + hdrlen);
269 		if (hdr->dir == PF_IN)
270 			inet_ntop(af, &ip->ip_src, ipstraddr,
271 			    sizeof(ipstraddr));
272 		else if (hdr->dir == PF_OUT && !flag_inbound)
273 			inet_ntop(af, &ip->ip_dst, ipstraddr,
274 			    sizeof(ipstraddr));
275 	}
276 
277 	if (ipstraddr[0] != '\0') {
278 		if (hdr->dir == PF_IN)
279 			logmsg(LOG_DEBUG,"inbound %s", ipstraddr);
280 		else
281 			logmsg(LOG_DEBUG,"outbound %s", ipstraddr);
282 		dbupdate(PATH_SPAMD_DB, ipstraddr);
283 	}
284 }
285 
286 int
287 dbupdate(char *dbname, char *ip)
288 {
289 	HASHINFO	hashinfo;
290 	DBT		dbk, dbd;
291 	DB		*db;
292 	struct gdata	gd;
293 	time_t		now;
294 	int		r;
295 	struct in_addr	ia;
296 
297 	now = time(NULL);
298 	memset(&hashinfo, 0, sizeof(hashinfo));
299 	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
300 	if (db == NULL) {
301 		logmsg(LOG_ERR, "Can not open db %s: %s", dbname,
302 		    strerror(errno));
303 		return (-1);
304 	}
305 	if (inet_pton(AF_INET, ip, &ia) != 1) {
306 		logmsg(LOG_NOTICE, "Invalid IP address %s", ip);
307 		goto bad;
308 	}
309 	memset(&dbk, 0, sizeof(dbk));
310 	dbk.size = strlen(ip);
311 	dbk.data = ip;
312 	memset(&dbd, 0, sizeof(dbd));
313 
314 	/* add or update whitelist entry */
315 	r = db->get(db, &dbk, &dbd, 0);
316 	if (r == -1) {
317 		logmsg(LOG_NOTICE, "db->get failed (%m)");
318 		goto bad;
319 	}
320 
321 	if (r) {
322 		/* new entry */
323 		memset(&gd, 0, sizeof(gd));
324 		gd.first = now;
325 		gd.bcount = 1;
326 		gd.pass = now;
327 		gd.expire = now + whiteexp;
328 		memset(&dbk, 0, sizeof(dbk));
329 		dbk.size = strlen(ip);
330 		dbk.data = ip;
331 		memset(&dbd, 0, sizeof(dbd));
332 		dbd.size = sizeof(gd);
333 		dbd.data = &gd;
334 		r = db->put(db, &dbk, &dbd, 0);
335 		if (r) {
336 			logmsg(LOG_NOTICE, "db->put failed (%m)");
337 			goto bad;
338 		}
339 	} else {
340 		/* XXX - backwards compat */
341 		if (gdcopyin(&dbd, &gd) == -1) {
342 			/* whatever this is, it doesn't belong */
343 			db->del(db, &dbk, 0);
344 			goto bad;
345 		}
346 		gd.pcount++;
347 		gd.expire = now + whiteexp;
348 		memset(&dbk, 0, sizeof(dbk));
349 		dbk.size = strlen(ip);
350 		dbk.data = ip;
351 		memset(&dbd, 0, sizeof(dbd));
352 		dbd.size = sizeof(gd);
353 		dbd.data = &gd;
354 		r = db->put(db, &dbk, &dbd, 0);
355 		if (r) {
356 			logmsg(LOG_NOTICE, "db->put failed (%m)");
357 			goto bad;
358 		}
359 	}
360 	db->close(db);
361 	db = NULL;
362 	if (syncsend)
363 		sync_white(now, now + whiteexp, ip);
364 	return (0);
365  bad:
366 	db->close(db);
367 	db = NULL;
368 	return (-1);
369 }
370 
371 void
372 usage(void)
373 {
374 	fprintf(stderr,
375 	    "usage: %s [-DI] [-i interface] [-l pflog_interface] "
376 	    "[-W whiteexp] [-Y synctarget]\n",
377 	    __progname);
378 	exit(1);
379 }
380 
381 int
382 main(int argc, char **argv)
383 {
384 	int		 ch;
385 	struct passwd	*pw;
386 	pcap_handler	 phandler = logpkt_handler;
387 	int syncfd = 0;
388 	struct servent *ent;
389 	char *sync_iface = NULL;
390 	char *sync_baddr = NULL;
391 	const char *errstr;
392 
393 	if (geteuid())
394 		errx(1, "need root privileges");
395 
396 	if ((ent = getservbyname("spamd-sync", "udp")) == NULL)
397 		errx(1, "Can't find service \"spamd-sync\" in /etc/services");
398 	sync_port = ntohs(ent->s_port);
399 
400 	while ((ch = getopt(argc, argv, "DIi:l:W:Y:")) != -1) {
401 		switch (ch) {
402 		case 'D':
403 			flag_debug = 1;
404 			break;
405 		case 'I':
406 			flag_inbound = 1;
407 			break;
408 		case 'i':
409 			networkif = optarg;
410 			break;
411 		case 'l':
412 			pflogif = optarg;
413 			break;
414 		case 'W':
415 			/* limit whiteexp to 2160 hours (90 days) */
416 			whiteexp = strtonum(optarg, 1, (24 * 90), &errstr);
417 			if (errstr)
418 				usage();
419 			/* convert to seconds from hours */
420 			whiteexp *= (60 * 60);
421 			break;
422 		case 'Y':
423 			if (sync_addhost(optarg, sync_port) != 0)
424 				sync_iface = optarg;
425 			syncsend++;
426 			break;
427 		default:
428 			usage();
429 		}
430 	}
431 
432 	signal(SIGINT , sighandler_close);
433 	signal(SIGQUIT, sighandler_close);
434 	signal(SIGTERM, sighandler_close);
435 
436 	logmsg(LOG_DEBUG, "Listening on %s for %s %s", pflogif,
437 	    (networkif == NULL) ? "all interfaces." : networkif,
438 	    (flag_inbound) ? "Inbound direction only." : "");
439 
440 	if (init_pcap() == -1)
441 		err(1, "couldn't initialize pcap");
442 
443 	if (syncsend) {
444 		syncfd = sync_init(sync_iface, sync_baddr, sync_port);
445 		if (syncfd == -1)
446 			err(1, "sync init");
447 	}
448 
449 	/* privdrop */
450 	if ((pw = getpwnam(SPAMD_USER)) == NULL)
451 		errx(1, "no such user %s", SPAMD_USER);
452 
453 	if (setgroups(1, &pw->pw_gid) ||
454 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
455 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
456 		err(1, "failed to drop privs");
457 
458 	if (!flag_debug) {
459 		if (daemon(0, 0) == -1)
460 			err(1, "daemon");
461 		tzset();
462 		openlog_r("spamlogd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata);
463 	}
464 
465 	if (unveil(PATH_SPAMD_DB, "rw") == -1)
466 		err(1, "unveil");
467 	if (syncsend) {
468 		if (pledge("stdio rpath wpath inet flock", NULL) == -1)
469 			err(1, "pledge");
470 	} else {
471 		if (pledge("stdio rpath wpath flock", NULL) == -1)
472 			err(1, "pledge");
473 	}
474 
475 	pcap_loop(hpcap, -1, phandler, NULL);
476 
477 	logmsg(LOG_NOTICE, "exiting");
478 	if (!flag_debug)
479 		closelog_r(&sdata);
480 
481 	exit(0);
482 }
483