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

Line Branch Exec Source
1
/*	$OpenBSD: spamd-setup.c,v 1.50 2017/07/07 00:10:15 djm Exp $ */
2
3
/*
4
 * Copyright (c) 2003 Bob Beck.  All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions
8
 * are met:
9
 * 1. Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 * 2. Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
 */
26
27
#include <arpa/inet.h>
28
#include <sys/socket.h>
29
#include <sys/types.h>
30
31
#include <err.h>
32
#include <errno.h>
33
#include <fcntl.h>
34
#include <netdb.h>
35
#include <pwd.h>
36
#include <stdio.h>
37
#include <stdlib.h>
38
#include <string.h>
39
#include <unistd.h>
40
#include <zlib.h>
41
42
#define PATH_FTP		"/usr/bin/ftp"
43
#define PATH_PFCTL		"/sbin/pfctl"
44
#define PATH_SPAMD_CONF		"/etc/mail/spamd.conf"
45
#define SPAMD_ARG_MAX		256 /* max # of args to an exec */
46
#define SPAMD_USER		"_spamd"
47
48
struct cidr {
49
	u_int32_t addr;
50
	u_int8_t bits;
51
};
52
53
struct bl {
54
	u_int32_t addr;
55
	int8_t b;
56
	int8_t w;
57
};
58
59
struct blacklist {
60
	char *name;
61
	char *message;
62
	struct bl *bl;
63
	size_t blc, bls;
64
	u_int8_t black;
65
};
66
67
u_int32_t	 imask(u_int8_t);
68
u_int8_t	 maxblock(u_int32_t, u_int8_t);
69
u_int8_t	 maxdiff(u_int32_t, u_int32_t);
70
struct cidr	*range2cidrlist(struct cidr *, u_int *, u_int *, u_int32_t,
71
		     u_int32_t);
72
void		 cidr2range(struct cidr, u_int32_t *, u_int32_t *);
73
char		*atop(u_int32_t);
74
int		 parse_netblock(char *, struct bl *, struct bl *, int);
75
int		 open_child(char *, char **, int);
76
int		 fileget(char *);
77
int		 open_file(char *, char *);
78
char		*fix_quoted_colons(char *);
79
void		 do_message(FILE *, char *);
80
struct bl	*add_blacklist(struct bl *, size_t *, size_t *, gzFile, int);
81
int		 cmpbl(const void *, const void *);
82
struct cidr	*collapse_blacklist(struct bl *, size_t, u_int *);
83
int		 configure_spamd(u_short, char *, char *, struct cidr *, u_int);
84
int		 configure_pf(struct cidr *);
85
int		 getlist(char **, char *, struct blacklist *, struct blacklist *);
86
__dead void	 usage(void);
87
88
uid_t		  spamd_uid;
89
gid_t		  spamd_gid;
90
int		  debug;
91
int		  dryrun;
92
int		  greyonly = 1;
93
94
extern char 	 *__progname;
95
96
#define MAXIMUM(a,b) (((a)>(b))?(a):(b))
97
98
u_int32_t
99
imask(u_int8_t b)
100
{
101
	if (b == 0)
102
		return (0);
103
	return (0xffffffffU << (32 - b));
104
}
105
106
u_int8_t
107
maxblock(u_int32_t addr, u_int8_t bits)
108
{
109
	u_int32_t m;
110
111
	while (bits > 0) {
112
		m = imask(bits - 1);
113
114
		if ((addr & m) != addr)
115
			return (bits);
116
		bits--;
117
	}
118
	return (bits);
119
}
120
121
u_int8_t
122
maxdiff(u_int32_t a, u_int32_t b)
123
{
124
	u_int8_t bits = 0;
125
	u_int32_t m;
126
127
	b++;
128
	while (bits < 32) {
129
		m = imask(bits);
130
131
		if ((a & m) != (b & m))
132
			return (bits);
133
		bits++;
134
	}
135
	return (bits);
136
}
137
138
struct cidr *
139
range2cidrlist(struct cidr *list, u_int *cli, u_int *cls, u_int32_t start,
140
    u_int32_t end)
