163248Speter/*	$OpenBSD: grey.c,v 1.67 2023/03/08 04:43:06 guenther Exp $	*/
263248Speter
364055Salex/*
464055Salex * Copyright (c) 2004-2006 Bob Beck.  All rights reserved.
564055Salex *
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
43extern time_t passtime, greyexp, whiteexp, trapexp;
44extern struct syslog_data sdata;
45extern struct passwd *pw;
46extern u_short cfg_port;
47extern pid_t jail_pid;
48extern FILE *trapcfg;
49extern FILE *grey;
50extern int debug;
51extern int syncsend;
52extern 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
58void	configure_spamd(char **, u_int, FILE *);
59int	configure_pf(char **, int);
60char	*dequotetolower(const char *);
61void	readsuffixlists(void);
62void	freeaddrlists(void);
63int	addwhiteaddr(char *);
64int	addtrapaddr(char *);
65int	db_addrstate(DB *, char *);
66int	greyscan(char *);
67int	trapcheck(DB *, char *);
68int	twupdate(char *, char *, char *, char *, char *);
69int	twread(char *);
70int	greyreader(void);
71void	greyscanner(void);
72
73
74u_int whitecount, whitealloc;
75u_int trapcount, trapalloc;
76char **whitelist;
77char **traplist;
78
79char *traplist_name = "spamd-greytrap";
80char *traplist_msg = "\"Your address %A has mailed to spamtraps here\\n\"";
81
82pid_t db_pid = -1;
83int pfdev;
84
85struct 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 */
97SLIST_HEAD(, db_change) db_changes = SLIST_HEAD_INITIALIZER(db_changes);
98
99struct mail_addr {
100	SLIST_ENTRY(mail_addr)	entry;
101	char			addr[MAX_MAIL];
102};
103
104/* list of suffixes that must match TO: */
105SLIST_HEAD(, mail_addr) match_suffix = SLIST_HEAD_INITIALIZER(match_suffix);
106char *alloweddomains_file = PATH_SPAMD_ALLOWEDDOMAINS;
107
108char *low_prio_mx_ip;
109time_t startup;
110
111static 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 */
117static void
118sig_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 */
133void
134configure_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
150int
151configure_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
229char *
230dequotetolower(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
249void
250readsuffixlists(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;
293bad:
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
301void
302freeaddrlists(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 */
322int
323addwhiteaddr(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 */
375int
376addtrapaddr(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
411static int
412queue_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
441static int
442do_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 */
495int
496db_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
520int
521greyscan(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
628int
629trapcheck(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
664int
665twupdate(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
757int
758greyupdate(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
894int
895twread(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
917int
918greyreader(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
1008void
1009greyscanner(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
1019static void
1020drop_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
1033void
1034check_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
1073int
1074greywatcher(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