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