xref: /openbsd-src/libexec/spamd/grey.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$OpenBSD: grey.c,v 1.55 2013/11/27 21:25:25 deraadt Exp $	*/
2 
3 /*
4  * Copyright (c) 2004-2006 Bob Beck.  All rights reserved.
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 #include <sys/ioctl.h>
22 #include <sys/fcntl.h>
23 #include <sys/wait.h>
24 #include <net/if.h>
25 #include <netinet/in.h>
26 #include <net/pfvar.h>
27 #include <arpa/inet.h>
28 #include <ctype.h>
29 #include <db.h>
30 #include <err.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <pwd.h>
34 #include <signal.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <syslog.h>
39 #include <time.h>
40 #include <unistd.h>
41 #include <netdb.h>
42 
43 #include "grey.h"
44 #include "sync.h"
45 
46 extern time_t passtime, greyexp, whiteexp, trapexp;
47 extern struct syslog_data sdata;
48 extern struct passwd *pw;
49 extern u_short cfg_port;
50 extern pid_t jail_pid;
51 extern FILE *trapcfg;
52 extern FILE *grey;
53 extern int debug;
54 extern int syncsend;
55 
56 /* From netinet/in.h, but only _KERNEL_ gets them. */
57 #define satosin(sa)	((struct sockaddr_in *)(sa))
58 #define satosin6(sa)	((struct sockaddr_in6 *)(sa))
59 int server_lookup4(struct sockaddr_in *, struct sockaddr_in *,
60     struct sockaddr_in *);
61 int server_lookup6(struct sockaddr_in6 *, struct sockaddr_in6 *,
62     struct sockaddr_in6 *);
63 
64 void	configure_spamd(char **, size_t, FILE *);
65 int	server_lookup(struct sockaddr *, struct sockaddr *,
66 	    struct sockaddr *);
67 int	configure_pf(char **, int);
68 char	*dequotetolower(const char *);
69 void	readsuffixlists(void);
70 void	freeaddrlists(void);
71 int	addwhiteaddr(char *);
72 int	addtrapaddr(char *);
73 int	db_addrstate(DB *, char *);
74 int	greyscan(char *);
75 int	trapcheck(DB *, char *);
76 int	twupdate(char *, char *, char *, char *, char *);
77 int	twread(char *);
78 int	greyreader(void);
79 void	greyscanner(void);
80 
81 
82 size_t whitecount, whitealloc;
83 size_t trapcount, trapalloc;
84 char **whitelist;
85 char **traplist;
86 
87 char *traplist_name = "spamd-greytrap";
88 char *traplist_msg = "\"Your address %A has mailed to spamtraps here\\n\"";
89 
90 pid_t db_pid = -1;
91 int pfdev;
92 
93 struct db_change {
94 	SLIST_ENTRY(db_change)	entry;
95 	char *			key;
96 	void *			data;
97 	size_t			dsiz;
98 	int			act;
99 };
100 
101 #define DBC_ADD 1
102 #define DBC_DEL 2
103 
104 /* db pending changes list */
105 SLIST_HEAD(, db_change) db_changes = SLIST_HEAD_INITIALIZER(db_changes);
106 
107 struct mail_addr {
108 	SLIST_ENTRY(mail_addr)	entry;
109 	char			addr[MAX_MAIL];
110 };
111 
112 /* list of suffixes that must match TO: */
113 SLIST_HEAD(, mail_addr) match_suffix = SLIST_HEAD_INITIALIZER(match_suffix);
114 char *alloweddomains_file = PATH_SPAMD_ALLOWEDDOMAINS;
115 
116 char *low_prio_mx_ip;
117 time_t startup;
118 
119 static char *pargv[11]= {
120 	"pfctl", "-p", "/dev/pf", "-q", "-t",
121 	"spamd-white", "-T", "replace", "-f", "-", NULL
122 };
123 
124 /* If the parent gets a signal, kill off the children and exit */
125 /* ARGSUSED */
126 static void
127 sig_term_chld(int sig)
128 {
129 	if (db_pid != -1)
130 		kill(db_pid, SIGTERM);
131 	if (jail_pid != -1)
132 		kill(jail_pid, SIGTERM);
133 	_exit(1);
134 }
135 
136 /*
137  * Greatly simplified version from spamd_setup.c  - only
138  * sends one blacklist to an already open stream. Has no need
139  * to collapse cidr ranges since these are only ever single
140  * host hits.
141  */
142 void
143 configure_spamd(char **addrs, size_t count, FILE *sdc)
144 {
145 	size_t i;
146 
147 	fprintf(sdc, "%s;", traplist_name);
148 	if (count != 0) {
149 		fprintf(sdc, "%s;", traplist_msg);
150 		for (i = 0; i < count; i++)
151 			fprintf(sdc, "%s/32;", addrs[i]);
152 	}
153 	fprintf(sdc, "\n");
154 	if (fflush(sdc) == EOF)
155 		syslog_r(LOG_DEBUG, &sdata, "configure_spamd: fflush failed (%m)");
156 }
157 
158 
159 /* Stolen from ftp-proxy */
160 int
161 server_lookup(struct sockaddr *client, struct sockaddr *proxy,
162     struct sockaddr *server)
163 {
164 	if (client->sa_family == AF_INET)
165 		return (server_lookup4(satosin(client), satosin(proxy),
166 		    satosin(server)));
167 
168 	if (client->sa_family == AF_INET6)
169 		return (server_lookup6(satosin6(client), satosin6(proxy),
170 		    satosin6(server)));
171 
172 	errno = EPROTONOSUPPORT;
173 	return (-1);
174 }
175 
176 int
177 server_lookup4(struct sockaddr_in *client, struct sockaddr_in *proxy,
178     struct sockaddr_in *server)
179 {
180 	struct pfioc_natlook pnl;
181 
182 	memset(&pnl, 0, sizeof pnl);
183 	pnl.direction = PF_OUT;
184 	pnl.af = AF_INET;
185 	pnl.proto = IPPROTO_TCP;
186 	memcpy(&pnl.saddr.v4, &client->sin_addr.s_addr, sizeof pnl.saddr.v4);
187 	memcpy(&pnl.daddr.v4, &proxy->sin_addr.s_addr, sizeof pnl.daddr.v4);
188 	pnl.sport = client->sin_port;
189 	pnl.dport = proxy->sin_port;
190 
191 	if (ioctl(pfdev, DIOCNATLOOK, &pnl) == -1)
192 		return (-1);
193 
194 	memset(server, 0, sizeof(struct sockaddr_in));
195 	server->sin_len = sizeof(struct sockaddr_in);
196 	server->sin_family = AF_INET;
197 	memcpy(&server->sin_addr.s_addr, &pnl.rdaddr.v4,
198 	    sizeof server->sin_addr.s_addr);
199 	server->sin_port = pnl.rdport;
200 
201 	return (0);
202 }
203 
204 int
205 server_lookup6(struct sockaddr_in6 *client, struct sockaddr_in6 *proxy,
206     struct sockaddr_in6 *server)
207 {
208 	struct pfioc_natlook pnl;
209 
210 	memset(&pnl, 0, sizeof pnl);
211 	pnl.direction = PF_OUT;
212 	pnl.af = AF_INET6;
213 	pnl.proto = IPPROTO_TCP;
214 	memcpy(&pnl.saddr.v6, &client->sin6_addr.s6_addr, sizeof pnl.saddr.v6);
215 	memcpy(&pnl.daddr.v6, &proxy->sin6_addr.s6_addr, sizeof pnl.daddr.v6);
216 	pnl.sport = client->sin6_port;
217 	pnl.dport = proxy->sin6_port;
218 
219 	if (ioctl(pfdev, DIOCNATLOOK, &pnl) == -1)
220 		return (-1);
221 
222 	memset(server, 0, sizeof(struct sockaddr_in6));
223 	server->sin6_len = sizeof(struct sockaddr_in6);
224 	server->sin6_family = AF_INET6;
225 	memcpy(&server->sin6_addr.s6_addr, &pnl.rdaddr.v6,
226 	    sizeof server->sin6_addr);
227 	server->sin6_port = pnl.rdport;
228 
229 	return (0);
230 }
231 
232 int
233 configure_pf(char **addrs, int count)
234 {
235 	FILE *pf = NULL;
236 	int i, pdes[2], status;
237 	pid_t pid;
238 	char *fdpath;
239 	struct sigaction sa;
240 
241 	sigfillset(&sa.sa_mask);
242 	sa.sa_flags = SA_RESTART;
243 	sa.sa_handler = sig_term_chld;
244 
245 	if (debug)
246 		fprintf(stderr, "configure_pf - device on fd %d\n", pfdev);
247 	if (pfdev < 1 || pfdev > 63)
248 		return(-1);
249 	if (asprintf(&fdpath, "/dev/fd/%d", pfdev) == -1)
250 		return(-1);
251 	pargv[2] = fdpath;
252 	if (pipe(pdes) != 0) {
253 		syslog_r(LOG_INFO, &sdata, "pipe failed (%m)");
254 		free(fdpath);
255 		fdpath = NULL;
256 		return(-1);
257 	}
258 	signal(SIGCHLD, SIG_DFL);
259 	switch (pid = fork()) {
260 	case -1:
261 		syslog_r(LOG_INFO, &sdata, "fork failed (%m)");
262 		free(fdpath);
263 		fdpath = NULL;
264 		close(pdes[0]);
265 		close(pdes[1]);
266 		sigaction(SIGCHLD, &sa, NULL);
267 		return(-1);
268 	case 0:
269 		/* child */
270 		close(pdes[1]);
271 		if (pdes[0] != STDIN_FILENO) {
272 			dup2(pdes[0], STDIN_FILENO);
273 			close(pdes[0]);
274 		}
275 		execvp(PATH_PFCTL, pargv);
276 		syslog_r(LOG_ERR, &sdata, "can't exec %s:%m", PATH_PFCTL);
277 		_exit(1);
278 	}
279 
280 	/* parent */
281 	free(fdpath);
282 	fdpath = NULL;
283 	close(pdes[0]);
284 	pf = fdopen(pdes[1], "w");
285 	if (pf == NULL) {
286 		syslog_r(LOG_INFO, &sdata, "fdopen failed (%m)");
287 		close(pdes[1]);
288 		sigaction(SIGCHLD, &sa, NULL);
289 		return(-1);
290 	}
291 	for (i = 0; i < count; i++)
292 		if (addrs[i] != NULL)
293 			fprintf(pf, "%s/32\n", addrs[i]);
294 	fclose(pf);
295 
296 	waitpid(pid, &status, 0);
297 	if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
298 		syslog_r(LOG_ERR, &sdata, "%s returned status %d", PATH_PFCTL,
299 		    WEXITSTATUS(status));
300 	else if (WIFSIGNALED(status))
301 		syslog_r(LOG_ERR, &sdata, "%s died on signal %d", PATH_PFCTL,
302 		    WTERMSIG(status));
303 
304 	sigaction(SIGCHLD, &sa, NULL);
305 	return(0);
306 }
307 
308 char *
309 dequotetolower(const char *addr)
310 {
311 	static char buf[MAX_MAIL];
312 	char *cp;
313 
314 	if (*addr == '<')
315 		addr++;
316 	(void) strlcpy(buf, addr, sizeof(buf));
317 	cp = strrchr(buf, '>');
318 	if (cp != NULL && cp[1] == '\0')
319 		*cp = '\0';
320 	cp = buf;
321 	while (*cp != '\0') {
322 		*cp = tolower((unsigned char)*cp);
323 		cp++;
324 	}
325 	return(buf);
326 }
327 
328 void
329 readsuffixlists(void)
330 {
331 	FILE *fp;
332 	char *buf;
333 	size_t len;
334 	struct mail_addr *m;
335 
336 	while (!SLIST_EMPTY(&match_suffix)) {
337 		m = SLIST_FIRST(&match_suffix);
338 		SLIST_REMOVE_HEAD(&match_suffix, entry);
339 		free(m);
340 	}
341 	if ((fp = fopen(alloweddomains_file, "r")) != NULL) {
342 		while ((buf = fgetln(fp, &len))) {
343 			/* strip white space-characters */
344 			while (len > 0 && isspace((unsigned char)buf[len-1]))
345 				len--;
346 			while (len > 0 && isspace((unsigned char)*buf)) {
347 				buf++;
348 				len--;
349 			}
350 			if (len == 0)
351 				continue;
352 			/* jump over comments and blank lines */
353 			if (*buf == '#' || *buf == '\n')
354 				continue;
355 			if (buf[len-1] == '\n')
356 				len--;
357 			if ((len + 1) > sizeof(m->addr)) {
358 				syslog_r(LOG_ERR, &sdata,
359 				    "line too long in %s - file ignored",
360 				    alloweddomains_file);
361 				goto bad;
362 			}
363 			if ((m = malloc(sizeof(struct mail_addr))) == NULL)
364 				goto bad;
365 			memcpy(m->addr, buf, len);
366 			m->addr[len]='\0';
367 			syslog_r(LOG_ERR, &sdata, "got suffix %s", m->addr);
368 			SLIST_INSERT_HEAD(&match_suffix, m, entry);
369 		}
370 	}
371 	return;
372 bad:
373 	while (!SLIST_EMPTY(&match_suffix)) {
374 	  	m = SLIST_FIRST(&match_suffix);
375 		SLIST_REMOVE_HEAD(&match_suffix, entry);
376 		free(m);
377 	}
378 }
379 
380 void
381 freeaddrlists(void)
382 {
383 	int i;
384 
385 	if (whitelist != NULL)
386 		for (i = 0; i < whitecount; i++) {
387 			free(whitelist[i]);
388 			whitelist[i] = NULL;
389 		}
390 	whitecount = 0;
391 	if (traplist != NULL) {
392 		for (i = 0; i < trapcount; i++) {
393 			free(traplist[i]);
394 			traplist[i] = NULL;
395 		}
396 	}
397 	trapcount = 0;
398 }
399 
400 /* validate, then add to list of addrs to whitelist */
401 int
402 addwhiteaddr(char *addr)
403 {
404 	struct addrinfo hints, *res;
405 
406 	memset(&hints, 0, sizeof(hints));
407 	hints.ai_family = AF_INET;		/*for now*/
408 	hints.ai_socktype = SOCK_DGRAM;		/*dummy*/
409 	hints.ai_protocol = IPPROTO_UDP;	/*dummy*/
410 	hints.ai_flags = AI_NUMERICHOST;
411 
412 	if (getaddrinfo(addr, NULL, &hints, &res) == 0) {
413 		if (whitecount == whitealloc) {
414 			char **tmp;
415 
416 			tmp = realloc(whitelist,
417 			    (whitealloc + 1024) * sizeof(char *));
418 			if (tmp == NULL) {
419 				freeaddrinfo(res);
420 				return(-1);
421 			}
422 			whitelist = tmp;
423 			whitealloc += 1024;
424 		}
425 		whitelist[whitecount] = strdup(addr);
426 		if (whitelist[whitecount] == NULL) {
427 			freeaddrinfo(res);
428 			return(-1);
429 		}
430 		whitecount++;
431 		freeaddrinfo(res);
432 	} else
433 		return(-1);
434 	return(0);
435 }
436 
437 /* validate, then add to list of addrs to traplist */
438 int
439 addtrapaddr(char *addr)
440 {
441 	struct addrinfo hints, *res;
442 
443 	memset(&hints, 0, sizeof(hints));
444 	hints.ai_family = AF_INET;		/*for now*/
445 	hints.ai_socktype = SOCK_DGRAM;		/*dummy*/
446 	hints.ai_protocol = IPPROTO_UDP;	/*dummy*/
447 	hints.ai_flags = AI_NUMERICHOST;
448 
449 	if (getaddrinfo(addr, NULL, &hints, &res) == 0) {
450 		if (trapcount == trapalloc) {
451 			char **tmp;
452 
453 			tmp = realloc(traplist,
454 			    (trapalloc + 1024) * sizeof(char *));
455 			if (tmp == NULL) {
456 				freeaddrinfo(res);
457 				return(-1);
458 			}
459 			traplist = tmp;
460 			trapalloc += 1024;
461 		}
462 		traplist[trapcount] = strdup(addr);
463 		if (traplist[trapcount] == NULL) {
464 			freeaddrinfo(res);
465 			return(-1);
466 		}
467 		trapcount++;
468 		freeaddrinfo(res);
469 	} else
470 		return(-1);
471 	return(0);
472 }
473 
474 static int
475 queue_change(char *key, char *data, size_t dsiz, int act)
476 {
477 	struct db_change *dbc;
478 
479 	if ((dbc = malloc(sizeof(*dbc))) == NULL) {
480 		syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
481 		return(-1);
482 	}
483 	if ((dbc->key = strdup(key)) == NULL) {
484 		syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
485 		free(dbc);
486 		return(-1);
487 	}
488 	if ((dbc->data = malloc(dsiz)) == NULL) {
489 		syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
490 		free(dbc->key);
491 		free(dbc);
492 		return(-1);
493 	}
494 	memcpy(dbc->data, data, dsiz);
495 	dbc->dsiz = dsiz;
496 	dbc->act = act;
497 	syslog_r(LOG_DEBUG, &sdata,
498 	    "queueing %s of %s", ((act == DBC_ADD) ? "add" : "deletion"),
499 	    dbc->key);
500 	SLIST_INSERT_HEAD(&db_changes, dbc, entry);
501 	return(0);
502 }
503 
504 static int
505 do_changes(DB *db)
506 {
507 	DBT			dbk, dbd;
508 	struct db_change	*dbc;
509 	int ret = 0;
510 
511 	while (!SLIST_EMPTY(&db_changes)) {
512 		dbc = SLIST_FIRST(&db_changes);
513 		switch (dbc->act) {
514 		case DBC_ADD:
515 			memset(&dbk, 0, sizeof(dbk));
516 			dbk.size = strlen(dbc->key);
517 			dbk.data = dbc->key;
518 			memset(&dbd, 0, sizeof(dbd));
519 			dbd.size = dbc->dsiz;
520 			dbd.data = dbc->data;
521 			if (db->put(db, &dbk, &dbd, 0)) {
522 				db->sync(db, 0);
523 				syslog_r(LOG_ERR, &sdata,
524 				    "can't add %s to spamd db (%m)", dbc->key);
525 				ret = -1;
526 			}
527 			db->sync(db, 0);
528 			break;
529 		case DBC_DEL:
530 			memset(&dbk, 0, sizeof(dbk));
531 			dbk.size = strlen(dbc->key);
532 			dbk.data = dbc->key;
533 			if (db->del(db, &dbk, 0)) {
534 				syslog_r(LOG_ERR, &sdata,
535 				    "can't delete %s from spamd db (%m)",
536 				    dbc->key);
537 				ret = -1;
538 			}
539 			break;
540 		default:
541 			syslog_r(LOG_ERR, &sdata, "Unrecognized db change");
542 			ret = -1;
543 		}
544 		free(dbc->key);
545 		dbc->key = NULL;
546 		free(dbc->data);
547 		dbc->data = NULL;
548 		dbc->act = 0;
549 		dbc->dsiz = 0;
550 		SLIST_REMOVE_HEAD(&db_changes, entry);
551 		free(dbc);
552 
553 	}
554 	return(ret);
555 }
556 
557 /* -1=error, 0=notfound, 1=TRAPPED, 2=WHITE */
558 int
559 db_addrstate(DB *db, char *key)
560 {
561 	DBT			dbk, dbd;
562 	struct gdata		gd;
563 
564 	memset(&dbk, 0, sizeof(dbk));
565 	dbk.size = strlen(key);
566 	dbk.data = key;
567 	memset(&dbd, 0, sizeof(dbd));
568 	switch (db->get(db, &dbk, &dbd, 0)) {
569 	case 1:
570 		/* not found */
571 		return (0);
572 	case 0:
573 		if (gdcopyin(&dbd, &gd) != -1)
574 			return (gd.pcount == -1 ? 1 : 2);
575 		/* FALLTHROUGH */
576 	default:
577 		/* error */
578 		return (-1);
579 	}
580 }
581 
582 
583 int
584 greyscan(char *dbname)
585 {
586 	HASHINFO	hashinfo;
587 	DBT		dbk, dbd;
588 	DB		*db;
589 	struct gdata	gd;
590 	int		r;
591 	char		*a = NULL;
592 	size_t		asiz = 0;
593 	time_t now = time(NULL);
594 
595 	/* walk db, expire, and whitelist */
596 	memset(&hashinfo, 0, sizeof(hashinfo));
597 	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
598 	if (db == NULL) {
599 		syslog_r(LOG_INFO, &sdata, "dbopen failed (%m)");
600 		return(-1);
601 	}
602 	memset(&dbk, 0, sizeof(dbk));
603 	memset(&dbd, 0, sizeof(dbd));
604 	for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
605 	    r = db->seq(db, &dbk, &dbd, R_NEXT)) {
606 		if ((dbk.size < 1) || gdcopyin(&dbd, &gd) == -1) {
607 			syslog_r(LOG_ERR, &sdata, "bogus entry in spamd database");
608 			goto bad;
609 		}
610 		if (asiz < dbk.size + 1) {
611 			char *tmp;
612 
613 			tmp = realloc(a, dbk.size * 2);
614 			if (tmp == NULL)
615 				goto bad;
616 			a = tmp;
617 			asiz = dbk.size * 2;
618 		}
619 		memset(a, 0, asiz);
620 		memcpy(a, dbk.data, dbk.size);
621 		if (gd.expire <= now && gd.pcount != -2) {
622 			/* get rid of entry */
623 			if (queue_change(a, NULL, 0, DBC_DEL) == -1)
624 				goto bad;
625 		} else if (gd.pcount == -1)  {
626 			/* this is a greytrap hit */
627 			if ((addtrapaddr(a) == -1) &&
628 			    (queue_change(a, NULL, 0, DBC_DEL) == -1))
629 				goto bad;
630 		} else if (gd.pcount >= 0 && gd.pass <= now) {
631 			int tuple = 0;
632 			char *cp;
633 			int state;
634 
635 			/*
636 			 * if not already TRAPPED,
637 			 * add address to whitelist
638 			 * add an address-keyed entry to db
639 			 */
640 			cp = strchr(a, '\n');
641 			if (cp != NULL) {
642 				tuple = 1;
643 				*cp = '\0';
644 			}
645 
646 			state = db_addrstate(db, a);
647 			if (state != 1 && addwhiteaddr(a) == -1) {
648 				if (cp != NULL)
649 					*cp = '\n';
650 				if (queue_change(a, NULL, 0, DBC_DEL) == -1)
651 					goto bad;
652 			}
653 
654 			if (tuple && state <= 0) {
655 				if (cp != NULL)
656 					*cp = '\0';
657 				/* re-add entry, keyed only by ip */
658 				gd.expire = now + whiteexp;
659 				dbd.size = sizeof(gd);
660 				dbd.data = &gd;
661 				if (queue_change(a, (void *) &gd, sizeof(gd),
662 				    DBC_ADD) == -1)
663 					goto bad;
664 				syslog_r(LOG_DEBUG, &sdata,
665 				    "whitelisting %s in %s", a, dbname);
666 			}
667 			if (debug)
668 				fprintf(stderr, "whitelisted %s\n", a);
669 		}
670 	}
671 	(void) do_changes(db);
672 	db->close(db);
673 	db = NULL;
674 	configure_pf(whitelist, whitecount);
675 	configure_spamd(traplist, trapcount, trapcfg);
676 
677 	freeaddrlists();
678 	free(a);
679 	a = NULL;
680 	asiz = 0;
681 	return(0);
682  bad:
683 	(void) do_changes(db);
684 	db->close(db);
685 	db = NULL;
686 	freeaddrlists();
687 	free(a);
688 	a = NULL;
689 	asiz = 0;
690 	return(-1);
691 }
692 
693 int
694 trapcheck(DB *db, char *to)
695 {
696 	int			i, j, smatch = 0;
697 	DBT			dbk, dbd;
698 	struct mail_addr	*m;
699 	char *			trap;
700 	size_t			s;
701 
702 	trap = dequotetolower(to);
703 	if (!SLIST_EMPTY(&match_suffix)) {
704 		s = strlen(trap);
705 		SLIST_FOREACH(m, &match_suffix, entry) {
706 			j = s - strlen(m->addr);
707 			if ((j >= 0) && (strcasecmp(trap+j, m->addr) == 0))
708 				smatch = 1;
709 		}
710 		if (!smatch)
711 			/* no suffixes match, so trap it */
712 			return (0);
713 	}
714 	memset(&dbk, 0, sizeof(dbk));
715 	dbk.size = strlen(trap);
716 	dbk.data = trap;
717 	memset(&dbd, 0, sizeof(dbd));
718 	i = db->get(db, &dbk, &dbd, 0);
719 	if (i == -1)
720 		return (-1);
721 	if (i)
722 		/* didn't exist - so this doesn't match a known spamtrap  */
723 		return (1);
724 	else
725 		/* To: address is a spamtrap, so add as a greytrap entry */
726 		return (0);
727 }
728 
729 int
730 twupdate(char *dbname, char *what, char *ip, char *source, char *expires)
731 {
732 	/* we got a TRAP or WHITE update from someone else */
733 	HASHINFO	hashinfo;
734 	DBT		dbk, dbd;
735 	DB		*db;
736 	struct gdata	gd;
737 	time_t		now, expire;
738 	int		r, spamtrap;
739 
740 	now = time(NULL);
741 	/* expiry times have to be in the future */
742 	expire = strtonum(expires, now,
743 	    sizeof(time_t) == sizeof(int) ? INT_MAX : LLONG_MAX, NULL);
744 	if (expire == 0)
745 		return(-1);
746 
747 	if (strcmp(what, "TRAP") == 0)
748 		spamtrap = 1;
749 	else if (strcmp(what, "WHITE") == 0)
750 		spamtrap = 0;
751 	else
752 		return(-1);
753 
754 	memset(&hashinfo, 0, sizeof(hashinfo));
755 	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
756 	if (db == NULL)
757 		return(-1);
758 
759 	memset(&dbk, 0, sizeof(dbk));
760 	dbk.size = strlen(ip);
761 	dbk.data = ip;
762 	memset(&dbd, 0, sizeof(dbd));
763 	r = db->get(db, &dbk, &dbd, 0);
764 	if (r == -1)
765 		goto bad;
766 	if (r) {
767 		/* new entry */
768 		memset(&gd, 0, sizeof(gd));
769 		gd.first = now;
770 		gd.pcount = spamtrap ? -1 : 0;
771 		gd.expire = expire;
772 		memset(&dbk, 0, sizeof(dbk));
773 		dbk.size = strlen(ip);
774 		dbk.data = ip;
775 		memset(&dbd, 0, sizeof(dbd));
776 		dbd.size = sizeof(gd);
777 		dbd.data = &gd;
778 		r = db->put(db, &dbk, &dbd, 0);
779 		db->sync(db, 0);
780 		if (r)
781 			goto bad;
782 		if (debug)
783 			fprintf(stderr, "added %s %s\n",
784 			    spamtrap ? "trap entry for" : "", ip);
785 		syslog_r(LOG_DEBUG, &sdata,
786 		    "new %s from %s for %s, expires %s", what, source, ip,
787 		    expires);
788 	} else {
789 		/* existing entry */
790 		if (gdcopyin(&dbd, &gd) == -1) {
791 			/* whatever this is, it doesn't belong */
792 			db->del(db, &dbk, 0);
793 			db->sync(db, 0);
794 			goto bad;
795 		}
796 		if (spamtrap) {
797 			gd.pcount = -1;
798 			gd.bcount++;
799 		} else
800 			gd.pcount++;
801 		memset(&dbk, 0, sizeof(dbk));
802 		dbk.size = strlen(ip);
803 		dbk.data = ip;
804 		memset(&dbd, 0, sizeof(dbd));
805 		dbd.size = sizeof(gd);
806 		dbd.data = &gd;
807 		r = db->put(db, &dbk, &dbd, 0);
808 		db->sync(db, 0);
809 		if (r)
810 			goto bad;
811 		if (debug)
812 			fprintf(stderr, "updated %s\n", ip);
813 	}
814 	db->close(db);
815 	return(0);
816  bad:
817 	db->close(db);
818 	return(-1);
819 
820 }
821 
822 int
823 greyupdate(char *dbname, char *helo, char *ip, char *from, char *to, int sync,
824     char *cip)
825 {
826 	HASHINFO	hashinfo;
827 	DBT		dbk, dbd;
828 	DB		*db;
829 	char		*key = NULL;
830 	char		*lookup;
831 	struct gdata	gd;
832 	time_t		now, expire;
833 	int		r, spamtrap;
834 
835 	now = time(NULL);
836 
837 	/* open with lock, find record, update, close, unlock */
838 	memset(&hashinfo, 0, sizeof(hashinfo));
839 	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
840 	if (db == NULL)
841 		return(-1);
842 	if (asprintf(&key, "%s\n%s\n%s\n%s", ip, helo, from, to) == -1)
843 		goto bad;
844 	r = trapcheck(db, to);
845 	switch (r) {
846 	case 1:
847 		/* do not trap */
848 		spamtrap = 0;
849 		lookup = key;
850 		expire = greyexp;
851 		break;
852 	case 0:
853 		/* trap */
854 		spamtrap = 1;
855 		lookup = ip;
856 		expire = trapexp;
857 		syslog_r(LOG_DEBUG, &sdata, "Trapping %s for tuple %s", ip,
858 		    key);
859 		break;
860 	default:
861 		goto bad;
862 		break;
863 	}
864 	memset(&dbk, 0, sizeof(dbk));
865 	dbk.size = strlen(lookup);
866 	dbk.data = lookup;
867 	memset(&dbd, 0, sizeof(dbd));
868 	r = db->get(db, &dbk, &dbd, 0);
869 	if (r == -1)
870 		goto bad;
871 	if (r) {
872 		/* new entry */
873 		if (sync &&  low_prio_mx_ip &&
874 		    (strcmp(cip, low_prio_mx_ip) == 0) &&
875 		    ((startup + 60)  < now)) {
876 			/* we haven't seen a greylist entry for this tuple,
877 			 * and yet the connection was to a low priority MX
878 			 * which we know can't be hit first if the client
879 			 * is adhering to the RFC's - soo.. kill it!
880 			 */
881 			spamtrap = 1;
882 			lookup = ip;
883 			expire = trapexp;
884 			syslog_r(LOG_DEBUG, &sdata,
885 			    "Trapping %s for trying %s first for tuple %s",
886 			    ip, low_prio_mx_ip, key);
887 		}
888 		memset(&gd, 0, sizeof(gd));
889 		gd.first = now;
890 		gd.bcount = 1;
891 		gd.pcount = spamtrap ? -1 : 0;
892 		gd.pass = now + expire;
893 		gd.expire = now + expire;
894 		memset(&dbk, 0, sizeof(dbk));
895 		dbk.size = strlen(lookup);
896 		dbk.data = lookup;
897 		memset(&dbd, 0, sizeof(dbd));
898 		dbd.size = sizeof(gd);
899 		dbd.data = &gd;
900 		r = db->put(db, &dbk, &dbd, 0);
901 		db->sync(db, 0);
902 		if (r)
903 			goto bad;
904 		if (debug)
905 			fprintf(stderr, "added %s %s\n",
906 			    spamtrap ? "greytrap entry for" : "", lookup);
907 		syslog_r(LOG_DEBUG, &sdata,
908 		    "new %sentry %s from %s to %s, helo %s",
909 		    spamtrap ? "greytrap " : "", ip, from, to, helo);
910 	} else {
911 		/* existing entry */
912 		if (gdcopyin(&dbd, &gd) == -1) {
913 			/* whatever this is, it doesn't belong */
914 			db->del(db, &dbk, 0);
915 			db->sync(db, 0);
916 			goto bad;
917 		}
918 		gd.bcount++;
919 		gd.pcount = spamtrap ? -1 : 0;
920 		if (gd.first + passtime < now)
921 			gd.pass = now;
922 		memset(&dbk, 0, sizeof(dbk));
923 		dbk.size = strlen(lookup);
924 		dbk.data = lookup;
925 		memset(&dbd, 0, sizeof(dbd));
926 		dbd.size = sizeof(gd);
927 		dbd.data = &gd;
928 		r = db->put(db, &dbk, &dbd, 0);
929 		db->sync(db, 0);
930 		if (r)
931 			goto bad;
932 		if (debug)
933 			fprintf(stderr, "updated %s\n", lookup);
934 	}
935 	free(key);
936 	key = NULL;
937 	db->close(db);
938 	db = NULL;
939 
940 	/* Entry successfully update, sent out sync message */
941 	if (syncsend && sync) {
942 		if (spamtrap) {
943 			syslog_r(LOG_DEBUG, &sdata,
944 			    "sync_trap %s", ip);
945 			sync_trapped(now, now + expire, ip);
946 		}
947 		else
948 			sync_update(now, helo, ip, from, to);
949 	}
950 	return(0);
951  bad:
952 	free(key);
953 	key = NULL;
954 	db->close(db);
955 	db = NULL;
956 	return(-1);
957 }
958 
959 int
960 twread(char *buf)
961 {
962 	if ((strncmp(buf, "WHITE:", 6) == 0) ||
963 	    (strncmp(buf, "TRAP:", 5) == 0)) {
964 		char **ap, *argv[5];
965 		int argc = 0;
966 
967 		for (ap = argv;
968 		    ap < &argv[4] && (*ap = strsep(&buf, ":")) != NULL;) {
969 			if (**ap != '\0')
970 				ap++;
971 			argc++;
972 		}
973 		*ap = NULL;
974 		if (argc != 4)
975 			return (-1);
976 		twupdate(PATH_SPAMD_DB, argv[0], argv[1], argv[2], argv[3]);
977 		return (0);
978 	} else
979 		return (-1);
980 }
981 
982 int
983 greyreader(void)
984 {
985 	char cip[32], ip[32], helo[MAX_MAIL], from[MAX_MAIL], to[MAX_MAIL];
986 	char *buf;
987 	size_t len;
988 	int state, sync;
989 	struct addrinfo hints, *res;
990 
991 	memset(&hints, 0, sizeof(hints));
992 	hints.ai_family = AF_INET;		/*for now*/
993 	hints.ai_socktype = SOCK_DGRAM;		/*dummy*/
994 	hints.ai_protocol = IPPROTO_UDP;	/*dummy*/
995 	hints.ai_flags = AI_NUMERICHOST;
996 
997 	state = 0;
998 	sync = 1;
999 	if (grey == NULL) {
1000 		syslog_r(LOG_ERR, &sdata, "No greylist pipe stream!\n");
1001 		return (-1);
1002 	}
1003 
1004 	/* grab trap suffixes */
1005 	readsuffixlists();
1006 
1007 	while ((buf = fgetln(grey, &len))) {
1008 		if (buf[len - 1] == '\n')
1009 			buf[len - 1] = '\0';
1010 		else
1011 			/* all valid lines end in \n */
1012 			continue;
1013 		if (strlen(buf) < 4)
1014 			continue;
1015 
1016 		if (strcmp(buf, "SYNC") == 0) {
1017 			sync = 0;
1018 			continue;
1019 		}
1020 
1021 		switch (state) {
1022 		case 0:
1023 			if (twread(buf) == 0) {
1024 				state = 0;
1025 				break;
1026 			}
1027 			if (strncmp(buf, "HE:", 3) != 0) {
1028 				if (strncmp(buf, "CO:", 3) == 0)
1029 					strlcpy(cip, buf+3, sizeof(cip));
1030 				state = 0;
1031 				break;
1032 			}
1033 			strlcpy(helo, buf+3, sizeof(helo));
1034 			state = 1;
1035 			break;
1036 		case 1:
1037 			if (strncmp(buf, "IP:", 3) != 0)
1038 				break;
1039 			strlcpy(ip, buf+3, sizeof(ip));
1040 			if (getaddrinfo(ip, NULL, &hints, &res) == 0) {
1041 				freeaddrinfo(res);
1042 				state = 2;
1043 			} else
1044 				state = 0;
1045 			break;
1046 		case 2:
1047 			if (strncmp(buf, "FR:", 3) != 0) {
1048 				state = 0;
1049 				break;
1050 			}
1051 			strlcpy(from, buf+3, sizeof(from));
1052 			state = 3;
1053 			break;
1054 		case 3:
1055 			if (strncmp(buf, "TO:", 3) != 0) {
1056 				state = 0;
1057 				break;
1058 			}
1059 			strlcpy(to, buf+3, sizeof(to));
1060 			if (debug)
1061 				fprintf(stderr,
1062 				    "Got Grey HELO %s, IP %s from %s to %s\n",
1063 				    helo, ip, from, to);
1064 			greyupdate(PATH_SPAMD_DB, helo, ip, from, to, sync, cip);
1065 			sync = 1;
1066 			state = 0;
1067 			break;
1068 		}
1069 	}
1070 	return (0);
1071 }
1072 
1073 void
1074 greyscanner(void)
1075 {
1076 	for (;;) {
1077 		if (greyscan(PATH_SPAMD_DB) == -1)
1078 			syslog_r(LOG_NOTICE, &sdata, "scan of %s failed",
1079 			    PATH_SPAMD_DB);
1080 		sleep(DB_SCAN_INTERVAL);
1081 	}
1082 	/* NOTREACHED */
1083 }
1084 
1085 static void
1086 drop_privs(void)
1087 {
1088 	/*
1089 	 * lose root, continue as non-root user
1090 	 */
1091 	if (pw) {
1092 		setgroups(1, &pw->pw_gid);
1093 		setegid(pw->pw_gid);
1094 		setgid(pw->pw_gid);
1095 		seteuid(pw->pw_uid);
1096 		setuid(pw->pw_uid);
1097 	}
1098 }
1099 
1100 static void
1101 check_spamd_db(void)
1102 {
1103 	HASHINFO hashinfo;
1104 	int i = -1;
1105 	DB *db;
1106 
1107 	/* check to see if /var/db/spamd exists, if not, create it */
1108 	memset(&hashinfo, 0, sizeof(hashinfo));
1109 	db = dbopen(PATH_SPAMD_DB, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
1110 
1111 	if (db == NULL) {
1112 		switch (errno) {
1113 		case ENOENT:
1114 			i = open(PATH_SPAMD_DB, O_RDWR|O_CREAT, 0644);
1115 			if (i == -1) {
1116 				syslog_r(LOG_ERR, &sdata,
1117 				    "create %s failed (%m)", PATH_SPAMD_DB);
1118 				exit(1);
1119 			}
1120 			/* if we are dropping privs, chown to that user */
1121 			if (pw && (fchown(i, pw->pw_uid, pw->pw_gid) == -1)) {
1122 				syslog_r(LOG_ERR, &sdata,
1123 				    "chown %s failed (%m)", PATH_SPAMD_DB);
1124 				exit(1);
1125 			}
1126 			close(i);
1127 			drop_privs();
1128 			return;
1129 			break;
1130 		default:
1131 			syslog_r(LOG_ERR, &sdata, "open of %s failed (%m)",
1132 			    PATH_SPAMD_DB);
1133 			exit(1);
1134 		}
1135 	}
1136 	db->sync(db, 0);
1137 	db->close(db);
1138 	drop_privs();
1139 }
1140 
1141 
1142 int
1143 greywatcher(void)
1144 {
1145 	struct sigaction sa;
1146 
1147 	check_spamd_db();
1148 
1149 	startup = time(NULL);
1150 	db_pid = fork();
1151 	switch (db_pid) {
1152 	case -1:
1153 		syslog_r(LOG_ERR, &sdata, "fork failed (%m)");
1154 		exit(1);
1155 	case 0:
1156 		/*
1157 		 * child, talks to jailed spamd over greypipe,
1158 		 * updates db. has no access to pf.
1159 		 */
1160 		close(pfdev);
1161 		setproctitle("(%s update)", PATH_SPAMD_DB);
1162 		if (greyreader() == -1) {
1163 		    syslog_r(LOG_ERR, &sdata, "greyreader failed (%m)");
1164 		    _exit(1);
1165 		}
1166 		_exit(0);
1167 	}
1168 
1169 
1170 	fclose(grey);
1171 	/*
1172 	 * parent, scans db periodically for changes and updates
1173 	 * pf whitelist table accordingly.
1174 	 */
1175 
1176 	sigfillset(&sa.sa_mask);
1177 	sa.sa_flags = SA_RESTART;
1178 	sa.sa_handler = sig_term_chld;
1179 	sigaction(SIGTERM, &sa, NULL);
1180 	sigaction(SIGHUP, &sa, NULL);
1181 	sigaction(SIGCHLD, &sa, NULL);
1182 	sigaction(SIGINT, &sa, NULL);
1183 
1184 	setproctitle("(pf <spamd-white> update)");
1185 	greyscanner();
1186 	/* NOTREACHED */
1187 	exit(1);
1188 }
1189