xref: /openbsd/libexec/spamd-setup/spamd-setup.c (revision a73dc8fa)
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
imask(u_int8_t b)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
maxblock(u_int32_t addr,u_int8_t bits)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
maxdiff(u_int32_t a,u_int32_t b)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 *
range2cidrlist(struct cidr * list,u_int * cli,u_int * cls,u_int32_t start,u_int32_t end)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
cidr2range(struct cidr cidr,u_int32_t * start,u_int32_t * end)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 *
atop(u_int32_t addr)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
parse_netblock(char * buf,struct bl * start,struct bl * end,int white)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
drop_privileges(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
open_child(char * file,char ** argv,int drop_privs)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
fileget(char * url)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
open_file(char * method,char * file)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 *
fix_quoted_colons(char * buf)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
do_message(FILE * sdc,char * msg)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 *
add_blacklist(struct bl * bl,size_t * blc,size_t * bls,gzFile gzf,int white)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
cmpbl(const void * a,const void * b)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 *
collapse_blacklist(struct bl * bl,size_t blc,u_int * clc)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
configure_spamd(u_short dport,char * name,char * message,struct cidr * blacklists,u_int count)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
configure_pf(struct cidr * blacklists)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
getlist(char ** db_array,char * name,struct blacklist * blist,struct blacklist * blistnew)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
send_blacklist(struct blacklist * blist,in_port_t port)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
usage(void)808 usage(void)
809 {
810 
811 	fprintf(stderr, "usage: %s [-bDdn]\n", __progname);
812 	exit(1);
813 }
814 
815 int
main(int argc,char * argv[])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 }
908