141
{
142
	u_int8_t maxsize, diff;
143
	struct cidr *tmp;
144
145
	while (end >= start) {
146
		maxsize = maxblock(start, 32);
147
		diff = maxdiff(start, end);
148
149
		maxsize = MAXIMUM(maxsize, diff);
150
		if (*cls <= *cli + 1) {		/* one extra for terminator */
151
			tmp = reallocarray(list, *cls + 32,
152
			    sizeof(struct cidr));
153
			if (tmp == NULL)
154
				err(1, NULL);
155
			list = tmp;
156
			*cls += 32;
157
		}
158
		list[*cli].addr = start;
159
		list[*cli].bits = maxsize;
160
		(*cli)++;
161
		start = start + (1 << (32 - maxsize));
162
	}
163
	return (list);
164
}
165
166
void
167
cidr2range(struct cidr cidr, u_int32_t *start, u_int32_t *end)
168
{
169
	*start = cidr.addr;
170
	*end = cidr.addr + (1 << (32 - cidr.bits)) - 1;
171
}
172
173
char *
174
atop(u_int32_t addr)
175
{
176
	struct in_addr in;
177
178
	memset(&in, 0, sizeof(in));
179
	in.s_addr = htonl(addr);
180
	return (inet_ntoa(in));
181
}
182
183
int
184
parse_netblock(char *buf, struct bl *start, struct bl *end, int white)
185
{
186
	char astring[16], astring2[16];
187
	unsigned maskbits;
188
	struct cidr c;
189
190
	/* skip leading spaces */
191
	while (*buf == ' ')
192
		buf++;
193
	/* bail if it's a comment */
194
	if (*buf == '#')
195
		return (0);
196
	/* otherwise, look for a netblock of some sort */
197
	if (sscanf(buf, "%15[^/]/%u", astring, &maskbits) == 2) {
198
		/* looks like a cidr */
199
		memset(&c.addr, 0, sizeof(c.addr));
200
		if (inet_net_pton(AF_INET, astring, &c.addr, sizeof(c.addr))
201
		    == -1)
202
			return (0);
203
		c.addr = ntohl(c.addr);
204
		if (maskbits > 32)
205
			return (0);
206
		c.bits = maskbits;
207
		cidr2range(c, &start->addr, &end->addr);
208
		end->addr += 1;
209
	} else if (sscanf(buf, "%15[0123456789.]%*[ -]%15[0123456789.]",
210
	    astring, astring2) == 2) {
211
		/* looks like start - end */
212
		memset(&start->addr, 0, sizeof(start->addr));
213
		memset(&end->addr, 0, sizeof(end->addr));
214
		if (inet_net_pton(AF_INET, astring, &start->addr,
215
		    sizeof(start->addr)) == -1)
216
			return (0);
217
		start->addr = ntohl(start->addr);
218
		if (inet_net_pton(AF_INET, astring2, &end->addr,
219
		    sizeof(end->addr)) == -1)
220
			return (0);
221
		end->addr = ntohl(end->addr) + 1;
222
		if (start > end)
223
			return (0);
224
	} else if (sscanf(buf, "%15[0123456789.]", astring) == 1) {
225
		/* just a single address */
226
		memset(&start->addr, 0, sizeof(start->addr));
227
		if (inet_net_pton(AF_INET, astring, &start->addr,
228
		    sizeof(start->addr)) == -1)
229
			return (0);
230
		start->addr = ntohl(start->addr);
231
		end->addr = start->addr + 1;
232
	} else
233
		return (0);
234
235
	if (white) {
236
		start->b = 0;
237
		start->w = 1;
238
		end->b = 0;
239
		end->w = -1;
240
	} else {
241
		start->b = 1;
242
		start->w = 0;
243
		end->b = -1;
244
		end->w = 0;
245
	}
246
	return (1);
247
}
248
249
void
250
drop_privileges(void)
251
{
252
	if (setgroups(1, &spamd_gid) != 0)
253
		err(1, "setgroups %ld", (long)spamd_gid);
254
	if (setresgid(spamd_gid, spamd_gid, spamd_gid) != 0)
255
		err(1, "setresgid %ld", (long)spamd_gid);
256
	if (setresuid(spamd_uid, spamd_uid, spamd_uid) != 0)
257
		err(1, "setresuid %ld", (long)spamd_uid);
258
}
259
260
int
261
open_child(char *file, char **argv, int drop_privs)
262
{
263
	int pdes[2];
264
265
	if (pipe(pdes) != 0)
266
		return (-1);
267
	switch (fork()) {
268
	case -1:
269
		close(pdes[0]);
270
		close(pdes[1]);
271
		return (-1);
272
	case 0:
273
		/* child */
274
		close(pdes[0]);
275
		if (pdes[1] != STDOUT_FILENO) {
276
			dup2(pdes[1], STDOUT_FILENO);
277
			close(pdes[1]);
278
		}
279
		if (drop_privs)
280
			drop_privileges();
281
		closefrom(STDERR_FILENO + 1);
282
		execvp(file, argv);
283
		_exit(1);
284
	}
285
286
	/* parent */
287
	close(pdes[1]);
288
	return (pdes[0]);
289
}
290
291
int
292
fileget(char *url)
293
{
294
	char *argv[6];
295
296
	argv[0] = "ftp";
297
	argv[1] = "-V";
298
	argv[2] = "-o";
299
	argv[3] = "-";
300
	argv[4] = url;
301
	argv[5] = NULL;
302
303
	if (debug)
304
		fprintf(stderr, "Getting %s\n", url);
305
306
	return (open_child(PATH_FTP, argv, 1));
307
}
308
309
int
310
open_file(char *method, char *file)
311
{
312
	char *url;
313
	char **ap, **argv;
314
	int len, i, oerrno;
315
316
	if ((method == NULL) || (strcmp(method, "file") == 0))
317
		return (open(file, O_RDONLY));
318
	if (strcmp(method, "http") == 0 || strcmp(method, "https") == 0 ||
319
	    strcmp(method, "ftp") == 0) {
320
		if (asprintf(&url, "%s://%s", method, file) == -1)
321
			return (-1);
322
		i = fileget(url);
323
		free(url);
324
		return (i);
325
	} else if (strcmp(method, "exec") == 0) {
326
		len = strlen(file);
327
		argv = calloc(len, sizeof(char *));
328
		if (argv == NULL)
329
			return (-1);
330
		for (ap = argv; ap < &argv[len - 1] &&
331
		    (*ap = strsep(&file, " \t")) != NULL;) {
332
			if (**ap != '\0')
333
				ap++;
334
		}
335
		*ap = NULL;
336
		i = open_child(argv[0], argv, 0);
337
		oerrno = errno;
338
		free(argv);
339
		errno = oerrno;
340
		return (i);
341
	}
342
	errx(1, "Unknown method %s", method);
343
	return (-1); /* NOTREACHED */
344
}
345
346
/*
347
 * fix_quoted_colons walks through a buffer returned by cgetent.  We
348
 * look for quoted strings, to escape colons (:) in quoted strings for
349
 * getcap by replacing them with \C so cgetstr() deals with it correctly
350
 * without having to see the \C bletchery in a configuration file that
351
 * needs to have urls in it. Frees the buffer passed to it, passes back
352
 * another larger one, with can be used with cgetxxx(), like the original
353
 * buffer, it must be freed by the caller.
354
 * This should really be a temporary fix until there is a sanctioned
355
 * way to make getcap(3) handle quoted strings like this in a nicer
356
 * way.
357
 */
