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