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