358
char *
359
fix_quoted_colons(char *buf)
360
{
361
	int in = 0;
362
	size_t i, j = 0;
363
	char *newbuf, last;
364
365
	/* Allocate enough space for a buf of all colons (impossible). */
366
	newbuf = malloc(2 * strlen(buf) + 1);
367
	if (newbuf == NULL)
368
		return (NULL);
369
	last = '\0';
370
	for (i = 0; i < strlen(buf); i++) {
371
		switch (buf[i]) {
372
		case ':':
373
			if (in) {
374
				newbuf[j++] = '\\';
375
				newbuf[j++] = 'C';
376
			} else
377
				newbuf[j++] = buf[i];
378
			break;
379
		case '"':
380
			if (last != '\\')
381
				in = !in;
382
			newbuf[j++] = buf[i];
383
			break;
384
		default:
385
			newbuf[j++] = buf[i];
386
		}
387
		last = buf[i];
388
	}
389
	free(buf);
390
	newbuf[j] = '\0';
391
	return (newbuf);
392
}
393
394
void
395
do_message(FILE *sdc, char *msg)
396
{
397
	size_t i, bs = 0, bu = 0, len;
398
	ssize_t n;
399
	char *buf = NULL, last, *tmp;
400
	int fd;
401
402
	len = strlen(msg);
403
	if (msg[0] == '"' && msg[len - 1] == '"') {
404
		/* quoted msg, escape newlines and send it out */
405
		msg[len - 1] = '\0';
406
		buf = msg + 1;
407
		bu = len - 2;
408
		goto sendit;
409
	} else {
410
		/*
411
		 * message isn't quoted - try to open a local
412
		 * file and read the message from it.
413
		 */
414
		fd = open(msg, O_RDONLY);
415
		if (fd == -1)
416
			err(1, "Can't open message from %s", msg);
417
		for (;;) {
418
			if (bu == bs) {
419
				tmp = realloc(buf, bs + 8192);
420
				if (tmp == NULL)
421
					err(1, NULL);
422
				bs += 8192;
423
				buf = tmp;
424
			}
425
426
			n = read(fd, buf + bu, bs - bu);
427
			if (n == 0) {
428
				goto sendit;
429
			} else if (n == -1) {
430
				err(1, "Can't read from %s", msg);
431
			} else
432
				bu += n;
433
		}
434
		buf[bu]='\0';
435
	}
436
 sendit:
437
	fprintf(sdc, ";\"");
438
	last = '\0';
439
	for (i = 0; i < bu; i++) {
440
		/* handle escaping the things spamd wants */
441
		switch (buf[i]) {
442
		case 'n':
443
			if (last == '\\')
444
				fprintf(sdc, "\\\\n");
445
			else
446
				fputc('n', sdc);
447
			last = '\0';
448
			break;
449
		case '\n':
450
			fprintf(sdc, "\\n");
451
			last = '\0';
452
			break;
453
		case '"':
454
			fputc('\\', sdc);
455
			/* FALLTHROUGH */
456
		default:
457
			fputc(buf[i], sdc);
458
			last = '\0';
459
		}
460
	}
461
	fputc('"', sdc);
462
	if (bs != 0)
463
		free(buf);
464
}
465
466
/* retrieve a list from fd. add to blacklist bl */
467
struct bl *
468
add_blacklist(struct bl *bl, size_t *blc, size_t *bls, gzFile gzf, int white)
469
{
470
	int i, n, start, bu = 0, bs = 0, serrno = 0;
471
	char *buf = NULL, *tmp;
472
	struct bl *blt;
473
474
	for (;;) {
475
		/* read in gzf, then parse */
476
		if (bu == bs) {
477
			tmp = realloc(buf, bs + (1024 * 1024) + 1);
478
			if (tmp == NULL) {
479
				serrno = errno;
480
				free(buf);
481
				buf = NULL;
482
				bs = 0;
483
				goto bldone;
484
			}
485
			bs += 1024 * 1024;
486
			buf = tmp;
487
		}
488
489
		n = gzread(gzf, buf + bu, bs - bu);
490
		if (n == 0)
491
			goto parse;
492
		else if (n == -1) {
493
			serrno = errno;
494
			goto bldone;
495
		} else
496
			bu += n;
497
	}
498
 parse:
499
	start = 0;
500
	/* we assume that there is an IP for every 14 bytes */
501
	if (*blc + bu / 7 >= *bls) {
502
		*bls += bu / 7;
503
		blt = reallocarray(bl, *bls, sizeof(struct bl));
504
		if (blt == NULL) {
505
			*bls -= bu / 7;
506
			serrno = errno;
507
			goto bldone;
508
		}
509
		bl = blt;
510
	}
511
	for (i = 0; i <= bu; i++) {
512
		if (*blc + 1 >= *bls) {
513
			*bls += 1024;
514
			blt = reallocarray(bl, *bls, sizeof(struct bl));
515
			if (blt == NULL) {
516
				*bls -= 1024;
517
				serrno = errno;
518
				goto bldone;
519
			}
520
			bl = blt;
521
		}
522
		if (i == bu || buf[i] == '\n') {
523
			buf[i] = '\0';
524
			if (parse_netblock(buf + start,
525
			    bl + *blc, bl + *blc + 1, white))
526
				*blc += 2;
527
			start = i + 1;
528
		}
529
	}
530
	if (bu == 0)
531
		errno = EIO;
532
 bldone:
533
	free(buf);
534
	if (serrno)
535
		errno = serrno;
536
	return (bl);
537
}
538
539
int
540
cmpbl(const void *a, const void *b)
541
{
542
	if (((struct bl *)a)->addr > ((struct bl *) b)->addr)
543
		return (1);
544
	if (((struct bl *)a)->addr < ((struct bl *) b)->addr)
545
		return (-1);
546
	return (0);
547
}
548
549
/*
550
 * collapse_blacklist takes blacklist/whitelist entries sorts, removes
551
 * overlaps and whitelist portions, and returns netblocks to blacklist
552
 * as lists of nonoverlapping cidr blocks suitable for feeding in
553
 * printable form to pfctl or spamd.
554
 */
