GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: libexec/spamd/grey.c Lines: 0 581 0.0 %
Date: 2017-11-13 Branches: 0 318 0.0 %

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