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