555
struct cidr *
556
collapse_blacklist(struct bl *bl, size_t blc, u_int *clc)
557
{
558
	int bs = 0, ws = 0, state=0;
559
	u_int cli, cls, i;
560
	u_int32_t bstart = 0;
561
	struct cidr *cl;
562
	int laststate;
563
	u_int32_t addr;
564
565
	if (blc == 0)
566
		return (NULL);
567
568
	/*
569
	 * Overallocate by 10% to avoid excessive realloc due to white
570
	 * entries splitting up CIDR blocks.
571
	 */
572
	cli = 0;
573
	cls = (blc / 2) + (blc / 20) + 1;
574
	cl = reallocarray(NULL, cls, sizeof(struct cidr));
575
	if (cl == NULL)
576
		return (NULL);
577
	qsort(bl, blc, sizeof(struct bl), cmpbl);
578
	for (i = 0; i < blc;) {
579
		laststate = state;
580
		addr = bl[i].addr;
581
582
		do {
583
			bs += bl[i].b;
584
			ws += bl[i].w;
585
			i++;
586
		} while (bl[i].addr == addr);
587
		if (state == 1 && bs == 0)
588
			state = 0;
589
		else if (state == 0 && bs > 0)
590
			state = 1;
591
		if (ws > 0)
592
			state = 0;
593
		if (laststate == 0 && state == 1) {
594
			/* start blacklist */
595
			bstart = addr;
596
		}
597
		if (laststate == 1 && state == 0) {
598
			/* end blacklist */
599
			cl = range2cidrlist(cl, &cli, &cls, bstart, addr - 1);
600
		}
601
		laststate = state;
602
	}
603
	cl[cli].addr = 0;
604
	*clc = cli;
605
	return (cl);
606
}
607
608
int
609
configure_spamd(u_short dport, char *name, char *message,
610
    struct cidr *blacklists, u_int count)
