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