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