611
{
612
	int lport = IPPORT_RESERVED - 1, s;
613
	struct sockaddr_in sin;
614
	FILE* sdc;
615
616
	s = rresvport(&lport);
617
	if (s == -1)
618
		return (-1);
619
	memset(&sin, 0, sizeof sin);
620
	sin.sin_len = sizeof(sin);
621
	sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
622
	sin.sin_family = AF_INET;
623
	sin.sin_port = htons(dport);
624
	if (connect(s, (struct sockaddr *)&sin, sizeof sin) == -1)
625
		return (-1);
626
	sdc = fdopen(s, "w");
627
	if (sdc == NULL) {
628
		close(s);
629
		return (-1);
630
	}
631
	fputs(name, sdc);
632
	do_message(sdc, message);
633
	fprintf(sdc, ";inet;%u", count);
634
	while (blacklists->addr != 0) {
635
		fprintf(sdc, ";%s/%u", atop(blacklists->addr),
636
		    blacklists->bits);
637
		blacklists++;
638
	}
639
	fputc('\n', sdc);
640
	fclose(sdc);
641
	close(s);
642
	return (0);
643
}
644
645
646
int
647
configure_pf(struct cidr *blacklists)
648
{
649
	char *argv[9]= {"pfctl", "-q", "-t", "spamd", "-T", "replace",
650
	    "-f" "-", NULL};
651
	static FILE *pf = NULL;
652
	int pdes[2];
653
654
	if (pf == NULL) {
655
		if (pipe(pdes) != 0)
656
			return (-1);
657
		switch (fork()) {
658
		case -1:
659
			close(pdes[0]);
660
			close(pdes[1]);
661
			return (-1);
662
		case 0:
663
			/* child */
664
			close(pdes[1]);
665
			if (pdes[0] != STDIN_FILENO) {
666
				dup2(pdes[0], STDIN_FILENO);
667
				close(pdes[0]);
668
			}
669
			closefrom(STDERR_FILENO + 1);
670
			execvp(PATH_PFCTL, argv);
671
			_exit(1);
672
		}
673
674
		/* parent */
675
		close(pdes[0]);
676
		pf = fdopen(pdes[1], "w");
677
		if (pf == NULL) {
678
			close(pdes[1]);
679
			return (-1);
680
		}
681
	}
682
	while (blacklists->addr != 0) {
683
		fprintf(pf, "%s/%u\n", atop(blacklists->addr),
684
		    blacklists->bits);
685
		blacklists++;
686
	}
687
	return (0);
688
}
689
690
int
691
getlist(char ** db_array, char *name, struct blacklist *blist,
692
    struct blacklist *blistnew)
