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

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