xref: /openbsd-src/libexec/spamd-setup/spamd-setup.c (revision 8500990981f885cbe5e6a4958549cacc238b5ae6)
1 /*	$OpenBSD: spamd-setup.c,v 1.14 2003/08/22 21:50:34 david Exp $ */
2 /*
3  * Copyright (c) 2003 Bob Beck.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 #include <errno.h>
31 #include <fcntl.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <err.h>
37 #include <netinet/ip_ipsp.h>
38 #include <netdb.h>
39 #include <machine/endian.h>
40 
41 #define PATH_FTP		"/usr/bin/ftp"
42 #define PATH_PFCTL		"/sbin/pfctl"
43 #define PATH_SPAMD_CONF		"/etc/spamd.conf"
44 #define SPAMD_ARG_MAX		256 /* max # of args to an exec */
45 
46 struct cidr {
47 	u_int32_t addr;
48 	u_int8_t bits;
49 };
50 
51 struct bl {
52 	u_int32_t addr;
53 	int8_t b;
54 	int8_t w;
55 };
56 
57 struct blacklist {
58 	char *name;
59 	char *message;
60 	struct bl *bl;
61 	size_t blc, bls;
62 	u_int8_t black;
63 };
64 
65 u_int32_t	imask(u_int8_t b);
66 u_int8_t	maxblock(u_int32_t addr, u_int8_t bits);
67 u_int8_t	maxdiff(u_int32_t a, u_int32_t b);
68 struct cidr	*range2cidrlist(u_int32_t start, u_int32_t end);
69 void		cidr2range(struct cidr cidr, u_int32_t *start, u_int32_t *end);
70 char		*atop(u_int32_t addr);
71 u_int32_t	ptoa(char *cp);
72 int		parse_netblock(char *buf, struct bl *start, struct bl *end,
73 		    int white);
74 int		open_child(char *file, char **argv);
75 int		fetch(char *url);
76 int		open_file(char *method, char *file);
77 char		*fix_quoted_colons(char *buf);
78 void		do_message(FILE *sdc, char *msg);
79 struct bl	*add_blacklist(struct bl *bl, int *blc, int *bls, int fd,
80 		    int white);
81 int		cmpbl(const void *a, const void *b);
82 struct cidr	**collapse_blacklist(struct bl *bl, int blc);
83 int		configure_spamd(u_short dport, char *name, char *message,
84 		    struct cidr **blacklists);
85 int		configure_pf(struct cidr **blacklists);
86 int		getlist(char ** db_array, char *name, struct blacklist *blist,
87 		    struct blacklist *blistnew);
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 	while (bits > 0) {
104 		u_int32_t m = imask(bits - 1);
105 
106 		if ((addr & m) != addr)
107 			return (bits);
108 		bits--;
109 	}
110 	return(bits);
111 }
112 
113 u_int8_t
114 maxdiff(u_int32_t a, u_int32_t b)
115 {
116 	u_int8_t bits = 0;
117 
118 	b++;
119 	while (bits < 32) {
120 		u_int32_t m = imask(bits);
121 		if ((a & m) != (b & m))
122 			return (bits);
123 		bits++;
124 	}
125 	return(bits);
126 }
127 
128 struct cidr *
129 range2cidrlist(u_int32_t start, u_int32_t end)
130 {
131 	struct cidr *list = NULL;
132 	size_t cs = 0, cu = 0;
133 
134 	while (end >= start) {
135 		u_int8_t maxsize = maxblock(start, 32);
136 		u_int8_t diff = maxdiff(start, end);
137 
138 		maxsize = MAX(maxsize, diff);
139 		if (cs == cu) {
140 			struct cidr *tmp;
141 
142 			tmp = realloc(list, (cs + 32) * sizeof(struct cidr));
143 			if (tmp == NULL)
144 				errx(1, "malloc failed");
145 			list = tmp;
146 			cs += 32;
147 		}
148 		list[cu].addr = start;
149 		list[cu].bits = maxsize;
150 		cu++;
151 		list[cu].addr = 0;
152 		list[cu].bits = 0;
153 		start = start + (1 << (32 - maxsize));
154 	}
155 	return(list);
156 }
157 
158 void
159 cidr2range(struct cidr cidr, u_int32_t *start, u_int32_t *end)
160 {
161 	*start = cidr.addr;
162 	*end = cidr.addr + (1 << (32 - cidr.bits)) - 1;
163 }
164 
165 char *
166 atop(u_int32_t addr)
167 {
168 	struct in_addr in;
169 
170 	memset(&in, 0, sizeof(in));
171 	in.s_addr = htonl(addr);
172 	return(inet_ntoa(in));
173 }
174 
175 u_int32_t
176 ptoa(char *cp)
177 {
178 	struct in_addr in;
179 
180 	memset(&in, 0, sizeof(in));
181 	(void) inet_aton(cp, &in);
182 	return ntohl(in.s_addr);
183 }
184 
185 int
186 parse_netblock(char *buf, struct bl *start, struct bl *end, int white)
187 {
188 	char astring[16], astring2[16];
189 	unsigned maskbits;
190 
191 	/* skip leading spaces */
192 	while (*buf == ' ')
193 		buf++;
194 	/* bail if it's a comment */
195 	if (*buf == '#')
196 		return(0);
197 	/* otherwise, look for a netblock of some sort */
198 	if (sscanf(buf, "%15[^/]/%u", astring, &maskbits) == 2) {
199 		/* looks like a cidr */
200 		struct cidr c;
201 		memset(&c.addr, 0, sizeof(c.addr));
202 		if (inet_net_pton(AF_INET, astring, &c.addr, sizeof(c.addr))
203 		    == -1)
204 			return(0);
205 		c.addr = ntohl(c.addr);
206 		if (maskbits > 32)
207 			return(0);
208 		c.bits = maskbits;
209 		cidr2range(c, &start->addr, &end->addr);
210 		end->addr += 1;
211 	} else if (sscanf(buf, "%15[0123456789.]%*[ -]%15[0123456789.]",
212 	    astring, astring2) == 2) {
213 		/* looks like start - end */
214 		memset(&start->addr, 0, sizeof(start->addr));
215 		memset(&end->addr, 0, sizeof(end->addr));
216 		if (inet_net_pton(AF_INET, astring, &start->addr,
217 		    sizeof(start->addr)) == -1)
218 			return(0);
219 		start->addr = ntohl(start->addr);
220 		if (inet_net_pton(AF_INET, astring2, &end->addr,
221 		    sizeof(end->addr)) == -1)
222 			return(0);
223 		end->addr = ntohl(end->addr) + 1;
224 		if (start > end)
225 			return(0);
226 	} else if (sscanf(buf, "%15[0123456789.]", astring) == 1) {
227 		/* just a single address */
228 		memset(&start->addr, 0, sizeof(start->addr));
229 		if (inet_net_pton(AF_INET, astring, &start->addr,
230 		    sizeof(start->addr)) == -1)
231 			return(0);
232 		start->addr = ntohl(start->addr);
233 		end->addr = start->addr + 1;
234 	} else
235 		return(0);
236 
237 	if (white) {
238 		start->b = 0;
239 		start->w = 1;
240 		end->b = 0;
241 		end->w = -1;
242 	} else {
243 		start->b = 1;
244 		start->w = 0;
245 		end->b = -1;
246 		end->w = 0;
247 	}
248 	return(1);
249 }
250 
251 int
252 open_child(char *file, char **argv)
253 {
254 	pid_t pid;
255 	int pdes[2];
256 
257 	if (pipe(pdes) != 0)
258 		return(-1);
259 	switch(pid = fork()) {
260 	case -1:
261 		return(-1);
262 	case 0:
263 		/* child */
264 		close(pdes[0]);
265 		if (pdes[1] != STDOUT_FILENO) {
266 			dup2(pdes[1], STDOUT_FILENO);
267 			close(pdes[1]);
268 		}
269 		execvp(file, argv);
270 		_exit(1);
271 	}
272 	/* parent */
273 	close(pdes[1]);
274 	return(pdes[0]);
275 }
276 
277 int
278 fetch(char *url)
279 {
280 	char *argv[6]= {"ftp", "-V", "-o", "-", url, NULL};
281 
282 	return open_child(PATH_FTP, argv);
283 }
284 
285 int
286 open_file(char *method, char *file)
287 {
288 	char *url;
289 
290 	if ((method == NULL) || (strcmp(method, "file") == 0))
291 		return(open(file, O_RDONLY));
292 	if ((strcmp(method, "http") == 0) ||
293 	    strcmp(method, "ftp") == 0) {
294 		int i;
295 
296 		asprintf(&url, "%s://%s", method, file);
297 		if (url == NULL)
298 			return(-1);
299 		i = fetch(url);
300 		free(url);
301 		return(i);
302 	} else if (strcmp(method, "exec") == 0) {
303 		char **ap, **argv;
304 		int len, i, oerrno;
305 
306 		len = strlen(file);
307 		argv = malloc(len * sizeof(char *));
308 		if (argv == NULL)
309 			errx(1, "malloc failed");
310 		for (ap = argv; ap < &argv[len - 1] &&
311 		    (*ap = strsep(&file, " \t")) != NULL;) {
312 			if (**ap != '\0')
313 				ap++;
314 		}
315 		*ap = NULL;
316 		i = open_child(argv[0], argv);
317 		oerrno = errno;
318 		free(argv);
319 		errno = oerrno;
320 		return(i);
321 	}
322 	errx(1, "Unknown method %s", method);
323 	return(-1); /* NOTREACHED */
324 }
325 
326 /*
327  * fix_quoted_colons walks through a buffer returned by cgetent.  We
328  * look for quoted strings, to escape colons (:) in quoted strings for
329  * getcap by replacing them with \C so cgetstr() deals with it correctly
330  * without having to see the \C bletchery in a configuration file that
331  * needs to have urls in it. Frees the buffer passed to it, passes back
332  * another larger one, with can be used with cgetxxx(), like the original
333  * buffer, it must be freed by the caller.
334  * This should really be a temporary fix until there is a sanctioned
335  * way to make getcap(3) handle quoted strings like this in a nicer
336  * way.
337  */
338 char *
339 fix_quoted_colons(char *buf)
340 {
341 	int nbs = 0, i = 0, j = 0, in = 0;
342 	char *newbuf, last;
343 
344 	nbs = strlen(buf) + 128;
345 	newbuf = malloc(nbs);
346 	if (newbuf == NULL)
347 		return NULL;
348 	last = '\0';
349 	for (i = 0; i < strlen(buf); i++) {
350 		switch (buf[i]) {
351 		case ':':
352 			if (in) {
353 				newbuf[j++] = '\\';
354 				newbuf[j++] = 'C';
355 			} else
356 				newbuf[j++] = buf[i];
357 			break;
358 		case '"':
359 			if (last != '\\')
360 				in = !in;
361 			newbuf[j++] = buf[i];
362 			break;
363 		default:
364 			newbuf[j++] = buf[i];
365 		}
366 		if (j == nbs) {
367 			char *tmp;
368 
369 			nbs += 128;
370 			tmp = realloc(newbuf, nbs);
371 			if (tmp == NULL)
372 				errx(1, "malloc failed");
373 			newbuf = tmp;
374 		}
375 	}
376 	free(buf);
377 	newbuf[j] = '\0';
378 	return(newbuf);
379 }
380 
381 void
382 do_message(FILE *sdc, char *msg)
383 {
384 	int i, n, bu = 0, bs = 0, len;
385 	char *buf = NULL, last;
386 
387 	len = strlen(msg);
388 	if (msg[0] == '"' && msg[len - 1] == '"') {
389 		/* quoted msg, escape newlines and send it out */
390 		msg[len - 1] = '\0';
391 		buf = msg+1;
392 		bu = len - 2;
393 		goto sendit;
394 	} else {
395 		/* message isn't quoted - try to open a local
396 		 * file and read the message from it.
397 		 */
398 		int fd;
399 
400 		fd = open(msg, O_RDONLY);
401 		if (fd == -1)
402 			err(1, "Can't open message from %s", msg);
403 		for (;;) {
404 			if (bu == bs) {
405 				char *tmp;
406 
407 				tmp = realloc(buf, bs + 8192);
408 				if (tmp == NULL)
409 					errx(1, "malloc failed");
410 				bs += 8192;
411 				buf = tmp;
412 			}
413 
414 			n = read(fd, buf + bu, bs - bu);
415 			if (n == 0) {
416 				goto sendit;
417 			} else if (n == -1) {
418 				err(1, "Can't read from %s", msg);
419 			} else
420 				bu += n;
421 		}
422 		buf[bu]='\0';
423 	}
424  sendit:
425 	fprintf(sdc, ";\"");
426 	last = '\0';
427 	for (i = 0; i < bu; i++) {
428 		/* handle escaping the things spamd wants */
429 		switch(buf[i]) {
430 		case 'n':
431 			if (last == '\\')
432 				fprintf(sdc, "\\\\n");
433 			else
434 				fputc('n', sdc);
435 			last = '\0';
436 			break;
437 		case '\n':
438 			fprintf(sdc, "\\n");
439 			last = '\0';
440 			break;
441 		case '"':
442 			fputc('\\', sdc);
443 			/* fall through */
444 		default:
445 			fputc(buf[i], sdc);
446 			last = '\0';
447 		}
448 	}
449 	fputc('"', sdc);
450 	if (bs != 0)
451 		free(buf);
452 }
453 
454 /* retrieve a list from fd. add to blacklist bl */
455 struct bl *
456 add_blacklist(struct bl *bl, int *blc, int *bls, int fd, int white)
457 {
458 	int i, n, start, bu = 0, bs = 0, serrno = 0;
459 	char *buf = NULL;
460 
461 	for (;;) {
462 		/* read in fd, then parse */
463 		if (bu == bs) {
464 			char *tmp;
465 
466 			tmp = realloc(buf, bs + 8192);
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 = read(fd, 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 			struct bl *tmp;
492 
493 			*bls += 1024;
494 			tmp = realloc(bl, *bls * sizeof(struct bl));
495 			if (tmp == NULL) {
496 				*bls -= 1024;
497 				serrno = errno;
498 				goto bldone;
499 			}
500 			bl = tmp;
501 		}
502 		if (buf[i] == '\n') {
503 			buf[i] = '\0';
504 			if (parse_netblock (buf + start,
505 			    bl + *blc, bl + *blc + 1, white))
506 				*blc+=2;
507 			start = i+1;
508 		}
509 	}
510 	if (bu == 0)
511 		errno = EIO;
512  bldone:
513 	if (buf)
514 		free(buf);
515 	if (serrno)
516 		errno = serrno;
517 	return (bl);
518 }
519 
520 int
521 cmpbl(const void *a, const void *b)
522 {
523 	if (((struct bl *)a)->addr > ((struct bl *) b)->addr)
524 		return(1);
525 	if (((struct bl *)a)->addr < ((struct bl *) b)->addr)
526 		return(-1);
527 	return(0);
528 }
529 
530 /*
531  * collapse_blacklist takes blacklist/whitelist entries sorts, removes
532  * overlaps and whitelist portions, and returns netblocks to blacklist
533  * as lists of nonoverlapping cidr blocks suitable for feeding in
534  * printable form to pfctl or spamd.
535  */
536 struct cidr **
537 collapse_blacklist(struct bl *bl, int blc)
538 {
539 	int bs = 0, ws = 0, state=0, cli, i;
540 	struct cidr ** cl;
541 	u_int32_t bstart = 0;
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 		int laststate = state;
554 		u_int32_t 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;
586 	struct sockaddr_in sin;
587 	FILE* sdc;
588 	int s;
589 
590 	s = rresvport(&lport);
591 	if (s == -1)
592 		return(-1);
593 	memset(&sin, 0, sizeof sin);
594 	sin.sin_len = sizeof(sin);
595 	sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
596 	sin.sin_family = AF_INET;
597 	sin.sin_port = htons(dport);
598 	if (connect(s, (struct sockaddr *)&sin, sizeof sin) == -1)
599 		return(-1);
600 	sdc = fdopen(s, "w");
601 	if (sdc == NULL)
602 		return(-1);
603 	fprintf(sdc, "%s", name);
604 	do_message(sdc, message);
605 	while (*blacklists != NULL) {
606 		struct cidr *b = *blacklists;
607 		while (b->addr != 0) {
608 			fprintf(sdc, ";%s/%u", atop(b->addr), (b->bits));
609 			b++;
610 		}
611 		blacklists++;
612 	}
613 	fputc('\n', sdc);
614 	fclose(sdc);
615 	close(s);
616 	return(0);
617 }
618 
619 
620 int
621 configure_pf(struct cidr **blacklists)
622 {
623 	char *argv[9]= {"pfctl", "-q", "-t", "spamd", "-T", "replace",
624 	    "-f" "-", NULL};
625 	static FILE *pf = NULL;
626 	int pdes[2];
627 	pid_t pid;
628 
629 	if (pf == NULL) {
630 		if (pipe(pdes) != 0)
631 			return(-1);
632 		switch(pid = fork()) {
633 		case -1:
634 			return(-1);
635 		case 0:
636 			/* child */
637 			close(pdes[1]);
638 			if (pdes[0] != STDIN_FILENO) {
639 				dup2(pdes[0], STDIN_FILENO);
640 				close(pdes[0]);
641 			}
642 			execvp(PATH_PFCTL, argv);
643 			_exit(1);
644 		}
645 		/* parent */
646 		close(pdes[0]);
647 		pf = fdopen(pdes[1], "w");
648 		if (pf == NULL)
649 			return(-1);
650 	}
651 	while (*blacklists != NULL) {
652 		struct cidr *b = *blacklists;
653 		while (b->addr != 0) {
654 			fprintf(pf, "%s/%u\n", atop(b->addr), (b->bits));
655 			b++;
656 		}
657 		blacklists++;
658 	}
659 	return(0);
660 }
661 
662 int
663 getlist(char ** db_array, char *name, struct blacklist *blist,
664     struct blacklist *blistnew)
665 {
666 	char *buf, *method, *file, *message;
667 	int blc, bls, fd, black = 0;
668 	struct bl *bl = NULL;
669 
670 	if (cgetent(&buf, db_array, name) != 0)
671 		err(1, "Can't find \"%s\" in spamd config", name);
672 	buf = fix_quoted_colons(buf);
673 	if (cgetcap(buf, "black", ':') != NULL) {
674 		/* use new list */
675 		black = 1;
676 		blc = blistnew->blc;
677 		bls = blistnew->bls;
678 		bl = blistnew->bl;
679 	} else if (cgetcap(buf, "white", ':') != NULL) {
680 		/* apply to most recent blacklist */
681 		black = 0;
682 		blc = blist->blc;
683 		bls = blist->bls;
684 		bl = blist->bl;
685 	} else
686 		errx(1, "Must have \"black\" or \"white\" in %s", name);
687 
688 	switch (cgetstr(buf, "msg", &message)) {
689 	case -1:
690 		if (black)
691 			errx(1, "No msg for blacklist \"%s\"", name);
692 		break;
693 	case -2:
694 		errx(1, "malloc failed");
695 	}
696 
697 	switch (cgetstr(buf, "method", &method)) {
698 	case -1:
699 		method = NULL;
700 		break;
701 	case -2:
702 		errx(1, "malloc failed");
703 	}
704 
705 	switch (cgetstr(buf, "file", &file)) {
706 	case -1:
707 		errx(1, "No file given for %slist %s", black?"black":"white",
708 		    name);
709 	case -2:
710 		errx(1, "malloc failed");
711 	default:
712 		fd = open_file(method, file);
713 		if (fd == -1)
714 			err(1, "Can't open %s by %s method",
715 			    file, method ? method:"file");
716 		free(method);
717 		free(file);
718 	}
719 	bl = add_blacklist(bl, &blc, &bls, fd, !black);
720 	if (bl == NULL) {
721 		warn("Could not add %slist %s", black ? "black" : "white",
722 		    name);
723 		return(0);
724 	}
725 	if (black) {
726 		blistnew->message = message;
727 		blistnew->name = name;
728 		blistnew->black = black;
729 		blistnew->bl = bl;
730 		blistnew->blc = blc;
731 		blistnew->bls = bls;
732 	} else {
733 		/* whitelist applied to last active blacklist */
734 		blist->bl = bl;
735 		blist->blc = blc;
736 		blist->bls = bls;
737 	}
738 	return(black);
739 }
740 
741 int
742 main(int argc, char *argv[])
743 {
744 	size_t dbs, dbc, blc, bls, black, white;
745 	struct blacklist *blists;
746 	char **db_array, *buf;
747 	char *name;
748 	int i;
749 	struct servent *ent;
750 
751 	if ((ent = getservbyname("spamd-cfg", "tcp")) == NULL)
752 		errx(1, "Can't find service \"spamd-cfg\" in /etc/services");
753 	ent->s_port = ntohs(ent->s_port);
754 
755 	dbs = argc + 2;
756 	dbc = 0;
757 	db_array = calloc(dbs, sizeof(char *));
758 	if (db_array == NULL)
759 		errx(1, "malloc failed");
760 
761 	db_array[dbc]= PATH_SPAMD_CONF;
762 	dbc++;
763 	for (i = 1; i < argc; i++)
764 		db_array[dbc++] = argv[i];
765 
766 	blists = NULL;
767 	blc = bls = 0;
768 	if (cgetent(&buf, db_array, "all") != 0)
769 		err(1, "Can't find \"all\" in spamd config");
770 	name = strsep(&buf, ": \t"); /* skip "all" at start */
771 	blc = 0;
772 	while ((name = strsep(&buf, ": \t")) != NULL) {
773 		if (*name) {
774 			/* extract config in order specified in "all" tag */
775 			if (blc == bls) {
776 				struct blacklist *tmp;
777 				bls += 1024;
778 				tmp = realloc(blists,
779 				    bls * sizeof(struct blacklist));
780 				if (tmp == NULL)
781 					errx(1, "malloc failed");
782 				blists = tmp;
783 			}
784 			if (blc == 0)
785 				black = white = 0;
786 			else {
787 				white = blc - 1;
788 				black = blc;
789 			}
790 			memset(&blists[black], 0, sizeof(struct blacklist));
791 			blc += getlist(db_array, name, &blists[white],
792 			    &blists[black]);
793 		}
794 	}
795 	for (i = 0; i < blc; i++) {
796 		struct cidr **cidrs, **tmp;
797 		if (blists[i].blc > 0) {
798 			cidrs = collapse_blacklist(blists[i].bl,
799 			   blists[i].blc);
800 			if (cidrs == NULL)
801 				errx(1, "malloc failed");
802 			if (configure_spamd(ent->s_port, blists[i].name,
803 					    blists[i].message, cidrs) == -1)
804 				err(1, "Can't connect to spamd on port %d",
805 				    ent->s_port);
806 			if (configure_pf(cidrs) == -1)
807 				err(1, "pfctl failed");
808 			tmp = cidrs;
809 			while (*tmp != NULL)
810 				free(*tmp++);
811 			free(cidrs);
812 			free(blists[i].bl);
813 		}
814 	}
815 	return (0);
816 }
817