xref: /openbsd-src/libexec/spamd/grey.c (revision 5b133f3f277e80f096764111e64f3a1284acb179)
1*5b133f3fSguenther /*	$OpenBSD: grey.c,v 1.67 2023/03/08 04:43:06 guenther Exp $	*/
284d82a5fSderaadt 
31f68c1d4Sbeck /*
44020b879Sbeck  * Copyright (c) 2004-2006 Bob Beck.  All rights reserved.
51f68c1d4Sbeck  *
61f68c1d4Sbeck  * Permission to use, copy, modify, and distribute this software for any
71f68c1d4Sbeck  * purpose with or without fee is hereby granted, provided that the above
81f68c1d4Sbeck  * copyright notice and this permission notice appear in all copies.
91f68c1d4Sbeck  *
101f68c1d4Sbeck  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
111f68c1d4Sbeck  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
121f68c1d4Sbeck  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
131f68c1d4Sbeck  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
141f68c1d4Sbeck  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
151f68c1d4Sbeck  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
161f68c1d4Sbeck  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
171f68c1d4Sbeck  */
181f68c1d4Sbeck 
191f68c1d4Sbeck #include <sys/types.h>
201f68c1d4Sbeck #include <sys/socket.h>
211f68c1d4Sbeck #include <sys/ioctl.h>
221f68c1d4Sbeck #include <sys/wait.h>
231f68c1d4Sbeck #include <net/if.h>
241f68c1d4Sbeck #include <netinet/in.h>
251f68c1d4Sbeck #include <net/pfvar.h>
261e913b92Sbeck #include <ctype.h>
271f68c1d4Sbeck #include <db.h>
281f68c1d4Sbeck #include <errno.h>
291f68c1d4Sbeck #include <fcntl.h>
301f68c1d4Sbeck #include <pwd.h>
318a6ccc17Sbeck #include <signal.h>
321f68c1d4Sbeck #include <stdio.h>
331f68c1d4Sbeck #include <stdlib.h>
341f68c1d4Sbeck #include <string.h>
351f68c1d4Sbeck #include <syslog.h>
361f68c1d4Sbeck #include <time.h>
371f68c1d4Sbeck #include <unistd.h>
38ef8fe961Sitojun #include <netdb.h>
391f68c1d4Sbeck 
401f68c1d4Sbeck #include "grey.h"
4198babcadSbeck #include "sync.h"
421f68c1d4Sbeck 
431e913b92Sbeck extern time_t passtime, greyexp, whiteexp, trapexp;
441f68c1d4Sbeck extern struct syslog_data sdata;
451f68c1d4Sbeck extern struct passwd *pw;
461e913b92Sbeck extern u_short cfg_port;
478a6ccc17Sbeck extern pid_t jail_pid;
481e913b92Sbeck extern FILE *trapcfg;
491f68c1d4Sbeck extern FILE *grey;
501f68c1d4Sbeck extern int debug;
5198babcadSbeck extern int syncsend;
5266baed91Smillert extern int greyback[2];
531f68c1d4Sbeck 
542c79ec66Sbeck /* From netinet/in.h, but only _KERNEL_ gets them. */
552c79ec66Sbeck #define satosin(sa)	((struct sockaddr_in *)(sa))
562c79ec66Sbeck #define satosin6(sa)	((struct sockaddr_in6 *)(sa))
572c79ec66Sbeck 
58694ff69bSmillert void	configure_spamd(char **, u_int, FILE *);
59f66a3d85Sderaadt int	configure_pf(char **, int);
60f66a3d85Sderaadt char	*dequotetolower(const char *);
61f66a3d85Sderaadt void	readsuffixlists(void);
62f66a3d85Sderaadt void	freeaddrlists(void);
63f66a3d85Sderaadt int	addwhiteaddr(char *);
64f66a3d85Sderaadt int	addtrapaddr(char *);
65f66a3d85Sderaadt int	db_addrstate(DB *, char *);
66f66a3d85Sderaadt int	greyscan(char *);
67f66a3d85Sderaadt int	trapcheck(DB *, char *);
68f66a3d85Sderaadt int	twupdate(char *, char *, char *, char *, char *);
69f66a3d85Sderaadt int	twread(char *);
70f66a3d85Sderaadt int	greyreader(void);
71f66a3d85Sderaadt void	greyscanner(void);
72f66a3d85Sderaadt 
73f66a3d85Sderaadt 
74694ff69bSmillert u_int whitecount, whitealloc;
75694ff69bSmillert u_int trapcount, trapalloc;
761f68c1d4Sbeck char **whitelist;
771e913b92Sbeck char **traplist;
781e913b92Sbeck 
791e913b92Sbeck char *traplist_name = "spamd-greytrap";
801e913b92Sbeck char *traplist_msg = "\"Your address %A has mailed to spamtraps here\\n\"";
811e913b92Sbeck 
828a6ccc17Sbeck pid_t db_pid = -1;
831f68c1d4Sbeck int pfdev;
841f68c1d4Sbeck 
854020b879Sbeck struct db_change {
864020b879Sbeck 	SLIST_ENTRY(db_change)	entry;
874020b879Sbeck 	char *			key;
884020b879Sbeck 	void *			data;
894020b879Sbeck 	size_t			dsiz;
904020b879Sbeck 	int			act;
914020b879Sbeck };
924020b879Sbeck 
934020b879Sbeck #define DBC_ADD 1
944020b879Sbeck #define DBC_DEL 2
954020b879Sbeck 
964020b879Sbeck /* db pending changes list */
974020b879Sbeck SLIST_HEAD(, db_change) db_changes = SLIST_HEAD_INITIALIZER(db_changes);
984020b879Sbeck 
992abcd8eaSbeck struct mail_addr {
1002abcd8eaSbeck 	SLIST_ENTRY(mail_addr)	entry;
1012abcd8eaSbeck 	char			addr[MAX_MAIL];
1022abcd8eaSbeck };
1032abcd8eaSbeck 
1042abcd8eaSbeck /* list of suffixes that must match TO: */
1052abcd8eaSbeck SLIST_HEAD(, mail_addr) match_suffix = SLIST_HEAD_INITIALIZER(match_suffix);
1062abcd8eaSbeck char *alloweddomains_file = PATH_SPAMD_ALLOWEDDOMAINS;
1072abcd8eaSbeck 
1082c79ec66Sbeck char *low_prio_mx_ip;
109ee505fa1Sbeck time_t startup;
1102c79ec66Sbeck 
11184d82a5fSderaadt static char *pargv[11]= {
11284d82a5fSderaadt 	"pfctl", "-p", "/dev/pf", "-q", "-t",
113dc7e4988Sderaadt 	"spamd-white", "-T", "replace", "-f", "-", NULL
11484d82a5fSderaadt };
11584d82a5fSderaadt 
1168a6ccc17Sbeck /* If the parent gets a signal, kill off the children and exit */
1178a6ccc17Sbeck static void
sig_term_chld(int sig)1188a6ccc17Sbeck sig_term_chld(int sig)
1198a6ccc17Sbeck {
1208a6ccc17Sbeck 	if (db_pid != -1)
1218a6ccc17Sbeck 		kill(db_pid, SIGTERM);
1228a6ccc17Sbeck 	if (jail_pid != -1)
1238a6ccc17Sbeck 		kill(jail_pid, SIGTERM);
1248a6ccc17Sbeck 	_exit(1);
1258a6ccc17Sbeck }
1268a6ccc17Sbeck 
1271e913b92Sbeck /*
1281e913b92Sbeck  * Greatly simplified version from spamd_setup.c  - only
1291e913b92Sbeck  * sends one blacklist to an already open stream. Has no need
1301e913b92Sbeck  * to collapse cidr ranges since these are only ever single
1311e913b92Sbeck  * host hits.
1321e913b92Sbeck  */
133b8f31536Sray void
configure_spamd(char ** addrs,u_int count,FILE * sdc)134694ff69bSmillert configure_spamd(char **addrs, u_int count, FILE *sdc)
1351e913b92Sbeck {
136694ff69bSmillert 	u_int i;
1371e913b92Sbeck 
138694ff69bSmillert 	/* XXX - doesn't support IPV6 yet */
139e25a0afeSbeck 	fprintf(sdc, "%s;", traplist_name);
140e25a0afeSbeck 	if (count != 0) {
141694ff69bSmillert 		fprintf(sdc, "%s;inet;%u", traplist_msg, count);
1421e913b92Sbeck 		for (i = 0; i < count; i++)
143694ff69bSmillert 			fprintf(sdc, ";%s/32", addrs[i]);
144e25a0afeSbeck 	}
145694ff69bSmillert 	fputc('\n', sdc);
1464020b879Sbeck 	if (fflush(sdc) == EOF)
1474020b879Sbeck 		syslog_r(LOG_DEBUG, &sdata, "configure_spamd: fflush failed (%m)");
1481e913b92Sbeck }
1491e913b92Sbeck 
1501f68c1d4Sbeck int
configure_pf(char ** addrs,int count)1511f68c1d4Sbeck configure_pf(char **addrs, int count)
1521f68c1d4Sbeck {
1531f68c1d4Sbeck 	FILE *pf = NULL;
154e6ec2729Sotto 	int i, pdes[2], status;
1551f68c1d4Sbeck 	pid_t pid;
1561f68c1d4Sbeck 	char *fdpath;
1573ac6234cSmoritz 	struct sigaction sa;
1583ac6234cSmoritz 
1593ac6234cSmoritz 	sigfillset(&sa.sa_mask);
1603ac6234cSmoritz 	sa.sa_flags = SA_RESTART;
1613ac6234cSmoritz 	sa.sa_handler = sig_term_chld;
1621f68c1d4Sbeck 
1631f68c1d4Sbeck 	if (debug)
1641f68c1d4Sbeck 		fprintf(stderr, "configure_pf - device on fd %d\n", pfdev);
1653c8544c9Sderaadt 
1663c8544c9Sderaadt 	/* Because /dev/fd/ only contains device nodes for 0-63 */
1671f68c1d4Sbeck 	if (pfdev < 1 || pfdev > 63)
1681f68c1d4Sbeck 		return(-1);
1693c8544c9Sderaadt 
1701f68c1d4Sbeck 	if (asprintf(&fdpath, "/dev/fd/%d", pfdev) == -1)
1711f68c1d4Sbeck 		return(-1);
17284d82a5fSderaadt 	pargv[2] = fdpath;
1731f68c1d4Sbeck 	if (pipe(pdes) != 0) {
1741f68c1d4Sbeck 		syslog_r(LOG_INFO, &sdata, "pipe failed (%m)");
1751f68c1d4Sbeck 		free(fdpath);
1767066e321Sbeck 		fdpath = NULL;
1771f68c1d4Sbeck 		return(-1);
1781f68c1d4Sbeck 	}
1798a6ccc17Sbeck 	signal(SIGCHLD, SIG_DFL);
1801f68c1d4Sbeck 	switch (pid = fork()) {
1811f68c1d4Sbeck 	case -1:
1821f68c1d4Sbeck 		syslog_r(LOG_INFO, &sdata, "fork failed (%m)");
1831f68c1d4Sbeck 		free(fdpath);
1847066e321Sbeck 		fdpath = NULL;
18584d82a5fSderaadt 		close(pdes[0]);
18684d82a5fSderaadt 		close(pdes[1]);
1873ac6234cSmoritz 		sigaction(SIGCHLD, &sa, NULL);
1881f68c1d4Sbeck 		return(-1);
1891f68c1d4Sbeck 	case 0:
1901f68c1d4Sbeck 		/* child */
1911f68c1d4Sbeck 		close(pdes[1]);
1921f68c1d4Sbeck 		if (pdes[0] != STDIN_FILENO) {
1931f68c1d4Sbeck 			dup2(pdes[0], STDIN_FILENO);
1941f68c1d4Sbeck 			close(pdes[0]);
1951f68c1d4Sbeck 		}
19684d82a5fSderaadt 		execvp(PATH_PFCTL, pargv);
1971f68c1d4Sbeck 		syslog_r(LOG_ERR, &sdata, "can't exec %s:%m", PATH_PFCTL);
1981f68c1d4Sbeck 		_exit(1);
1991f68c1d4Sbeck 	}
2001f68c1d4Sbeck 
2011f68c1d4Sbeck 	/* parent */
2021f68c1d4Sbeck 	free(fdpath);
2037066e321Sbeck 	fdpath = NULL;
2041f68c1d4Sbeck 	close(pdes[0]);
2051f68c1d4Sbeck 	pf = fdopen(pdes[1], "w");
2061f68c1d4Sbeck 	if (pf == NULL) {
2071f68c1d4Sbeck 		syslog_r(LOG_INFO, &sdata, "fdopen failed (%m)");
2087066e321Sbeck 		close(pdes[1]);
2093ac6234cSmoritz 		sigaction(SIGCHLD, &sa, NULL);
2101f68c1d4Sbeck 		return(-1);
2111f68c1d4Sbeck 	}
2121f68c1d4Sbeck 	for (i = 0; i < count; i++)
21308225fe9Sbeck 		if (addrs[i] != NULL)
2141f68c1d4Sbeck 			fprintf(pf, "%s/32\n", addrs[i]);
2151f68c1d4Sbeck 	fclose(pf);
216e6ec2729Sotto 
217e6ec2729Sotto 	waitpid(pid, &status, 0);
218e6ec2729Sotto 	if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
219e6ec2729Sotto 		syslog_r(LOG_ERR, &sdata, "%s returned status %d", PATH_PFCTL,
220e6ec2729Sotto 		    WEXITSTATUS(status));
221e6ec2729Sotto 	else if (WIFSIGNALED(status))
222e6ec2729Sotto 		syslog_r(LOG_ERR, &sdata, "%s died on signal %d", PATH_PFCTL,
223e6ec2729Sotto 		    WTERMSIG(status));
224e6ec2729Sotto 
2253ac6234cSmoritz 	sigaction(SIGCHLD, &sa, NULL);
2261f68c1d4Sbeck 	return(0);
2271f68c1d4Sbeck }
2281f68c1d4Sbeck 
2292abcd8eaSbeck char *
dequotetolower(const char * addr)2302abcd8eaSbeck dequotetolower(const char *addr)
2312abcd8eaSbeck {
2322abcd8eaSbeck 	static char buf[MAX_MAIL];
23361a23f49Sbeck 	char *cp;
2342abcd8eaSbeck 
23531ffa88fSjsg 	if (*addr == '<')
2362abcd8eaSbeck 		addr++;
23761a23f49Sbeck 	(void) strlcpy(buf, addr, sizeof(buf));
23861a23f49Sbeck 	cp = strrchr(buf, '>');
23961a23f49Sbeck 	if (cp != NULL && cp[1] == '\0')
24061a23f49Sbeck 		*cp = '\0';
24161a23f49Sbeck 	cp = buf;
24261a23f49Sbeck 	while (*cp != '\0') {
2434207a9b6Sderaadt 		*cp = tolower((unsigned char)*cp);
24461a23f49Sbeck 		cp++;
24561a23f49Sbeck 	}
2462abcd8eaSbeck 	return(buf);
2472abcd8eaSbeck }
2482abcd8eaSbeck 
2492abcd8eaSbeck void
readsuffixlists(void)2502abcd8eaSbeck readsuffixlists(void)
2512abcd8eaSbeck {
2522abcd8eaSbeck 	FILE *fp;
2532abcd8eaSbeck 	char *buf;
2542abcd8eaSbeck 	size_t len;
2552abcd8eaSbeck 	struct mail_addr *m;
2562abcd8eaSbeck 
257f576ce10Sbeck 	while (!SLIST_EMPTY(&match_suffix)) {
258f576ce10Sbeck 		m = SLIST_FIRST(&match_suffix);
2592abcd8eaSbeck 		SLIST_REMOVE_HEAD(&match_suffix, entry);
260f576ce10Sbeck 		free(m);
261f576ce10Sbeck 	}
2622abcd8eaSbeck 	if ((fp = fopen(alloweddomains_file, "r")) != NULL) {
2632abcd8eaSbeck 		while ((buf = fgetln(fp, &len))) {
26400ddf0caSbeck 			/* strip white space-characters */
2654207a9b6Sderaadt 			while (len > 0 && isspace((unsigned char)buf[len-1]))
26600ddf0caSbeck 				len--;
2674207a9b6Sderaadt 			while (len > 0 && isspace((unsigned char)*buf)) {
26800ddf0caSbeck 				buf++;
26900ddf0caSbeck 				len--;
27000ddf0caSbeck 			}
27100ddf0caSbeck 			if (len == 0)
27200ddf0caSbeck 				continue;
27300ddf0caSbeck 			/* jump over comments and blank lines */
27400ddf0caSbeck 			if (*buf == '#' || *buf == '\n')
27500ddf0caSbeck 				continue;
2762abcd8eaSbeck 			if (buf[len-1] == '\n')
2772abcd8eaSbeck 				len--;
2782abcd8eaSbeck 			if ((len + 1) > sizeof(m->addr)) {
2792abcd8eaSbeck 				syslog_r(LOG_ERR, &sdata,
2802abcd8eaSbeck 				    "line too long in %s - file ignored",
2812abcd8eaSbeck 				    alloweddomains_file);
2822abcd8eaSbeck 				goto bad;
2832abcd8eaSbeck 			}
28434fa0e41Sderaadt 			if ((m = malloc(sizeof(struct mail_addr))) == NULL)
28534fa0e41Sderaadt 				goto bad;
2862abcd8eaSbeck 			memcpy(m->addr, buf, len);
2872abcd8eaSbeck 			m->addr[len]='\0';
2882abcd8eaSbeck 			syslog_r(LOG_ERR, &sdata, "got suffix %s", m->addr);
2892abcd8eaSbeck 			SLIST_INSERT_HEAD(&match_suffix, m, entry);
2902abcd8eaSbeck 		}
2912abcd8eaSbeck 	}
2922abcd8eaSbeck 	return;
2932abcd8eaSbeck bad:
294f576ce10Sbeck 	while (!SLIST_EMPTY(&match_suffix)) {
295f576ce10Sbeck 	  	m = SLIST_FIRST(&match_suffix);
2962abcd8eaSbeck 		SLIST_REMOVE_HEAD(&match_suffix, entry);
297f576ce10Sbeck 		free(m);
298f576ce10Sbeck 	}
2992abcd8eaSbeck }
3002abcd8eaSbeck 
30108225fe9Sbeck void
freeaddrlists(void)3021e913b92Sbeck freeaddrlists(void)
30308225fe9Sbeck {
30408225fe9Sbeck 	int i;
30508225fe9Sbeck 
30608225fe9Sbeck 	if (whitelist != NULL)
30708225fe9Sbeck 		for (i = 0; i < whitecount; i++) {
30808225fe9Sbeck 			free(whitelist[i]);
30908225fe9Sbeck 			whitelist[i] = NULL;
31008225fe9Sbeck 		}
31108225fe9Sbeck 	whitecount = 0;
3121e913b92Sbeck 	if (traplist != NULL) {
3131e913b92Sbeck 		for (i = 0; i < trapcount; i++) {
3141e913b92Sbeck 			free(traplist[i]);
3151e913b92Sbeck 			traplist[i] = NULL;
3161e913b92Sbeck 		}
3171e913b92Sbeck 	}
3181e913b92Sbeck 	trapcount = 0;
31908225fe9Sbeck }
32008225fe9Sbeck 
3211f68c1d4Sbeck /* validate, then add to list of addrs to whitelist */
3221f68c1d4Sbeck int
addwhiteaddr(char * addr)3231f68c1d4Sbeck addwhiteaddr(char *addr)
3241f68c1d4Sbeck {
325ef8fe961Sitojun 	struct addrinfo hints, *res;
32666baed91Smillert 	char ch;
3271f68c1d4Sbeck 
328ef8fe961Sitojun 	memset(&hints, 0, sizeof(hints));
32994c946baSitojun 	hints.ai_family = AF_INET;		/*for now*/
330ef8fe961Sitojun 	hints.ai_socktype = SOCK_DGRAM;		/*dummy*/
331ef8fe961Sitojun 	hints.ai_protocol = IPPROTO_UDP;	/*dummy*/
332ef8fe961Sitojun 	hints.ai_flags = AI_NUMERICHOST;
333ef8fe961Sitojun 
33466baed91Smillert 	if (getaddrinfo(addr, NULL, &hints, &res) != 0)
33566baed91Smillert 		return(-1);
33666baed91Smillert 
33766baed91Smillert 	/* Check spamd blacklists in main process. */
33866baed91Smillert 	if (send(greyback[0], res->ai_addr, res->ai_addr->sa_len, 0) == -1) {
33966baed91Smillert 		syslog_r(LOG_ERR, &sdata, "%s: send: %m", __func__);
34066baed91Smillert 	} else {
34166baed91Smillert 		if (recv(greyback[0], &ch, sizeof(ch), 0) == 1) {
34266baed91Smillert 			if (ch == '1') {
34366baed91Smillert 				syslog_r(LOG_DEBUG, &sdata,
34466baed91Smillert 				    "%s blacklisted, removing from whitelist",
34566baed91Smillert 				    addr);
34666baed91Smillert 				freeaddrinfo(res);
34766baed91Smillert 				return(-1);
34866baed91Smillert 			}
34966baed91Smillert 		}
35066baed91Smillert 	}
35166baed91Smillert 
3521f68c1d4Sbeck 	if (whitecount == whitealloc) {
3531f68c1d4Sbeck 		char **tmp;
3541f68c1d4Sbeck 
355915dd247Sderaadt 		tmp = reallocarray(whitelist,
356915dd247Sderaadt 		    whitealloc + 1024, sizeof(char *));
357053e71a7Sderaadt 		if (tmp == NULL) {
358053e71a7Sderaadt 			freeaddrinfo(res);
3591f68c1d4Sbeck 			return(-1);
360053e71a7Sderaadt 		}
3611f68c1d4Sbeck 		whitelist = tmp;
3621f68c1d4Sbeck 		whitealloc += 1024;
3631f68c1d4Sbeck 	}
3641f68c1d4Sbeck 	whitelist[whitecount] = strdup(addr);
365053e71a7Sderaadt 	if (whitelist[whitecount] == NULL) {
366053e71a7Sderaadt 		freeaddrinfo(res);
3671f68c1d4Sbeck 		return(-1);
368053e71a7Sderaadt 	}
3691f68c1d4Sbeck 	whitecount++;
370ef8fe961Sitojun 	freeaddrinfo(res);
3711f68c1d4Sbeck 	return(0);
3721f68c1d4Sbeck }
3731f68c1d4Sbeck 
3741e913b92Sbeck /* validate, then add to list of addrs to traplist */
3751e913b92Sbeck int
addtrapaddr(char * addr)3761e913b92Sbeck addtrapaddr(char *addr)
3771e913b92Sbeck {
3781e913b92Sbeck 	struct addrinfo hints, *res;
3791e913b92Sbeck 
3801e913b92Sbeck 	memset(&hints, 0, sizeof(hints));
3811e913b92Sbeck 	hints.ai_family = AF_INET;		/*for now*/
3821e913b92Sbeck 	hints.ai_socktype = SOCK_DGRAM;		/*dummy*/
3831e913b92Sbeck 	hints.ai_protocol = IPPROTO_UDP;	/*dummy*/
3841e913b92Sbeck 	hints.ai_flags = AI_NUMERICHOST;
3851e913b92Sbeck 
3861e913b92Sbeck 	if (getaddrinfo(addr, NULL, &hints, &res) == 0) {
3871e913b92Sbeck 		if (trapcount == trapalloc) {
3881e913b92Sbeck 			char **tmp;
3891e913b92Sbeck 
390915dd247Sderaadt 			tmp = reallocarray(traplist,
391915dd247Sderaadt 			    trapalloc + 1024, sizeof(char *));
3921e913b92Sbeck 			if (tmp == NULL) {
3931e913b92Sbeck 				freeaddrinfo(res);
3941e913b92Sbeck 				return(-1);
3951e913b92Sbeck 			}
3961e913b92Sbeck 			traplist = tmp;
3971e913b92Sbeck 			trapalloc += 1024;
3981e913b92Sbeck 		}
3991e913b92Sbeck 		traplist[trapcount] = strdup(addr);
4001e913b92Sbeck 		if (traplist[trapcount] == NULL) {
4011e913b92Sbeck 			freeaddrinfo(res);
4021e913b92Sbeck 			return(-1);
4031e913b92Sbeck 		}
4041e913b92Sbeck 		trapcount++;
4051e913b92Sbeck 		freeaddrinfo(res);
4061e913b92Sbeck 	} else
4071e913b92Sbeck 		return(-1);
4081e913b92Sbeck 	return(0);
4091e913b92Sbeck }
4101e913b92Sbeck 
4114020b879Sbeck static int
queue_change(char * key,char * data,size_t dsiz,int act)41220a5c6fdSderaadt queue_change(char *key, char *data, size_t dsiz, int act)
41320a5c6fdSderaadt {
4144020b879Sbeck 	struct db_change *dbc;
4154020b879Sbeck 
4164020b879Sbeck 	if ((dbc = malloc(sizeof(*dbc))) == NULL) {
4174020b879Sbeck 		syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
4184020b879Sbeck 		return(-1);
4194020b879Sbeck 	}
4204020b879Sbeck 	if ((dbc->key = strdup(key)) == NULL) {
4214020b879Sbeck 		syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
4224020b879Sbeck 		free(dbc);
4234020b879Sbeck 		return(-1);
4244020b879Sbeck 	}
4254020b879Sbeck 	if ((dbc->data = malloc(dsiz)) == NULL) {
4264020b879Sbeck 		syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)");
4274020b879Sbeck 		free(dbc->key);
4284020b879Sbeck 		free(dbc);
4294020b879Sbeck 		return(-1);
4304020b879Sbeck 	}
4314020b879Sbeck 	memcpy(dbc->data, data, dsiz);
4324020b879Sbeck 	dbc->dsiz = dsiz;
4334020b879Sbeck 	dbc->act = act;
4344020b879Sbeck 	syslog_r(LOG_DEBUG, &sdata,
4354020b879Sbeck 	    "queueing %s of %s", ((act == DBC_ADD) ? "add" : "deletion"),
4364020b879Sbeck 	    dbc->key);
4374020b879Sbeck 	SLIST_INSERT_HEAD(&db_changes, dbc, entry);
4384020b879Sbeck 	return(0);
4394020b879Sbeck }
4404020b879Sbeck 
4414020b879Sbeck static int
do_changes(DB * db)44220a5c6fdSderaadt do_changes(DB *db)
44320a5c6fdSderaadt {
4444020b879Sbeck 	DBT			dbk, dbd;
4454020b879Sbeck 	struct db_change	*dbc;
4464020b879Sbeck 	int ret = 0;
4474020b879Sbeck 
4484020b879Sbeck 	while (!SLIST_EMPTY(&db_changes)) {
4494020b879Sbeck 		dbc = SLIST_FIRST(&db_changes);
4504020b879Sbeck 		switch (dbc->act) {
4514020b879Sbeck 		case DBC_ADD:
4524020b879Sbeck 			memset(&dbk, 0, sizeof(dbk));
4534020b879Sbeck 			dbk.size = strlen(dbc->key);
4544020b879Sbeck 			dbk.data = dbc->key;
4554020b879Sbeck 			memset(&dbd, 0, sizeof(dbd));
4564020b879Sbeck 			dbd.size = dbc->dsiz;
4574020b879Sbeck 			dbd.data = dbc->data;
4584020b879Sbeck 			if (db->put(db, &dbk, &dbd, 0)) {
4594020b879Sbeck 				db->sync(db, 0);
4604020b879Sbeck 				syslog_r(LOG_ERR, &sdata,
4614020b879Sbeck 				    "can't add %s to spamd db (%m)", dbc->key);
4624020b879Sbeck 				ret = -1;
4634020b879Sbeck 			}
4644020b879Sbeck 			db->sync(db, 0);
4654020b879Sbeck 			break;
4664020b879Sbeck 		case DBC_DEL:
4674020b879Sbeck 			memset(&dbk, 0, sizeof(dbk));
4684020b879Sbeck 			dbk.size = strlen(dbc->key);
4694020b879Sbeck 			dbk.data = dbc->key;
4704020b879Sbeck 			if (db->del(db, &dbk, 0)) {
4714020b879Sbeck 				syslog_r(LOG_ERR, &sdata,
4724020b879Sbeck 				    "can't delete %s from spamd db (%m)",
4734020b879Sbeck 				    dbc->key);
4744020b879Sbeck 				ret = -1;
4754020b879Sbeck 			}
4764020b879Sbeck 			break;
4774020b879Sbeck 		default:
4784020b879Sbeck 			syslog_r(LOG_ERR, &sdata, "Unrecognized db change");
4794020b879Sbeck 			ret = -1;
4804020b879Sbeck 		}
4814020b879Sbeck 		free(dbc->key);
4824020b879Sbeck 		dbc->key = NULL;
4834020b879Sbeck 		free(dbc->data);
4844020b879Sbeck 		dbc->data = NULL;
4854020b879Sbeck 		dbc->act = 0;
4864020b879Sbeck 		dbc->dsiz = 0;
4874020b879Sbeck 		SLIST_REMOVE_HEAD(&db_changes, entry);
488f576ce10Sbeck 		free(dbc);
4894020b879Sbeck 
4904020b879Sbeck 	}
4914020b879Sbeck 	return(ret);
4924020b879Sbeck }
4934020b879Sbeck 
494a6397563Sstephan /* -1=error, 0=notfound, 1=TRAPPED, 2=WHITE */
4951f68c1d4Sbeck int
db_addrstate(DB * db,char * key)496a6397563Sstephan db_addrstate(DB *db, char *key)
49705b7f62bSbeck {
49805b7f62bSbeck 	DBT			dbk, dbd;
499a6397563Sstephan 	struct gdata		gd;
50005b7f62bSbeck 
50105b7f62bSbeck 	memset(&dbk, 0, sizeof(dbk));
50205b7f62bSbeck 	dbk.size = strlen(key);
50305b7f62bSbeck 	dbk.data = key;
50400ddf0caSbeck 	memset(&dbd, 0, sizeof(dbd));
50551210d5cSmillert 	switch (db->get(db, &dbk, &dbd, 0)) {
50651210d5cSmillert 	case 1:
50751210d5cSmillert 		/* not found */
50805b7f62bSbeck 		return (0);
50951210d5cSmillert 	case 0:
51051210d5cSmillert 		if (gdcopyin(&dbd, &gd) != -1)
51151210d5cSmillert 			return (gd.pcount == -1 ? 1 : 2);
51251210d5cSmillert 		/* FALLTHROUGH */
51351210d5cSmillert 	default:
51451210d5cSmillert 		/* error */
51551210d5cSmillert 		return (-1);
51651210d5cSmillert 	}
51705b7f62bSbeck }
51805b7f62bSbeck 
51905b7f62bSbeck 
52005b7f62bSbeck int
greyscan(char * dbname)5211f68c1d4Sbeck greyscan(char *dbname)
5221f68c1d4Sbeck {
5234020b879Sbeck 	HASHINFO	hashinfo;
5247066e321Sbeck 	DBT		dbk, dbd;
5257066e321Sbeck 	DB		*db;
5261f68c1d4Sbeck 	struct gdata	gd;
5271f68c1d4Sbeck 	int		r;
5281e913b92Sbeck 	char		*a = NULL;
5291e913b92Sbeck 	size_t		asiz = 0;
5307066e321Sbeck 	time_t now = time(NULL);
5311f68c1d4Sbeck 
5321f68c1d4Sbeck 	/* walk db, expire, and whitelist */
5334020b879Sbeck 	memset(&hashinfo, 0, sizeof(hashinfo));
5344020b879Sbeck 	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
5351f68c1d4Sbeck 	if (db == NULL) {
5361f68c1d4Sbeck 		syslog_r(LOG_INFO, &sdata, "dbopen failed (%m)");
5371f68c1d4Sbeck 		return(-1);
5381f68c1d4Sbeck 	}
5391f68c1d4Sbeck 	memset(&dbk, 0, sizeof(dbk));
5401f68c1d4Sbeck 	memset(&dbd, 0, sizeof(dbd));
5411f68c1d4Sbeck 	for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r;
5421f68c1d4Sbeck 	    r = db->seq(db, &dbk, &dbd, R_NEXT)) {
54351210d5cSmillert 		if ((dbk.size < 1) || gdcopyin(&dbd, &gd) == -1) {
5444020b879Sbeck 			syslog_r(LOG_ERR, &sdata, "bogus entry in spamd database");
5451e913b92Sbeck 			goto bad;
5461f68c1d4Sbeck 		}
5471e913b92Sbeck 		if (asiz < dbk.size + 1) {
5481e913b92Sbeck 			char *tmp;
5491e913b92Sbeck 
550915dd247Sderaadt 			tmp = reallocarray(a, dbk.size, 2);
5511e913b92Sbeck 			if (tmp == NULL)
5521e913b92Sbeck 				goto bad;
5531e913b92Sbeck 			a = tmp;
5541e913b92Sbeck 			asiz = dbk.size * 2;
5551e913b92Sbeck 		}
5561e913b92Sbeck 		memset(a, 0, asiz);
5571e913b92Sbeck 		memcpy(a, dbk.data, dbk.size);
5581e913b92Sbeck 		if (gd.expire <= now && gd.pcount != -2) {
5591f68c1d4Sbeck 			/* get rid of entry */
5604020b879Sbeck 			if (queue_change(a, NULL, 0, DBC_DEL) == -1)
5611e913b92Sbeck 				goto bad;
5621e913b92Sbeck 		} else if (gd.pcount == -1)  {
5631e913b92Sbeck 			/* this is a greytrap hit */
5641e913b92Sbeck 			if ((addtrapaddr(a) == -1) &&
5654020b879Sbeck 			    (queue_change(a, NULL, 0, DBC_DEL) == -1))
5661e913b92Sbeck 				goto bad;
5671e913b92Sbeck 		} else if (gd.pcount >= 0 && gd.pass <= now) {
5681f68c1d4Sbeck 			int tuple = 0;
5691f68c1d4Sbeck 			char *cp;
570a6397563Sstephan 			int state;
5711f68c1d4Sbeck 
5721f68c1d4Sbeck 			/*
573a6397563Sstephan 			 * if not already TRAPPED,
5741f68c1d4Sbeck 			 * add address to whitelist
5751f68c1d4Sbeck 			 * add an address-keyed entry to db
5761f68c1d4Sbeck 			 */
5771f68c1d4Sbeck 			cp = strchr(a, '\n');
5781f68c1d4Sbeck 			if (cp != NULL) {
5791f68c1d4Sbeck 				tuple = 1;
5801f68c1d4Sbeck 				*cp = '\0';
5811f68c1d4Sbeck 			}
5824020b879Sbeck 
583a6397563Sstephan 			state = db_addrstate(db, a);
584a6397563Sstephan 			if (state != 1 && addwhiteaddr(a) == -1) {
5854020b879Sbeck 				if (cp != NULL)
5864020b879Sbeck 					*cp = '\n';
5874020b879Sbeck 				if (queue_change(a, NULL, 0, DBC_DEL) == -1)
5881f68c1d4Sbeck 					goto bad;
589a2836901Sbeck 			}
5904020b879Sbeck 
591a6397563Sstephan 			if (tuple && state <= 0) {
5924020b879Sbeck 				if (cp != NULL)
5934020b879Sbeck 					*cp = '\0';
5941f68c1d4Sbeck 				/* re-add entry, keyed only by ip */
5957066e321Sbeck 				gd.expire = now + whiteexp;
5961f68c1d4Sbeck 				dbd.size = sizeof(gd);
5971f68c1d4Sbeck 				dbd.data = &gd;
5984020b879Sbeck 				if (queue_change(a, (void *) &gd, sizeof(gd),
5994020b879Sbeck 				    DBC_ADD) == -1)
6001f68c1d4Sbeck 					goto bad;
6011f68c1d4Sbeck 				syslog_r(LOG_DEBUG, &sdata,
6021f68c1d4Sbeck 				    "whitelisting %s in %s", a, dbname);
6031f68c1d4Sbeck 			}
6041f68c1d4Sbeck 			if (debug)
6051f68c1d4Sbeck 				fprintf(stderr, "whitelisted %s\n", a);
6061f68c1d4Sbeck 		}
6071f68c1d4Sbeck 	}
6084020b879Sbeck 	(void) do_changes(db);
6090199266dSbeck 	db->close(db);
6100199266dSbeck 	db = NULL;
6111f68c1d4Sbeck 	configure_pf(whitelist, whitecount);
612b8f31536Sray 	configure_spamd(traplist, trapcount, trapcfg);
6131e913b92Sbeck 
6141e913b92Sbeck 	freeaddrlists();
6151e913b92Sbeck 	free(a);
6161e913b92Sbeck 	a = NULL;
6171f68c1d4Sbeck 	return(0);
6181f68c1d4Sbeck  bad:
6194020b879Sbeck 	(void) do_changes(db);
6201f68c1d4Sbeck 	db->close(db);
62184d82a5fSderaadt 	db = NULL;
6221e913b92Sbeck 	freeaddrlists();
6231e913b92Sbeck 	free(a);
6241e913b92Sbeck 	a = NULL;
6251f68c1d4Sbeck 	return(-1);
6261f68c1d4Sbeck }
6271f68c1d4Sbeck 
6281f68c1d4Sbeck int
trapcheck(DB * db,char * to)6292abcd8eaSbeck trapcheck(DB *db, char *to)
6302abcd8eaSbeck {
6312abcd8eaSbeck 	int			i, j, smatch = 0;
6322abcd8eaSbeck 	DBT			dbk, dbd;
6332abcd8eaSbeck 	struct mail_addr	*m;
6342abcd8eaSbeck 	char *			trap;
6352abcd8eaSbeck 	size_t			s;
6362abcd8eaSbeck 
6372abcd8eaSbeck 	trap = dequotetolower(to);
6382abcd8eaSbeck 	if (!SLIST_EMPTY(&match_suffix)) {
6392abcd8eaSbeck 		s = strlen(trap);
6402abcd8eaSbeck 		SLIST_FOREACH(m, &match_suffix, entry) {
6412abcd8eaSbeck 			j = s - strlen(m->addr);
6422abcd8eaSbeck 			if ((j >= 0) && (strcasecmp(trap+j, m->addr) == 0))
6432abcd8eaSbeck 				smatch = 1;
6442abcd8eaSbeck 		}
6452abcd8eaSbeck 		if (!smatch)
6462abcd8eaSbeck 			/* no suffixes match, so trap it */
6472abcd8eaSbeck 			return (0);
6482abcd8eaSbeck 	}
6492abcd8eaSbeck 	memset(&dbk, 0, sizeof(dbk));
6502abcd8eaSbeck 	dbk.size = strlen(trap);
6512abcd8eaSbeck 	dbk.data = trap;
6522abcd8eaSbeck 	memset(&dbd, 0, sizeof(dbd));
6532abcd8eaSbeck 	i = db->get(db, &dbk, &dbd, 0);
6542abcd8eaSbeck 	if (i == -1)
6552abcd8eaSbeck 		return (-1);
6562abcd8eaSbeck 	if (i)
6572abcd8eaSbeck 		/* didn't exist - so this doesn't match a known spamtrap  */
6582abcd8eaSbeck 		return (1);
6592abcd8eaSbeck 	else
6602abcd8eaSbeck 		/* To: address is a spamtrap, so add as a greytrap entry */
6612abcd8eaSbeck 		return (0);
6622abcd8eaSbeck }
6632abcd8eaSbeck 
6642abcd8eaSbeck int
twupdate(char * dbname,char * what,char * ip,char * source,char * expires)66520a5c6fdSderaadt twupdate(char *dbname, char *what, char *ip, char *source, char *expires)
66620a5c6fdSderaadt {
66798babcadSbeck 	/* we got a TRAP or WHITE update from someone else */
66898babcadSbeck 	HASHINFO	hashinfo;
66998babcadSbeck 	DBT		dbk, dbd;
67098babcadSbeck 	DB		*db;
67198babcadSbeck 	struct gdata	gd;
67298babcadSbeck 	time_t		now, expire;
67398babcadSbeck 	int		r, spamtrap;
67498babcadSbeck 
67598babcadSbeck 	now = time(NULL);
67698babcadSbeck 	/* expiry times have to be in the future */
677f66a3d85Sderaadt 	expire = strtonum(expires, now,
678f66a3d85Sderaadt 	    sizeof(time_t) == sizeof(int) ? INT_MAX : LLONG_MAX, NULL);
67998babcadSbeck 	if (expire == 0)
68098babcadSbeck 		return(-1);
68198babcadSbeck 
68298babcadSbeck 	if (strcmp(what, "TRAP") == 0)
68398babcadSbeck 		spamtrap = 1;
68498babcadSbeck 	else if (strcmp(what, "WHITE") == 0)
68598babcadSbeck 		spamtrap = 0;
68698babcadSbeck 	else
68798babcadSbeck 		return(-1);
68898babcadSbeck 
68998babcadSbeck 	memset(&hashinfo, 0, sizeof(hashinfo));
69098babcadSbeck 	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
69198babcadSbeck 	if (db == NULL)
69298babcadSbeck 		return(-1);
69398babcadSbeck 
69498babcadSbeck 	memset(&dbk, 0, sizeof(dbk));
69598babcadSbeck 	dbk.size = strlen(ip);
69698babcadSbeck 	dbk.data = ip;
69798babcadSbeck 	memset(&dbd, 0, sizeof(dbd));
69898babcadSbeck 	r = db->get(db, &dbk, &dbd, 0);
69998babcadSbeck 	if (r == -1)
70098babcadSbeck 		goto bad;
70198babcadSbeck 	if (r) {
70298babcadSbeck 		/* new entry */
70398babcadSbeck 		memset(&gd, 0, sizeof(gd));
70498babcadSbeck 		gd.first = now;
70598babcadSbeck 		gd.pcount = spamtrap ? -1 : 0;
70698babcadSbeck 		gd.expire = expire;
70798babcadSbeck 		memset(&dbk, 0, sizeof(dbk));
70898babcadSbeck 		dbk.size = strlen(ip);
70998babcadSbeck 		dbk.data = ip;
71098babcadSbeck 		memset(&dbd, 0, sizeof(dbd));
71198babcadSbeck 		dbd.size = sizeof(gd);
71298babcadSbeck 		dbd.data = &gd;
71398babcadSbeck 		r = db->put(db, &dbk, &dbd, 0);
71498babcadSbeck 		db->sync(db, 0);
71598babcadSbeck 		if (r)
71698babcadSbeck 			goto bad;
71798babcadSbeck 		if (debug)
71898babcadSbeck 			fprintf(stderr, "added %s %s\n",
71998babcadSbeck 			    spamtrap ? "trap entry for" : "", ip);
7202c79ec66Sbeck 		syslog_r(LOG_DEBUG, &sdata,
721c0289727Sreyk 		    "new %s from %s for %s, expires %s", what, source, ip,
7222c79ec66Sbeck 		    expires);
72398babcadSbeck 	} else {
72498babcadSbeck 		/* existing entry */
72551210d5cSmillert 		if (gdcopyin(&dbd, &gd) == -1) {
72698babcadSbeck 			/* whatever this is, it doesn't belong */
72798babcadSbeck 			db->del(db, &dbk, 0);
72898babcadSbeck 			db->sync(db, 0);
72998babcadSbeck 			goto bad;
73098babcadSbeck 		}
73198babcadSbeck 		if (spamtrap) {
73298babcadSbeck 			gd.pcount = -1;
73398babcadSbeck 			gd.bcount++;
73498babcadSbeck 		} else
73598babcadSbeck 			gd.pcount++;
73698babcadSbeck 		memset(&dbk, 0, sizeof(dbk));
73798babcadSbeck 		dbk.size = strlen(ip);
73898babcadSbeck 		dbk.data = ip;
73998babcadSbeck 		memset(&dbd, 0, sizeof(dbd));
74098babcadSbeck 		dbd.size = sizeof(gd);
74198babcadSbeck 		dbd.data = &gd;
74298babcadSbeck 		r = db->put(db, &dbk, &dbd, 0);
74398babcadSbeck 		db->sync(db, 0);
74498babcadSbeck 		if (r)
74598babcadSbeck 			goto bad;
74698babcadSbeck 		if (debug)
74798babcadSbeck 			fprintf(stderr, "updated %s\n", ip);
74898babcadSbeck 	}
74998babcadSbeck 	db->close(db);
75098babcadSbeck 	return(0);
75198babcadSbeck  bad:
75298babcadSbeck 	db->close(db);
75398babcadSbeck 	return(-1);
75498babcadSbeck 
75598babcadSbeck }
75698babcadSbeck 
75798babcadSbeck int
greyupdate(char * dbname,char * helo,char * ip,char * from,char * to,int sync,char * cip)7582c79ec66Sbeck greyupdate(char *dbname, char *helo, char *ip, char *from, char *to, int sync,
7592c79ec66Sbeck     char *cip)
7601f68c1d4Sbeck {
7614020b879Sbeck 	HASHINFO	hashinfo;
7627066e321Sbeck 	DBT		dbk, dbd;
7637066e321Sbeck 	DB		*db;
7641f68c1d4Sbeck 	char		*key = NULL;
7651e913b92Sbeck 	char		*lookup;
7661f68c1d4Sbeck 	struct gdata	gd;
7671e913b92Sbeck 	time_t		now, expire;
7682abcd8eaSbeck 	int		r, spamtrap;
7691f68c1d4Sbeck 
77084d82a5fSderaadt 	now = time(NULL);
77184d82a5fSderaadt 
7721f68c1d4Sbeck 	/* open with lock, find record, update, close, unlock */
7734020b879Sbeck 	memset(&hashinfo, 0, sizeof(hashinfo));
7744020b879Sbeck 	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
7751f68c1d4Sbeck 	if (db == NULL)
7761f68c1d4Sbeck 		return(-1);
7775b0222eaSbeck 	if (asprintf(&key, "%s\n%s\n%s\n%s", ip, helo, from, to) == -1)
7781f68c1d4Sbeck 		goto bad;
7792abcd8eaSbeck 	r = trapcheck(db, to);
7802abcd8eaSbeck 	switch (r) {
7812abcd8eaSbeck 	case 1:
7822abcd8eaSbeck 		/* do not trap */
7831e913b92Sbeck 		spamtrap = 0;
7841e913b92Sbeck 		lookup = key;
7851e913b92Sbeck 		expire = greyexp;
7862abcd8eaSbeck 		break;
7872abcd8eaSbeck 	case 0:
7882abcd8eaSbeck 		/* trap */
7891e913b92Sbeck 		spamtrap = 1;
7901e913b92Sbeck 		lookup = ip;
7911e913b92Sbeck 		expire = trapexp;
7924b6843aaSbeck 		syslog_r(LOG_DEBUG, &sdata, "Trapping %s for tuple %s", ip,
7934b6843aaSbeck 		    key);
7942abcd8eaSbeck 		break;
7952abcd8eaSbeck 	default:
7962abcd8eaSbeck 		goto bad;
7972abcd8eaSbeck 		break;
7981e913b92Sbeck 	}
7991e913b92Sbeck 	memset(&dbk, 0, sizeof(dbk));
8001e913b92Sbeck 	dbk.size = strlen(lookup);
8011e913b92Sbeck 	dbk.data = lookup;
8021f68c1d4Sbeck 	memset(&dbd, 0, sizeof(dbd));
8031f68c1d4Sbeck 	r = db->get(db, &dbk, &dbd, 0);
8041f68c1d4Sbeck 	if (r == -1)
8051f68c1d4Sbeck 		goto bad;
8061f68c1d4Sbeck 	if (r) {
8071f68c1d4Sbeck 		/* new entry */
808ee505fa1Sbeck 		if (sync &&  low_prio_mx_ip &&
809ee505fa1Sbeck 		    (strcmp(cip, low_prio_mx_ip) == 0) &&
810ee505fa1Sbeck 		    ((startup + 60)  < now)) {
8112c79ec66Sbeck 			/* we haven't seen a greylist entry for this tuple,
8122c79ec66Sbeck 			 * and yet the connection was to a low priority MX
8132c79ec66Sbeck 			 * which we know can't be hit first if the client
8142c79ec66Sbeck 			 * is adhering to the RFC's - soo.. kill it!
8152c79ec66Sbeck 			 */
8162c79ec66Sbeck 			spamtrap = 1;
8172c79ec66Sbeck 			lookup = ip;
8182c79ec66Sbeck 			expire = trapexp;
8192c79ec66Sbeck 			syslog_r(LOG_DEBUG, &sdata,
8202c79ec66Sbeck 			    "Trapping %s for trying %s first for tuple %s",
8212c79ec66Sbeck 			    ip, low_prio_mx_ip, key);
8222c79ec66Sbeck 		}
8231f68c1d4Sbeck 		memset(&gd, 0, sizeof(gd));
8241f68c1d4Sbeck 		gd.first = now;
8251f68c1d4Sbeck 		gd.bcount = 1;
8261e913b92Sbeck 		gd.pcount = spamtrap ? -1 : 0;
8271e913b92Sbeck 		gd.pass = now + expire;
8281e913b92Sbeck 		gd.expire = now + expire;
8291f68c1d4Sbeck 		memset(&dbk, 0, sizeof(dbk));
8301e913b92Sbeck 		dbk.size = strlen(lookup);
8311e913b92Sbeck 		dbk.data = lookup;
8321f68c1d4Sbeck 		memset(&dbd, 0, sizeof(dbd));
8331f68c1d4Sbeck 		dbd.size = sizeof(gd);
8341f68c1d4Sbeck 		dbd.data = &gd;
8351f68c1d4Sbeck 		r = db->put(db, &dbk, &dbd, 0);
836a2836901Sbeck 		db->sync(db, 0);
8371f68c1d4Sbeck 		if (r)
8381f68c1d4Sbeck 			goto bad;
8391f68c1d4Sbeck 		if (debug)
8401e913b92Sbeck 			fprintf(stderr, "added %s %s\n",
8411e913b92Sbeck 			    spamtrap ? "greytrap entry for" : "", lookup);
842c0289727Sreyk 		syslog_r(LOG_DEBUG, &sdata,
843c0289727Sreyk 		    "new %sentry %s from %s to %s, helo %s",
844c0289727Sreyk 		    spamtrap ? "greytrap " : "", ip, from, to, helo);
8451f68c1d4Sbeck 	} else {
8461f68c1d4Sbeck 		/* existing entry */
84751210d5cSmillert 		if (gdcopyin(&dbd, &gd) == -1) {
8481f68c1d4Sbeck 			/* whatever this is, it doesn't belong */
8491f68c1d4Sbeck 			db->del(db, &dbk, 0);
850a2836901Sbeck 			db->sync(db, 0);
8511f68c1d4Sbeck 			goto bad;
8521f68c1d4Sbeck 		}
8531f68c1d4Sbeck 		gd.bcount++;
8541e913b92Sbeck 		gd.pcount = spamtrap ? -1 : 0;
8557066e321Sbeck 		if (gd.first + passtime < now)
8561f68c1d4Sbeck 			gd.pass = now;
8571f68c1d4Sbeck 		memset(&dbk, 0, sizeof(dbk));
8581e913b92Sbeck 		dbk.size = strlen(lookup);
8591e913b92Sbeck 		dbk.data = lookup;
8601f68c1d4Sbeck 		memset(&dbd, 0, sizeof(dbd));
8611f68c1d4Sbeck 		dbd.size = sizeof(gd);
8621f68c1d4Sbeck 		dbd.data = &gd;
8631f68c1d4Sbeck 		r = db->put(db, &dbk, &dbd, 0);
864a2836901Sbeck 		db->sync(db, 0);
8651f68c1d4Sbeck 		if (r)
8661f68c1d4Sbeck 			goto bad;
8671f68c1d4Sbeck 		if (debug)
8681e913b92Sbeck 			fprintf(stderr, "updated %s\n", lookup);
8691f68c1d4Sbeck 	}
8701f68c1d4Sbeck 	free(key);
8717066e321Sbeck 	key = NULL;
87284d82a5fSderaadt 	db->close(db);
87384d82a5fSderaadt 	db = NULL;
87498babcadSbeck 
87598babcadSbeck 	/* Entry successfully update, sent out sync message */
87698babcadSbeck 	if (syncsend && sync) {
8772c79ec66Sbeck 		if (spamtrap) {
8782c79ec66Sbeck 			syslog_r(LOG_DEBUG, &sdata,
8792c79ec66Sbeck 			    "sync_trap %s", ip);
88098babcadSbeck 			sync_trapped(now, now + expire, ip);
8812c79ec66Sbeck 		}
88298babcadSbeck 		else
88398babcadSbeck 			sync_update(now, helo, ip, from, to);
88498babcadSbeck 	}
8851f68c1d4Sbeck 	return(0);
8861f68c1d4Sbeck  bad:
8871f68c1d4Sbeck 	free(key);
8887066e321Sbeck 	key = NULL;
8891f68c1d4Sbeck 	db->close(db);
89084d82a5fSderaadt 	db = NULL;
8911f68c1d4Sbeck 	return(-1);
8921f68c1d4Sbeck }
8931f68c1d4Sbeck 
8941f68c1d4Sbeck int
twread(char * buf)89520a5c6fdSderaadt twread(char *buf)
89620a5c6fdSderaadt {
89798babcadSbeck 	if ((strncmp(buf, "WHITE:", 6) == 0) ||
89898babcadSbeck 	    (strncmp(buf, "TRAP:", 5) == 0)) {
89998babcadSbeck 		char **ap, *argv[5];
90098babcadSbeck 		int argc = 0;
90120a5c6fdSderaadt 
90220a5c6fdSderaadt 		for (ap = argv;
90320a5c6fdSderaadt 		    ap < &argv[4] && (*ap = strsep(&buf, ":")) != NULL;) {
90498babcadSbeck 			if (**ap != '\0')
90598babcadSbeck 				ap++;
90698babcadSbeck 			argc++;
90798babcadSbeck 		}
90898babcadSbeck 		*ap = NULL;
90998babcadSbeck 		if (argc != 4)
91098babcadSbeck 			return (-1);
91198babcadSbeck 		twupdate(PATH_SPAMD_DB, argv[0], argv[1], argv[2], argv[3]);
91220a5c6fdSderaadt 		return (0);
91398babcadSbeck 	} else
91420a5c6fdSderaadt 		return (-1);
91598babcadSbeck }
91698babcadSbeck 
91798babcadSbeck int
greyreader(void)9181f68c1d4Sbeck greyreader(void)
9191f68c1d4Sbeck {
9202c79ec66Sbeck 	char cip[32], ip[32], helo[MAX_MAIL], from[MAX_MAIL], to[MAX_MAIL];
9212c79ec66Sbeck 	char *buf;
9221f68c1d4Sbeck 	size_t len;
92398babcadSbeck 	int state, sync;
924ef8fe961Sitojun 	struct addrinfo hints, *res;
925ef8fe961Sitojun 
926ef8fe961Sitojun 	memset(&hints, 0, sizeof(hints));
92794c946baSitojun 	hints.ai_family = AF_INET;		/*for now*/
928ef8fe961Sitojun 	hints.ai_socktype = SOCK_DGRAM;		/*dummy*/
929ef8fe961Sitojun 	hints.ai_protocol = IPPROTO_UDP;	/*dummy*/
930ef8fe961Sitojun 	hints.ai_flags = AI_NUMERICHOST;
9311f68c1d4Sbeck 
9321f68c1d4Sbeck 	state = 0;
93398babcadSbeck 	sync = 1;
9348a6ccc17Sbeck 	if (grey == NULL) {
9358a6ccc17Sbeck 		syslog_r(LOG_ERR, &sdata, "No greylist pipe stream!\n");
93651210d5cSmillert 		return (-1);
9378a6ccc17Sbeck 	}
9382abcd8eaSbeck 
9392abcd8eaSbeck 	/* grab trap suffixes */
9402abcd8eaSbeck 	readsuffixlists();
9412abcd8eaSbeck 
9421f68c1d4Sbeck 	while ((buf = fgetln(grey, &len))) {
9431f68c1d4Sbeck 		if (buf[len - 1] == '\n')
9441f68c1d4Sbeck 			buf[len - 1] = '\0';
9451f68c1d4Sbeck 		else
9461f68c1d4Sbeck 			/* all valid lines end in \n */
9471f68c1d4Sbeck 			continue;
9481f68c1d4Sbeck 		if (strlen(buf) < 4)
9491f68c1d4Sbeck 			continue;
9501f68c1d4Sbeck 
95198babcadSbeck 		if (strcmp(buf, "SYNC") == 0) {
95298babcadSbeck 			sync = 0;
95398babcadSbeck 			continue;
95498babcadSbeck 		}
95598babcadSbeck 
9561f68c1d4Sbeck 		switch (state) {
9571f68c1d4Sbeck 		case 0:
95898babcadSbeck 			if (twread(buf) == 0) {
95998babcadSbeck 				state = 0;
96098babcadSbeck 				break;
96198babcadSbeck 			}
9625b0222eaSbeck 			if (strncmp(buf, "HE:", 3) != 0) {
9632c79ec66Sbeck 				if (strncmp(buf, "CO:", 3) == 0)
9642c79ec66Sbeck 					strlcpy(cip, buf+3, sizeof(cip));
9655b0222eaSbeck 				state = 0;
9665b0222eaSbeck 				break;
9675b0222eaSbeck 			}
9685b0222eaSbeck 			strlcpy(helo, buf+3, sizeof(helo));
9695b0222eaSbeck 			state = 1;
9705b0222eaSbeck 			break;
9715b0222eaSbeck 		case 1:
9721f68c1d4Sbeck 			if (strncmp(buf, "IP:", 3) != 0)
9731f68c1d4Sbeck 				break;
9741f68c1d4Sbeck 			strlcpy(ip, buf+3, sizeof(ip));
975ef8fe961Sitojun 			if (getaddrinfo(ip, NULL, &hints, &res) == 0) {
976ef8fe961Sitojun 				freeaddrinfo(res);
9775b0222eaSbeck 				state = 2;
978ef8fe961Sitojun 			} else
9791f68c1d4Sbeck 				state = 0;
9801f68c1d4Sbeck 			break;
9815b0222eaSbeck 		case 2:
9821f68c1d4Sbeck 			if (strncmp(buf, "FR:", 3) != 0) {
9831f68c1d4Sbeck 				state = 0;
9841f68c1d4Sbeck 				break;
9851f68c1d4Sbeck 			}
9861f68c1d4Sbeck 			strlcpy(from, buf+3, sizeof(from));
9875b0222eaSbeck 			state = 3;
9881f68c1d4Sbeck 			break;
9895b0222eaSbeck 		case 3:
9901f68c1d4Sbeck 			if (strncmp(buf, "TO:", 3) != 0) {
9911f68c1d4Sbeck 				state = 0;
9921f68c1d4Sbeck 				break;
9931f68c1d4Sbeck 			}
9941f68c1d4Sbeck 			strlcpy(to, buf+3, sizeof(to));
9951f68c1d4Sbeck 			if (debug)
9961f68c1d4Sbeck 				fprintf(stderr,
9975b0222eaSbeck 				    "Got Grey HELO %s, IP %s from %s to %s\n",
9985b0222eaSbeck 				    helo, ip, from, to);
9992c79ec66Sbeck 			greyupdate(PATH_SPAMD_DB, helo, ip, from, to, sync, cip);
10000af273bdSbeck 			sync = 1;
10011f68c1d4Sbeck 			state = 0;
10021f68c1d4Sbeck 			break;
10031f68c1d4Sbeck 		}
10041f68c1d4Sbeck 	}
10051f68c1d4Sbeck 	return (0);
10061f68c1d4Sbeck }
10071f68c1d4Sbeck 
10081f68c1d4Sbeck void
greyscanner(void)10091f68c1d4Sbeck greyscanner(void)
10101f68c1d4Sbeck {
10111f68c1d4Sbeck 	for (;;) {
10124020b879Sbeck 		if (greyscan(PATH_SPAMD_DB) == -1)
10137066e321Sbeck 			syslog_r(LOG_NOTICE, &sdata, "scan of %s failed",
10147066e321Sbeck 			    PATH_SPAMD_DB);
10154020b879Sbeck 		sleep(DB_SCAN_INTERVAL);
10161f68c1d4Sbeck 	}
10171f68c1d4Sbeck }
10181f68c1d4Sbeck 
10194020b879Sbeck static void
drop_privs(void)10204020b879Sbeck drop_privs(void)
10211f68c1d4Sbeck {
10221f68c1d4Sbeck 	/*
10231f68c1d4Sbeck 	 * lose root, continue as non-root user
10241f68c1d4Sbeck 	 */
1025747158c7Smestre 	if (setgroups(1, &pw->pw_gid) ||
1026747158c7Smestre 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
1027747158c7Smestre 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) {
1028747158c7Smestre 		syslog_r(LOG_ERR, &sdata, "failed to drop privs (%m)");
1029747158c7Smestre 		exit(1);
10301f68c1d4Sbeck 	}
10314020b879Sbeck }
10324020b879Sbeck 
10331cdc3374Sbeck void
check_spamd_db(void)10344020b879Sbeck check_spamd_db(void)
10354020b879Sbeck {
10364020b879Sbeck 	HASHINFO hashinfo;
10374020b879Sbeck 	int i = -1;
10384020b879Sbeck 	DB *db;
10394020b879Sbeck 
10404020b879Sbeck 	/* check to see if /var/db/spamd exists, if not, create it */
10414020b879Sbeck 	memset(&hashinfo, 0, sizeof(hashinfo));
10424020b879Sbeck 	db = dbopen(PATH_SPAMD_DB, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
10434020b879Sbeck 
10444020b879Sbeck 	if (db == NULL) {
10454020b879Sbeck 		switch (errno) {
10464020b879Sbeck 		case ENOENT:
10474020b879Sbeck 			i = open(PATH_SPAMD_DB, O_RDWR|O_CREAT, 0644);
10484020b879Sbeck 			if (i == -1) {
10494020b879Sbeck 				syslog_r(LOG_ERR, &sdata,
10504020b879Sbeck 				    "create %s failed (%m)", PATH_SPAMD_DB);
10514020b879Sbeck 				exit(1);
10524020b879Sbeck 			}
10534020b879Sbeck 			/* if we are dropping privs, chown to that user */
10544020b879Sbeck 			if (pw && (fchown(i, pw->pw_uid, pw->pw_gid) == -1)) {
10554020b879Sbeck 				syslog_r(LOG_ERR, &sdata,
10564020b879Sbeck 				    "chown %s failed (%m)", PATH_SPAMD_DB);
10574020b879Sbeck 				exit(1);
10584020b879Sbeck 			}
10594020b879Sbeck 			close(i);
10604020b879Sbeck 			return;
10614020b879Sbeck 			break;
10624020b879Sbeck 		default:
10634020b879Sbeck 			syslog_r(LOG_ERR, &sdata, "open of %s failed (%m)",
10644020b879Sbeck 			    PATH_SPAMD_DB);
10654020b879Sbeck 			exit(1);
10664020b879Sbeck 		}
10674020b879Sbeck 	}
10684020b879Sbeck 	db->sync(db, 0);
10694020b879Sbeck 	db->close(db);
10704020b879Sbeck }
10714020b879Sbeck 
10724020b879Sbeck 
10734020b879Sbeck int
greywatcher(void)10744020b879Sbeck greywatcher(void)
10754020b879Sbeck {
10764020b879Sbeck 	struct sigaction sa;
10774020b879Sbeck 
10781cdc3374Sbeck 	drop_privs();
10791f68c1d4Sbeck 
1080800fd717Smestre 	if (unveil(PATH_SPAMD_DB, "rw") == -1) {
1081800fd717Smestre 		syslog_r(LOG_ERR, &sdata, "unveil failed (%m)");
1082800fd717Smestre 		exit(1);
1083800fd717Smestre 	}
1084800fd717Smestre 	if (unveil(alloweddomains_file, "r") == -1) {
1085800fd717Smestre 		syslog_r(LOG_ERR, &sdata, "unveil failed (%m)");
1086800fd717Smestre 		exit(1);
1087800fd717Smestre 	}
1088800fd717Smestre 	if (unveil(PATH_PFCTL, "x") == -1) {
1089800fd717Smestre 		syslog_r(LOG_ERR, &sdata, "unveil failed (%m)");
1090800fd717Smestre 		exit(1);
1091800fd717Smestre 	}
10922d7c0bc3Sbeck 	if (pledge("stdio rpath wpath inet flock proc exec", NULL) == -1) {
10932d7c0bc3Sbeck 		syslog_r(LOG_ERR, &sdata, "pledge failed (%m)");
10942d7c0bc3Sbeck 		exit(1);
10952d7c0bc3Sbeck 	}
10962d7c0bc3Sbeck 
1097ee505fa1Sbeck 	startup = time(NULL);
10988a6ccc17Sbeck 	db_pid = fork();
10998a6ccc17Sbeck 	switch (db_pid) {
11007066e321Sbeck 	case -1:
11018a6ccc17Sbeck 		syslog_r(LOG_ERR, &sdata, "fork failed (%m)");
11028a6ccc17Sbeck 		exit(1);
11037066e321Sbeck 	case 0:
11041f68c1d4Sbeck 		/*
11051f68c1d4Sbeck 		 * child, talks to jailed spamd over greypipe,
11061f68c1d4Sbeck 		 * updates db. has no access to pf.
11071f68c1d4Sbeck 		 */
11081f68c1d4Sbeck 		close(pfdev);
11091f68c1d4Sbeck 		setproctitle("(%s update)", PATH_SPAMD_DB);
111051210d5cSmillert 		if (greyreader() == -1) {
111129c90403Sphessler 		    syslog_r(LOG_ERR, &sdata, "greyreader failed (%m)");
11127066e321Sbeck 		    _exit(1);
11137066e321Sbeck 		}
111451210d5cSmillert 		_exit(0);
111551210d5cSmillert 	}
11164020b879Sbeck 
11172c79ec66Sbeck 
11182c79ec66Sbeck 	fclose(grey);
11191f68c1d4Sbeck 	/*
11201f68c1d4Sbeck 	 * parent, scans db periodically for changes and updates
11211f68c1d4Sbeck 	 * pf whitelist table accordingly.
11221f68c1d4Sbeck 	 */
11232c79ec66Sbeck 
11243ac6234cSmoritz 	sigfillset(&sa.sa_mask);
11253ac6234cSmoritz 	sa.sa_flags = SA_RESTART;
11263ac6234cSmoritz 	sa.sa_handler = sig_term_chld;
11273ac6234cSmoritz 	sigaction(SIGTERM, &sa, NULL);
11283ac6234cSmoritz 	sigaction(SIGHUP, &sa, NULL);
11293ac6234cSmoritz 	sigaction(SIGCHLD, &sa, NULL);
11303ac6234cSmoritz 	sigaction(SIGINT, &sa, NULL);
11318a6ccc17Sbeck 
11321f68c1d4Sbeck 	setproctitle("(pf <spamd-white> update)");
11331f68c1d4Sbeck 	greyscanner();
11347066e321Sbeck 	exit(1);
11351f68c1d4Sbeck }
1136