693
{
694
	char *buf, *method, *file, *message;
695
	int fd, black = 0, serror;
696
	size_t blc, bls;
697
	struct bl *bl = NULL;
698
	gzFile gzf;
699
700
	if (cgetent(&buf, db_array, name) != 0)
701
		err(1, "Can't find \"%s\" in spamd config", name);
702
	buf = fix_quoted_colons(buf);
703
	if (cgetcap(buf, "black", ':') != NULL) {
704
		/* use new list */
705
		black = 1;
706
		blc = blistnew->blc;
707
		bls = blistnew->bls;
708
		bl = blistnew->bl;
709
	} else if (cgetcap(buf, "white", ':') != NULL) {
710
		/* apply to most recent blacklist */
711
		black = 0;
712
		blc = blist->blc;
713
		bls = blist->bls;
714
		bl = blist->bl;
715
	} else
716
		errx(1, "Must have \"black\" or \"white\" in %s", name);
717
718
	switch (cgetstr(buf, "msg", &message)) {
719
	case -1:
720
		if (black)
721
			errx(1, "No msg for blacklist \"%s\"", name);
722
		break;
723
	case -2:
724
		err(1, NULL);
725
	}
726
727
	switch (cgetstr(buf, "method", &method)) {
728
	case -1:
729
		method = NULL;
730
		break;
731
	case -2:
732
		err(1, NULL);
733
	}
734
735
	switch (cgetstr(buf, "file", &file)) {
736
	case -1:
737
		errx(1, "No file given for %slist %s",
738
		    black ? "black" : "white", name);
739
	case -2:
740
		err(1, NULL);
741
	default:
742
		fd = open_file(method, file);
743
		if (fd == -1)
744
			err(1, "Can't open %s by %s method",
745
			    file, method ? method : "file");
746
		free(method);
747
		free(file);
748
		gzf = gzdopen(fd, "r");
749
		if (gzf == NULL)
750
			errx(1, "gzdopen");
751
	}
752
	free(buf);
753
	bl = add_blacklist(bl, &blc, &bls, gzf, !black);
754
	serror = errno;
755
	gzclose(gzf);
756
	if (bl == NULL) {
757
		errno = serror;
758
		warn("Could not add %slist %s", black ? "black" : "white",
759
		    name);
760
		return (0);
761
	}
762
	if (black) {
763
		if (debug)
764
			fprintf(stderr, "blacklist %s %zu entries\n",
765
			    name, blc / 2);
766
		blistnew->message = message;
767
		blistnew->name = name;
768
		blistnew->black = black;
769
		blistnew->bl = bl;
770
		blistnew->blc = blc;
771
		blistnew->bls = bls;
772
	} else {
773
		/* whitelist applied to last active blacklist */
774
		if (debug)
775
			fprintf(stderr, "whitelist %s %zu entries\n",
776
			    name, (blc - blist->blc) / 2);
777
		blist->bl = bl;
778
		blist->blc = blc;
779
		blist->bls = bls;
780
	}
781
	return (black);
782
}
783
784
void
785
send_blacklist(struct blacklist *blist, in_port_t port)
786
{
787
	struct cidr *cidrs;
788
	u_int clc;
789
790
	if (blist->blc > 0) {
791
		cidrs = collapse_blacklist(blist->bl, blist->blc, &clc);
792
		if (cidrs == NULL)
793
			err(1, NULL);
794
		if (!dryrun) {
795
			if (configure_spamd(port, blist->name,
796
			    blist->message, cidrs, clc) == -1)
797
				err(1, "Can't connect to spamd on port %d",
798
				    port);
799
			if (!greyonly && configure_pf(cidrs) == -1)
800
				err(1, "pfctl failed");
801
		}
802
		free(cidrs);
803
		free(blist->bl);
804
	}
805
}
806
807
__dead void
808
usage(void)
809
{
810
811
	fprintf(stderr, "usage: %s [-bDdn]\n", __progname);
812
	exit(1);
813
}
814
815
int
816
main(int argc, char *argv[])
817
{
818
	size_t blc, bls, black, white;
819
	char *db_array[2], *buf, *name;
820
	struct blacklist *blists;
821
	struct servent *ent;
822
	int daemonize = 0, ch;
823
	struct passwd *pw;
824
825
	while ((ch = getopt(argc, argv, "bdDn")) != -1) {
826
		switch (ch) {
827
		case 'n':
828
			dryrun = 1;
829
			break;
830
		case 'd':
831
			debug = 1;
832
			break;
833
		case 'b':
834
			greyonly = 0;
835
			break;
836
		case 'D':
837
			daemonize = 1;
838
			break;
839
		default:
840
			usage();
841
			break;
842
		}
843
	}
844
	argc -= optind;
845
	argv += optind;
846
	if (argc != 0)
847
		usage();
848
849
	if ((pw = getpwnam(SPAMD_USER)) == NULL)
850
		errx(1, "cannot find user %s", SPAMD_USER);
851
	spamd_uid = pw->pw_uid;
852
	spamd_gid = pw->pw_gid;
853
854
	if (pledge("stdio rpath inet proc exec id", NULL) == -1)
855
		err(1, "pledge");
856
857
	if (daemonize)
858
		daemon(0, 0);
859
	else if (chdir("/") != 0)
860
		err(1, "chdir(\"/\")");
861
862
	if ((ent = getservbyname("spamd-cfg", "tcp")) == NULL)
863
		errx(1, "cannot find service \"spamd-cfg\" in /etc/services");
864
	ent->s_port = ntohs(ent->s_port);
865
866
	db_array[0] = PATH_SPAMD_CONF;
867
	db_array[1] = NULL;
868
869
	if (cgetent(&buf, db_array, "all") != 0)
870
		err(1, "Can't find \"all\" in spamd config");
871
	name = strsep(&buf, ": \t"); /* skip "all" at start */
872
	blists = NULL;
873
	blc = bls = 0;
874
	while ((name = strsep(&buf, ": \t")) != NULL) {
875
		if (*name) {
876
			/* extract config in order specified in "all" tag */
877
			if (blc == bls) {
878
				struct blacklist *tmp;
879
880
				bls += 32;
881
				tmp = reallocarray(blists, bls,
882
				    sizeof(struct blacklist));
883
				if (tmp == NULL)
884
					err(1, NULL);
885
				blists = tmp;
886
			}
887
			if (blc == 0)
888
				black = white = 0;
889
			else {
890
				white = blc - 1;
891
				black = blc;
892
			}
893
			memset(&blists[black], 0, sizeof(struct blacklist));
894
			black = getlist(db_array, name, &blists[white],
895
			    &blists[black]);
896
			if (black && blc > 0) {
897
				/* collapse and free previous blacklist */
898
				send_blacklist(&blists[blc - 1], ent->s_port);
899
			}
900
			blc += black;
901
		}
902
	}
903
	/* collapse and free last blacklist */
904
	if (blc > 0)
905
		send_blacklist(&blists[blc - 1], ent->s_port);
906
	return (0);
907
}