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