xref: /openbsd/libexec/spamd/grey.c (revision 5b133f3f)
1 /*	$OpenBSD: grey.c,v 1.67 2023/03/08 04:43:06 guenther Exp $	*/
2 
3 /*
4  * Copyright (c) 2004-2006 Bob Beck.  All rights reserved.
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 #include <sys/ioctl.h>
22 #include <sys/wait.h>
23 #include <net/if.h>
24 #include <netinet/in.h>
25 #include <net/pfvar.h>
26 #include <ctype.h>
27 #include <db.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <pwd.h>
31 #include <signal.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <syslog.h>
36 #include <time.h>
37 #include <unistd.h>
38 #include <netdb.h>
39 
40 #include "grey.h"
41 #include "sync.h"
42 
43 extern time_t passtime, greyexp, whiteexp, trapexp;
44 extern struct syslog_data sdata;
45 extern struct passwd *pw;
46 extern u_short cfg_port;
47 extern pid_t jail_pid;
48 extern FILE *trapcfg;
49 extern FILE *grey;
50 extern int debug;
51 extern int syncsend;
52 extern int greyback[2];
53 
54 /* From netinet/in.h, but only _KERNEL_ gets them. */
55 #define satosin(sa)	((struct sockaddr_in *)(sa))
56 #define satosin6(sa)	((struct sockaddr_in6 *)(sa))
57 
58 void	configure_spamd(char **, u_int, FILE *);
59 int	configure_pf(char **, int);
60 char	*dequotetolower(const char *);
61 void	readsuffixlists(void);
62 void	freeaddrlists(void);
63 int	addwhiteaddr(char *);
64 int	addtrapaddr(char *);
65 int	db_addrstate(DB *, char *);
66 int	greyscan(char *);
67 int	trapcheck(DB *, char *);
68 int	twupdate(char *, char *, char *, char *, char *);
69 int	twread(char *);
70 int	greyreader(void);
71 void	greyscanner(void);
72 
73 
74 u_int whitecount, whitealloc;
75 u_int trapcount, trapalloc;
76 char **whitelist;
77 char **traplist;
78 
79 char *traplist_name = "spamd-greytrap";
80 char *traplist_msg = "\"Your address %A has mailed to spamtraps here\\n\"";
81 
82 pid_t db_pid = -1;
83 int pfdev;
84 
85 struct db_change {
86 	SLIST_ENTRY(db_change)	entry;
87 	char *			key;
88 	void *			data;
89 	size_t			dsiz;
90 	int			act;
91 };
92 
93 #define DBC_ADD 1
94 #define DBC_DEL 2
95 
96 /* db pending changes list */
97 SLIST_HEAD(, db_change) db_changes = SLIST_HEAD_INITIALIZER(db_changes);
98 
99 struct mail_addr {
100 	SLIST_ENTRY(mail_addr)	entry;
101 	char			addr[MAX_MAIL];
102 };
103 
104 /* list of suffixes that must match TO: */
105 SLIST_HEAD(, mail_addr) match_suffix = SLIST_HEAD_INITIALIZER(match_suffix);
106 char *alloweddomains_file = PATH_SPAMD_ALLOWEDDOMAINS;
107 
108 char *low_prio_mx_ip;
109 time_t startup;
110 
111 static char *pargv[11]= {
112 	"pfctl", "-p", "/dev/pf", "-q", "-t",
113 	"spamd-white", "-T", "replace", "-f", "-", NULL
114 };
115 
116 /* If the parent gets a signal, kill off the children and exit */
117 static void
sig_term_chld(int sig)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
configure_spamd(char ** addrs,u_int count,FILE * sdc)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
configure_pf(char ** addrs,int count)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 *
dequotetolower(const char * addr)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
readsuffixlists(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
freeaddrlists(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
addwhiteaddr(char * addr)323 addwhiteaddr(char *addr)
324 {
325 	struct addrinfo hints, *res;
326 	char ch;
327 
328 	memset(&hints, 0, sizeof(hints));
329 	hints.ai_family = AF_INET;		/*for now*/
330 	hints.ai_socktype = SOCK_DGRAM;		/*dummy*/
331 	hints.ai_protocol = IPPROTO_UDP;	/*dummy*/
332 	hints.ai_flags = AI_NUMERICHOST;
333 
334 	if (getaddrinfo(addr, NULL, &hints, &res) != 0)
335 		return(-1);
336 
337 	/* Check spamd blacklists in main process. */
338 	if (send(greyback[0], res->ai_addr, res->ai_addr->sa_len, 0) == -1) {
339 		syslog_r(LOG_ERR, &sdata, "%s: send: %m", __func__);
340 	} else {
341 		if (recv(greyback[0], &ch, sizeof(ch), 0) == 1) {
342 			if (ch == '1') {
343 				syslog_r(LOG_DEBUG, &sdata,
344 				    "%s blacklisted, removing from whitelist",
345 				    addr);
346 				freeaddrinfo(res);
347 				return(-1);
348 			}
349 		}
350 	}
351 
352 	if (whitecount == whitealloc) {
353 		char **tmp;
354 
355 		tmp = reallocarray(whitelist,
356 		    whitealloc + 1024, sizeof(char *));
357 		if (tmp == NULL) {
358 			freeaddrinfo(res);
359 			return(-1);
360 		}
361 		whitelist = tmp;
362 		whitealloc += 1024;
363 	}
364 	whitelist[whitecount] = strdup(addr);
365 	if (whitelist[whitecount] == NULL) {
366 		freeaddrinfo(res);
367 		return(-1);
368 	}
369 	whitecount++;
370 	freeaddrinfo(res);
371 	return(0);
372 }
373 
374 /* validate, then add to list of addrs to traplist */
375 int
addtrapaddr(char * addr)376 addtrapaddr(char *addr)
377 {
378 	struct addrinfo hints, *res;
379 
380 	memset(&hints, 0, sizeof(hints));
381 	hints.ai_family = AF_INET;		/*for now*/
382 	hints.ai_socktype = SOCK_DGRAM;		/*dummy*/
383 	hints.ai_protocol = IPPROTO_UDP;	/*dummy*/
384 	hints.ai_flags = AI_NUMERICHOST;
385 
386 	if (getaddrinfo(addr, NULL, &hints, &res) == 0) {
387 		if (trapcount == trapalloc) {
388 			char **tmp;
389 
390 			tmp = reallocarray(traplist,
391 			    trapalloc + 1024, sizeof(char *));
392 			if (tmp == NULL) {
393 				freeaddrinfo(res);
394 				return(-1);
395 			}
396 			traplist = tmp;
397 			trapalloc += 1024;
398 		}
399 		traplist[trapcount] = strdup(addr);
400 		if (traplist[trapcount] == NULL) {
401 			freeaddrinfo(res);
402 			return(-1);
403 		}
404 		trapcount++;
405 		freeaddrinfo(res);
406 	} else
407 		return(-1);
408 	return(0);
409 }
410 
411 static int
queue_change(char * key,char * data,size_t dsiz,int act)412 queue_change(char *key, char *data, size_t dsiz, int act)
413 {
414 	struct db_change *dbc;
415 
416 	if ((dbc = malloc(sizeof(*dbc))) == NULL) {
417 		syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
418 		return(-1);
419 	}
420 	if ((dbc->key = strdup(key)) == NULL) {
421 		syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
422 		free(dbc);
423 		return(-1);
424 	}
425 	if ((dbc->data = malloc(dsiz)) == NULL) {
426 		syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
427 		free(dbc->key);
428 		free(dbc);
429 		return(-1);
430 	}
431 	memcpy(dbc->data, data, dsiz);
432 	dbc->dsiz = dsiz;
433 	dbc->act = act;
434 	syslog_r(LOG_DEBUG, &sdata,
435 	    "queueing %s of %s", ((act == DBC_ADD) ? "add" : "deletion"),
436 	    dbc->key);
437 	SLIST_INSERT_HEAD(&db_changes, dbc, entry);
438 	return(0);
439 }
440 
441 static int
do_changes(DB * db)442 do_changes(DB *db)
443 {
444 	DBT			dbk, dbd;
445 	struct db_change	*dbc;
446 	int ret = 0;
447 
448 	while (!SLIST_EMPTY(&db_changes)) {
449 		dbc = SLIST_FIRST(&db_changes);
450 		switch (dbc->act) {
451 		case DBC_ADD:
452 			memset(&dbk, 0, sizeof(dbk));
453 			dbk.size = strlen(dbc->key);
454 			dbk.data = dbc->key;
455 			memset(&dbd, 0, sizeof(dbd));
456 			dbd.size = dbc->dsiz;
457 			dbd.data = dbc->data;
458 			if (db->put(db, &dbk, &dbd, 0)) {
459 				db->sync(db, 0);
460 				syslog_r(LOG_ERR, &sdata,
461 				    "can't add %s to spamd db (%m)", dbc->key);
462 				ret = -1;
463 			}
464 			db->sync(db, 0);
465 			break;
466 		case DBC_DEL:
467 			memset(&dbk, 0, sizeof(dbk));
468 			dbk.size = strlen(dbc->key);
469 			dbk.data = dbc->key;
470 			if (db->del(db, &dbk, 0)) {
471 				syslog_r(LOG_ERR, &sdata,
472 				    "can't delete %s from spamd db (%m)",
473 				    dbc->key);
474 				ret = -1;
475 			}
476 			break;
477 		default:
478 			syslog_r(LOG_ERR, &sdata, "Unrecognized db change");
479 			ret = -1;
480 		}
481 		free(dbc->key);
482 		dbc->key = NULL;
483 		free(dbc->data);
484 		dbc->data = NULL;
485 		dbc->act = 0;
486 		dbc->dsiz = 0;
487 		SLIST_REMOVE_HEAD(&db_changes, entry);
488 		free(dbc);
489 
490 	}
491 	return(ret);
492 }
493 
494 /* -1=error, 0=notfound, 1=TRAPPED, 2=WHITE */
495 int
db_addrstate(DB * db,char * key)496 db_addrstate(DB *db, char *key)
497 {
498 	DBT			dbk, dbd;
499 	struct gdata		gd;
500 
501 	memset(&dbk, 0, sizeof(dbk));
502 	dbk.size = strlen(key);
503 	dbk.data = key;
504 	memset(&dbd, 0, sizeof(dbd));
505 	switch (db->get(db, &dbk, &dbd, 0)) {
506 	case 1:
507 		/* not found */
508 		return (0);
509 	case 0:
510 		if (gdcopyin(&dbd, &gd) != -1)
511 			return (gd.pcount == -1 ? 1 : 2);
512 		/* FALLTHROUGH */
513 	default:
514 		/* error */
515 		return (-1);
516 	}
517 }
518 
519 
520 int
greyscan(char * dbname)521 greyscan(char *dbname)
522 {
523 	HASHINFO	hashinfo;
524 	DBT		dbk, dbd;
525 	DB		*db;
526 	struct gdata	gd;
527 	int		r;
528 	char		*a = NULL;
529 	size_t		asiz = 0;
530 	time_t now = time(NULL);
531 
532 	/* walk db, expire, and whitelist */
533 	memset(&hashinfo, 0, sizeof(hashinfo));
534 	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
535 	if (db == NULL) {
536 		syslog_r(LOG_INFO, &sdata, "dbopen failed (%m)");
537 		return(-1);
538 	}
539 	memset(&dbk, 0, sizeof(dbk));
540 	memset(&dbd, 0, sizeof(dbd));
541 	for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
542 	    r = db->seq(db, &dbk, &dbd, R_NEXT)) {
543 		if ((dbk.size < 1) || gdcopyin(&dbd, &gd) == -1) {
544 			syslog_r(LOG_ERR, &sdata, "bogus entry in spamd database");
545 			goto bad;
546 		}
547 		if (asiz < dbk.size + 1) {
548 			char *tmp;
549 
550 			tmp = reallocarray(a, dbk.size, 2);
551 			if (tmp == NULL)
552 				goto bad;
553 			a = tmp;
554 			asiz = dbk.size * 2;
555 		}
556 		memset(a, 0, asiz);
557 		memcpy(a, dbk.data, dbk.size);
558 		if (gd.expire <= now && gd.pcount != -2) {
559 			/* get rid of entry */
560 			if (queue_change(a, NULL, 0, DBC_DEL) == -1)
561 				goto bad;
562 		} else if (gd.pcount == -1)  {
563 			/* this is a greytrap hit */
564 			if ((addtrapaddr(a) == -1) &&
565 			    (queue_change(a, NULL, 0, DBC_DEL) == -1))
566 				goto bad;
567 		} else if (gd.pcount >= 0 && gd.pass <= now) {
568 			int tuple = 0;
569 			char *cp;
570 			int state;
571 
572 			/*
573 			 * if not already TRAPPED,
574 			 * add address to whitelist
575 			 * add an address-keyed entry to db
576 			 */
577 			cp = strchr(a, '\n');
578 			if (cp != NULL) {
579 				tuple = 1;
580 				*cp = '\0';
581 			}
582 
583 			state = db_addrstate(db, a);
584 			if (state != 1 && addwhiteaddr(a) == -1) {
585 				if (cp != NULL)
586 					*cp = '\n';
587 				if (queue_change(a, NULL, 0, DBC_DEL) == -1)
588 					goto bad;
589 			}
590 
591 			if (tuple && state <= 0) {
592 				if (cp != NULL)
593 					*cp = '\0';
594 				/* re-add entry, keyed only by ip */
595 				gd.expire = now + whiteexp;
596 				dbd.size = sizeof(gd);
597 				dbd.data = &gd;
598 				if (queue_change(a, (void *) &gd, sizeof(gd),
599 				    DBC_ADD) == -1)
600 					goto bad;
601 				syslog_r(LOG_DEBUG, &sdata,
602 				    "whitelisting %s in %s", a, dbname);
603 			}
604 			if (debug)
605 				fprintf(stderr, "whitelisted %s\n", a);
606 		}
607 	}
608 	(void) do_changes(db);
609 	db->close(db);
610 	db = NULL;
611 	configure_pf(whitelist, whitecount);
612 	configure_spamd(traplist, trapcount, trapcfg);
613 
614 	freeaddrlists();
615 	free(a);
616 	a = NULL;
617 	return(0);
618  bad:
619 	(void) do_changes(db);
620 	db->close(db);
621 	db = NULL;
622 	freeaddrlists();
623 	free(a);
624 	a = NULL;
625 	return(-1);
626 }
627 
628 int
trapcheck(DB * db,char * to)629 trapcheck(DB *db, char *to)
630 {
631 	int			i, j, smatch = 0;
632 	DBT			dbk, dbd;
633 	struct mail_addr	*m;
634 	char *			trap;
635 	size_t			s;
636 
637 	trap = dequotetolower(to);
638 	if (!SLIST_EMPTY(&match_suffix)) {
639 		s = strlen(trap);
640 		SLIST_FOREACH(m, &match_suffix, entry) {
641 			j = s - strlen(m->addr);
642 			if ((j >= 0) && (strcasecmp(trap+j, m->addr) == 0))
643 				smatch = 1;
644 		}
645 		if (!smatch)
646 			/* no suffixes match, so trap it */
647 			return (0);
648 	}
649 	memset(&dbk, 0, sizeof(dbk));
650 	dbk.size = strlen(trap);
651 	dbk.data = trap;
652 	memset(&dbd, 0, sizeof(dbd));
653 	i = db->get(db, &dbk, &dbd, 0);
654 	if (i == -1)
655 		return (-1);
656 	if (i)
657 		/* didn't exist - so this doesn't match a known spamtrap  */
658 		return (1);
659 	else
660 		/* To: address is a spamtrap, so add as a greytrap entry */
661 		return (0);
662 }
663 
664 int
twupdate(char * dbname,char * what,char * ip,char * source,char * expires)665 twupdate(char *dbname, char *what, char *ip, char *source, char *expires)
666 {
667 	/* we got a TRAP or WHITE update from someone else */
668 	HASHINFO	hashinfo;
669 	DBT		dbk, dbd;
670 	DB		*db;
671 	struct gdata	gd;
672 	time_t		now, expire;
673 	int		r, spamtrap;
674 
675 	now = time(NULL);
676 	/* expiry times have to be in the future */
677 	expire = strtonum(expires, now,
678 	    sizeof(time_t) == sizeof(int) ? INT_MAX : LLONG_MAX, NULL);
679 	if (expire == 0)
680 		return(-1);
681 
682 	if (strcmp(what, "TRAP") == 0)
683 		spamtrap = 1;
684 	else if (strcmp(what, "WHITE") == 0)
685 		spamtrap = 0;
686 	else
687 		return(-1);
688 
689 	memset(&hashinfo, 0, sizeof(hashinfo));
690 	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
691 	if (db == NULL)
692 		return(-1);
693 
694 	memset(&dbk, 0, sizeof(dbk));
695 	dbk.size = strlen(ip);
696 	dbk.data = ip;
697 	memset(&dbd, 0, sizeof(dbd));
698 	r = db->get(db, &dbk, &dbd, 0);
699 	if (r == -1)
700 		goto bad;
701 	if (r) {
702 		/* new entry */
703 		memset(&gd, 0, sizeof(gd));
704 		gd.first = now;
705 		gd.pcount = spamtrap ? -1 : 0;
706 		gd.expire = expire;
707 		memset(&dbk, 0, sizeof(dbk));
708 		dbk.size = strlen(ip);
709 		dbk.data = ip;
710 		memset(&dbd, 0, sizeof(dbd));
711 		dbd.size = sizeof(gd);
712 		dbd.data = &gd;
713 		r = db->put(db, &dbk, &dbd, 0);
714 		db->sync(db, 0);
715 		if (r)
716 			goto bad;
717 		if (debug)
718 			fprintf(stderr, "added %s %s\n",
719 			    spamtrap ? "trap entry for" : "", ip);
720 		syslog_r(LOG_DEBUG, &sdata,
721 		    "new %s from %s for %s, expires %s", what, source, ip,
722 		    expires);
723 	} else {
724 		/* existing entry */
725 		if (gdcopyin(&dbd, &gd) == -1) {
726 			/* whatever this is, it doesn't belong */
727 			db->del(db, &dbk, 0);
728 			db->sync(db, 0);
729 			goto bad;
730 		}
731 		if (spamtrap) {
732 			gd.pcount = -1;
733 			gd.bcount++;
734 		} else
735 			gd.pcount++;
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, "updated %s\n", ip);
748 	}
749 	db->close(db);
750 	return(0);
751  bad:
752 	db->close(db);
753 	return(-1);
754 
755 }
756 
757 int
greyupdate(char * dbname,char * helo,char * ip,char * from,char * to,int sync,char * cip)758 greyupdate(char *dbname, char *helo, char *ip, char *from, char *to, int sync,
759     char *cip)
760 {
761 	HASHINFO	hashinfo;
762 	DBT		dbk, dbd;
763 	DB		*db;
764 	char		*key = NULL;
765 	char		*lookup;
766 	struct gdata	gd;
767 	time_t		now, expire;
768 	int		r, spamtrap;
769 
770 	now = time(NULL);
771 
772 	/* open with lock, find record, update, close, unlock */
773 	memset(&hashinfo, 0, sizeof(hashinfo));
774 	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
775 	if (db == NULL)
776 		return(-1);
777 	if (asprintf(&key, "%s\n%s\n%s\n%s", ip, helo, from, to) == -1)
778 		goto bad;
779 	r = trapcheck(db, to);
780 	switch (r) {
781 	case 1:
782 		/* do not trap */
783 		spamtrap = 0;
784 		lookup = key;
785 		expire = greyexp;
786 		break;
787 	case 0:
788 		/* trap */
789 		spamtrap = 1;
790 		lookup = ip;
791 		expire = trapexp;
792 		syslog_r(LOG_DEBUG, &sdata, "Trapping %s for tuple %s", ip,
793 		    key);
794 		break;
795 	default:
796 		goto bad;
797 		break;
798 	}
799 	memset(&dbk, 0, sizeof(dbk));
800 	dbk.size = strlen(lookup);
801 	dbk.data = lookup;
802 	memset(&dbd, 0, sizeof(dbd));
803 	r = db->get(db, &dbk, &dbd, 0);
804 	if (r == -1)
805 		goto bad;
806 	if (r) {
807 		/* new entry */
808 		if (sync &&  low_prio_mx_ip &&
809 		    (strcmp(cip, low_prio_mx_ip) == 0) &&
810 		    ((startup + 60)  < now)) {
811 			/* we haven't seen a greylist entry for this tuple,
812 			 * and yet the connection was to a low priority MX
813 			 * which we know can't be hit first if the client
814 			 * is adhering to the RFC's - soo.. kill it!
815 			 */
816 			spamtrap = 1;
817 			lookup = ip;
818 			expire = trapexp;
819 			syslog_r(LOG_DEBUG, &sdata,
820 			    "Trapping %s for trying %s first for tuple %s",
821 			    ip, low_prio_mx_ip, key);
822 		}
823 		memset(&gd, 0, sizeof(gd));
824 		gd.first = now;
825 		gd.bcount = 1;
826 		gd.pcount = spamtrap ? -1 : 0;
827 		gd.pass = now + expire;
828 		gd.expire = now + expire;
829 		memset(&dbk, 0, sizeof(dbk));
830 		dbk.size = strlen(lookup);
831 		dbk.data = lookup;
832 		memset(&dbd, 0, sizeof(dbd));
833 		dbd.size = sizeof(gd);
834 		dbd.data = &gd;
835 		r = db->put(db, &dbk, &dbd, 0);
836 		db->sync(db, 0);
837 		if (r)
838 			goto bad;
839 		if (debug)
840 			fprintf(stderr, "added %s %s\n",
841 			    spamtrap ? "greytrap entry for" : "", lookup);
842 		syslog_r(LOG_DEBUG, &sdata,
843 		    "new %sentry %s from %s to %s, helo %s",
844 		    spamtrap ? "greytrap " : "", ip, from, to, helo);
845 	} else {
846 		/* existing entry */
847 		if (gdcopyin(&dbd, &gd) == -1) {
848 			/* whatever this is, it doesn't belong */
849 			db->del(db, &dbk, 0);
850 			db->sync(db, 0);
851 			goto bad;
852 		}
853 		gd.bcount++;
854 		gd.pcount = spamtrap ? -1 : 0;
855 		if (gd.first + passtime < now)
856 			gd.pass = now;
857 		memset(&dbk, 0, sizeof(dbk));
858 		dbk.size = strlen(lookup);
859 		dbk.data = lookup;
860 		memset(&dbd, 0, sizeof(dbd));
861 		dbd.size = sizeof(gd);
862 		dbd.data = &gd;
863 		r = db->put(db, &dbk, &dbd, 0);
864 		db->sync(db, 0);
865 		if (r)
866 			goto bad;
867 		if (debug)
868 			fprintf(stderr, "updated %s\n", lookup);
869 	}
870 	free(key);
871 	key = NULL;
872 	db->close(db);
873 	db = NULL;
874 
875 	/* Entry successfully update, sent out sync message */
876 	if (syncsend && sync) {
877 		if (spamtrap) {
878 			syslog_r(LOG_DEBUG, &sdata,
879 			    "sync_trap %s", ip);
880 			sync_trapped(now, now + expire, ip);
881 		}
882 		else
883 			sync_update(now, helo, ip, from, to);
884 	}
885 	return(0);
886  bad:
887 	free(key);
888 	key = NULL;
889 	db->close(db);
890 	db = NULL;
891 	return(-1);
892 }
893 
894 int
twread(char * buf)895 twread(char *buf)
896 {
897 	if ((strncmp(buf, "WHITE:", 6) == 0) ||
898 	    (strncmp(buf, "TRAP:", 5) == 0)) {
899 		char **ap, *argv[5];
900 		int argc = 0;
901 
902 		for (ap = argv;
903 		    ap < &argv[4] && (*ap = strsep(&buf, ":")) != NULL;) {
904 			if (**ap != '\0')
905 				ap++;
906 			argc++;
907 		}
908 		*ap = NULL;
909 		if (argc != 4)
910 			return (-1);
911 		twupdate(PATH_SPAMD_DB, argv[0], argv[1], argv[2], argv[3]);
912 		return (0);
913 	} else
914 		return (-1);
915 }
916 
917 int
greyreader(void)918 greyreader(void)
919 {
920 	char cip[32], ip[32], helo[MAX_MAIL], from[MAX_MAIL], to[MAX_MAIL];
921 	char *buf;
922 	size_t len;
923 	int state, sync;
924 	struct addrinfo hints, *res;
925 
926 	memset(&hints, 0, sizeof(hints));
927 	hints.ai_family = AF_INET;		/*for now*/
928 	hints.ai_socktype = SOCK_DGRAM;		/*dummy*/
929 	hints.ai_protocol = IPPROTO_UDP;	/*dummy*/
930 	hints.ai_flags = AI_NUMERICHOST;
931 
932 	state = 0;
933 	sync = 1;
934 	if (grey == NULL) {
935 		syslog_r(LOG_ERR, &sdata, "No greylist pipe stream!\n");
936 		return (-1);
937 	}
938 
939 	/* grab trap suffixes */
940 	readsuffixlists();
941 
942 	while ((buf = fgetln(grey, &len))) {
943 		if (buf[len - 1] == '\n')
944 			buf[len - 1] = '\0';
945 		else
946 			/* all valid lines end in \n */
947 			continue;
948 		if (strlen(buf) < 4)
949 			continue;
950 
951 		if (strcmp(buf, "SYNC") == 0) {
952 			sync = 0;
953 			continue;
954 		}
955 
956 		switch (state) {
957 		case 0:
958 			if (twread(buf) == 0) {
959 				state = 0;
960 				break;
961 			}
962 			if (strncmp(buf, "HE:", 3) != 0) {
963 				if (strncmp(buf, "CO:", 3) == 0)
964 					strlcpy(cip, buf+3, sizeof(cip));
965 				state = 0;
966 				break;
967 			}
968 			strlcpy(helo, buf+3, sizeof(helo));
969 			state = 1;
970 			break;
971 		case 1:
972 			if (strncmp(buf, "IP:", 3) != 0)
973 				break;
974 			strlcpy(ip, buf+3, sizeof(ip));
975 			if (getaddrinfo(ip, NULL, &hints, &res) == 0) {
976 				freeaddrinfo(res);
977 				state = 2;
978 			} else
979 				state = 0;
980 			break;
981 		case 2:
982 			if (strncmp(buf, "FR:", 3) != 0) {
983 				state = 0;
984 				break;
985 			}
986 			strlcpy(from, buf+3, sizeof(from));
987 			state = 3;
988 			break;
989 		case 3:
990 			if (strncmp(buf, "TO:", 3) != 0) {
991 				state = 0;
992 				break;
993 			}
994 			strlcpy(to, buf+3, sizeof(to));
995 			if (debug)
996 				fprintf(stderr,
997 				    "Got Grey HELO %s, IP %s from %s to %s\n",
998 				    helo, ip, from, to);
999 			greyupdate(PATH_SPAMD_DB, helo, ip, from, to, sync, cip);
1000 			sync = 1;
1001 			state = 0;
1002 			break;
1003 		}
1004 	}
1005 	return (0);
1006 }
1007 
1008 void
greyscanner(void)1009 greyscanner(void)
1010 {
1011 	for (;;) {
1012 		if (greyscan(PATH_SPAMD_DB) == -1)
1013 			syslog_r(LOG_NOTICE, &sdata, "scan of %s failed",
1014 			    PATH_SPAMD_DB);
1015 		sleep(DB_SCAN_INTERVAL);
1016 	}
1017 }
1018 
1019 static void
drop_privs(void)1020 drop_privs(void)
1021 {
1022 	/*
1023 	 * lose root, continue as non-root user
1024 	 */
1025 	if (setgroups(1, &pw->pw_gid) ||
1026 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
1027 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) {
1028 		syslog_r(LOG_ERR, &sdata, "failed to drop privs (%m)");
1029 		exit(1);
1030 	}
1031 }
1032 
1033 void
check_spamd_db(void)1034 check_spamd_db(void)
1035 {
1036 	HASHINFO hashinfo;
1037 	int i = -1;
1038 	DB *db;
1039 
1040 	/* check to see if /var/db/spamd exists, if not, create it */
1041 	memset(&hashinfo, 0, sizeof(hashinfo));
1042 	db = dbopen(PATH_SPAMD_DB, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
1043 
1044 	if (db == NULL) {
1045 		switch (errno) {
1046 		case ENOENT:
1047 			i = open(PATH_SPAMD_DB, O_RDWR|O_CREAT, 0644);
1048 			if (i == -1) {
1049 				syslog_r(LOG_ERR, &sdata,
1050 				    "create %s failed (%m)", PATH_SPAMD_DB);
1051 				exit(1);
1052 			}
1053 			/* if we are dropping privs, chown to that user */
1054 			if (pw && (fchown(i, pw->pw_uid, pw->pw_gid) == -1)) {
1055 				syslog_r(LOG_ERR, &sdata,
1056 				    "chown %s failed (%m)", PATH_SPAMD_DB);
1057 				exit(1);
1058 			}
1059 			close(i);
1060 			return;
1061 			break;
1062 		default:
1063 			syslog_r(LOG_ERR, &sdata, "open of %s failed (%m)",
1064 			    PATH_SPAMD_DB);
1065 			exit(1);
1066 		}
1067 	}
1068 	db->sync(db, 0);
1069 	db->close(db);
1070 }
1071 
1072 
1073 int
greywatcher(void)1074 greywatcher(void)
1075 {
1076 	struct sigaction sa;
1077 
1078 	drop_privs();
1079 
1080 	if (unveil(PATH_SPAMD_DB, "rw") == -1) {
1081 		syslog_r(LOG_ERR, &sdata, "unveil failed (%m)");
1082 		exit(1);
1083 	}
1084 	if (unveil(alloweddomains_file, "r") == -1) {
1085 		syslog_r(LOG_ERR, &sdata, "unveil failed (%m)");
1086 		exit(1);
1087 	}
1088 	if (unveil(PATH_PFCTL, "x") == -1) {
1089 		syslog_r(LOG_ERR, &sdata, "unveil failed (%m)");
1090 		exit(1);
1091 	}
1092 	if (pledge("stdio rpath wpath inet flock proc exec", NULL) == -1) {
1093 		syslog_r(LOG_ERR, &sdata, "pledge failed (%m)");
1094 		exit(1);
1095 	}
1096 
1097 	startup = time(NULL);
1098 	db_pid = fork();
1099 	switch (db_pid) {
1100 	case -1:
1101 		syslog_r(LOG_ERR, &sdata, "fork failed (%m)");
1102 		exit(1);
1103 	case 0:
1104 		/*
1105 		 * child, talks to jailed spamd over greypipe,
1106 		 * updates db. has no access to pf.
1107 		 */
1108 		close(pfdev);
1109 		setproctitle("(%s update)", PATH_SPAMD_DB);
1110 		if (greyreader() == -1) {
1111 		    syslog_r(LOG_ERR, &sdata, "greyreader failed (%m)");
1112 		    _exit(1);
1113 		}
1114 		_exit(0);
1115 	}
1116 
1117 
1118 	fclose(grey);
1119 	/*
1120 	 * parent, scans db periodically for changes and updates
1121 	 * pf whitelist table accordingly.
1122 	 */
1123 
1124 	sigfillset(&sa.sa_mask);
1125 	sa.sa_flags = SA_RESTART;
1126 	sa.sa_handler = sig_term_chld;
1127 	sigaction(SIGTERM, &sa, NULL);
1128 	sigaction(SIGHUP, &sa, NULL);
1129 	sigaction(SIGCHLD, &sa, NULL);
1130 	sigaction(SIGINT, &sa, NULL);
1131 
1132 	setproctitle("(pf <spamd-white> update)");
1133 	greyscanner();
1134 	exit(1);
1135 }
1136