1 /* $OpenBSD: spamd-setup.c,v 1.32 2007/02/27 02:10:58 beck 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/mail/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 __dead void usage(void);
86
87 int debug;
88 int dryrun;
89 int greyonly = 1;
90
91 extern char *__progname;
92
93 u_int32_t
imask(u_int8_t b)94 imask(u_int8_t b)
95 {
96 u_int32_t j = 0;
97 int i;
98
99 for (i = 31; i > 31 - b; --i)
100 j |= (1 << i);
101 return (j);
102 }
103
104 u_int8_t
maxblock(u_int32_t addr,u_int8_t bits)105 maxblock(u_int32_t addr, u_int8_t bits)
106 {
107 u_int32_t m;
108
109 while (bits > 0) {
110 m = imask(bits - 1);
111
112 if ((addr & m) != addr)
113 return (bits);
114 bits--;
115 }
116 return (bits);
117 }
118
119 u_int8_t
maxdiff(u_int32_t a,u_int32_t b)120 maxdiff(u_int32_t a, u_int32_t b)
121 {
122 u_int8_t bits = 0;
123 u_int32_t m;
124
125 b++;
126 while (bits < 32) {
127 m = imask(bits);
128
129 if ((a & m) != (b & m))
130 return (bits);
131 bits++;
132 }
133 return (bits);
134 }
135
136 struct cidr *
range2cidrlist(u_int32_t start,u_int32_t end)137 range2cidrlist(u_int32_t start, u_int32_t end)
138 {
139 struct cidr *list = NULL;
140 size_t cs = 0, cu = 0;
141 u_int8_t maxsize, diff;
142 struct cidr *tmp;
143
144 while (end >= start) {
145 maxsize = maxblock(start, 32);
146 diff = maxdiff(start, end);
147
148 maxsize = MAX(maxsize, diff);
149 if (cs <= cu + 1) { /* one extra for terminator */
150 tmp = realloc(list, (cs + 32) * sizeof(struct cidr));
151 if (tmp == NULL)
152 errx(1, "malloc failed");
153 list = tmp;
154 cs += 32;
155 }
156 list[cu].addr = start;
157 list[cu].bits = maxsize;
158 cu++;
159 list[cu].addr = 0;
160 list[cu].bits = 0;
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 int
open_child(char * file,char ** argv)250 open_child(char *file, char **argv)
251 {
252 int pdes[2];
253
254 if (pipe(pdes) != 0)
255 return (-1);
256 switch (fork()) {
257 case -1:
258 close(pdes[0]);
259 close(pdes[1]);
260 return (-1);
261 case 0:
262 /* child */
263 close(pdes[0]);
264 if (pdes[1] != STDOUT_FILENO) {
265 dup2(pdes[1], STDOUT_FILENO);
266 close(pdes[1]);
267 }
268 execvp(file, argv);
269 _exit(1);
270 }
271
272 /* parent */
273 close(pdes[1]);
274 return (pdes[0]);
275 }
276
277 int
fileget(char * url)278 fileget(char *url)
279 {
280 char *argv[6];
281
282 argv[0] = "ftp";
283 argv[1] = "-V";
284 argv[2] = "-o";
285 argv[3] = "-";
286 argv[4] = url;
287 argv[5] = NULL;
288
289 if (debug)
290 fprintf(stderr, "Getting %s\n", url);
291
292 return (open_child(PATH_FTP, argv));
293 }
294
295 int
open_file(char * method,char * file)296 open_file(char *method, char *file)
297 {
298 char *url;
299 char **ap, **argv;
300 int len, i, oerrno;
301
302 if ((method == NULL) || (strcmp(method, "file") == 0))
303 return (open(file, O_RDONLY));
304 if ((strcmp(method, "http") == 0) ||
305 strcmp(method, "ftp") == 0) {
306 asprintf(&url, "%s://%s", method, file);
307 if (url == NULL)
308 return (-1);
309 i = fileget(url);
310 free(url);
311 return (i);
312 } else if (strcmp(method, "exec") == 0) {
313 len = strlen(file);
314 argv = malloc(len * sizeof(char *));
315 if (argv == NULL)
316 errx(1, "malloc failed");
317 for (ap = argv; ap < &argv[len - 1] &&
318 (*ap = strsep(&file, " \t")) != NULL;) {
319 if (**ap != '\0')
320 ap++;
321 }
322 *ap = NULL;
323 i = open_child(argv[0], argv);
324 oerrno = errno;
325 free(argv);
326 errno = oerrno;
327 return (i);
328 }
329 errx(1, "Unknown method %s", method);
330 return (-1); /* NOTREACHED */
331 }
332
333 /*
334 * fix_quoted_colons walks through a buffer returned by cgetent. We
335 * look for quoted strings, to escape colons (:) in quoted strings for
336 * getcap by replacing them with \C so cgetstr() deals with it correctly
337 * without having to see the \C bletchery in a configuration file that
338 * needs to have urls in it. Frees the buffer passed to it, passes back
339 * another larger one, with can be used with cgetxxx(), like the original
340 * buffer, it must be freed by the caller.
341 * This should really be a temporary fix until there is a sanctioned
342 * way to make getcap(3) handle quoted strings like this in a nicer
343 * way.
344 */
345 char *
fix_quoted_colons(char * buf)346 fix_quoted_colons(char *buf)
347 {
348 int in = 0;
349 size_t i, j = 0;
350 char *newbuf, last;
351
352 /* Allocate enough space for a buf of all colons (impossible). */
353 newbuf = malloc(2 * strlen(buf) + 1);
354 if (newbuf == NULL)
355 return (NULL);
356 last = '\0';
357 for (i = 0; i < strlen(buf); i++) {
358 switch (buf[i]) {
359 case ':':
360 if (in) {
361 newbuf[j++] = '\\';
362 newbuf[j++] = 'C';
363 } else
364 newbuf[j++] = buf[i];
365 break;
366 case '"':
367 if (last != '\\')
368 in = !in;
369 newbuf[j++] = buf[i];
370 break;
371 default:
372 newbuf[j++] = buf[i];
373 }
374 last = buf[i];
375 }
376 free(buf);
377 newbuf[j] = '\0';
378 return (newbuf);
379 }
380
381 void
do_message(FILE * sdc,char * msg)382 do_message(FILE *sdc, char *msg)
383 {
384 size_t i, bs = 0, bu = 0, len;
385 ssize_t n;
386 char *buf = NULL, last, *tmp;
387 int fd;
388
389 len = strlen(msg);
390 if (msg[0] == '"' && msg[len - 1] == '"') {
391 /* quoted msg, escape newlines and send it out */
392 msg[len - 1] = '\0';
393 buf = msg + 1;
394 bu = len - 2;
395 goto sendit;
396 } else {
397 /*
398 * message isn't quoted - try to open a local
399 * file and read the message from it.
400 */
401 fd = open(msg, O_RDONLY);
402 if (fd == -1)
403 err(1, "Can't open message from %s", msg);
404 for (;;) {
405 if (bu == bs) {
406 tmp = realloc(buf, bs + 8192);
407 if (tmp == NULL)
408 errx(1, "malloc failed");
409 bs += 8192;
410 buf = tmp;
411 }
412
413 n = read(fd, buf + bu, bs - bu);
414 if (n == 0) {
415 goto sendit;
416 } else if (n == -1) {
417 err(1, "Can't read from %s", msg);
418 } else
419 bu += n;
420 }
421 buf[bu]='\0';
422 }
423 sendit:
424 fprintf(sdc, ";\"");
425 last = '\0';
426 for (i = 0; i < bu; i++) {
427 /* handle escaping the things spamd wants */
428 switch (buf[i]) {
429 case 'n':
430 if (last == '\\')
431 fprintf(sdc, "\\\\n");
432 else
433 fputc('n', sdc);
434 last = '\0';
435 break;
436 case '\n':
437 fprintf(sdc, "\\n");
438 last = '\0';
439 break;
440 case '"':
441 fputc('\\', sdc);
442 /* FALLTHROUGH */
443 default:
444 fputc(buf[i], sdc);
445 last = '\0';
446 }
447 }
448 fputc('"', sdc);
449 if (bs != 0)
450 free(buf);
451 }
452
453 /* retrieve a list from fd. add to blacklist bl */
454 struct bl *
add_blacklist(struct bl * bl,size_t * blc,size_t * bls,gzFile gzf,int white)455 add_blacklist(struct bl *bl, size_t *blc, size_t *bls, gzFile gzf, int white)
456 {
457 int i, n, start, bu = 0, bs = 0, serrno = 0;
458 char *buf = NULL, *tmp;
459 struct bl *blt;
460
461 for (;;) {
462 /* read in gzf, then parse */
463 if (bu == bs) {
464 tmp = realloc(buf, bs + (1024 * 1024) + 1);
465 if (tmp == NULL) {
466 free(buf);
467 buf = NULL;
468 bs = 0;
469 serrno = errno;
470 goto bldone;
471 }
472 bs += 1024 * 1024;
473 buf = tmp;
474 }
475
476 n = gzread(gzf, buf + bu, bs - bu);
477 if (n == 0)
478 goto parse;
479 else if (n == -1) {
480 serrno = errno;
481 goto bldone;
482 } else
483 bu += n;
484 }
485 parse:
486 start = 0;
487 for (i = 0; i <= bu; i++) {
488 if (*blc == *bls) {
489 *bls += 1024;
490 blt = realloc(bl, *bls * sizeof(struct bl));
491 if (blt == NULL) {
492 *bls -= 1024;
493 serrno = errno;
494 goto bldone;
495 }
496 bl = blt;
497 }
498 if (i == bu || buf[i] == '\n') {
499 buf[i] = '\0';
500 if (parse_netblock(buf + start,
501 bl + *blc, bl + *blc + 1, white))
502 *blc += 2;
503 start = i + 1;
504 }
505 }
506 if (bu == 0)
507 errno = EIO;
508 bldone:
509 if (buf)
510 free(buf);
511 if (serrno)
512 errno = serrno;
513 return (bl);
514 }
515
516 int
cmpbl(const void * a,const void * b)517 cmpbl(const void *a, const void *b)
518 {
519 if (((struct bl *)a)->addr > ((struct bl *) b)->addr)
520 return (1);
521 if (((struct bl *)a)->addr < ((struct bl *) b)->addr)
522 return (-1);
523 return (0);
524 }
525
526 /*
527 * collapse_blacklist takes blacklist/whitelist entries sorts, removes
528 * overlaps and whitelist portions, and returns netblocks to blacklist
529 * as lists of nonoverlapping cidr blocks suitable for feeding in
530 * printable form to pfctl or spamd.
531 */
532 struct cidr **
collapse_blacklist(struct bl * bl,size_t blc)533 collapse_blacklist(struct bl *bl, size_t blc)
534 {
535 int bs = 0, ws = 0, state=0, cli, i;
536 u_int32_t bstart = 0;
537 struct cidr **cl;
538 int laststate;
539 u_int32_t addr;
540
541 if (blc == 0)
542 return (NULL);
543 cl = malloc(((blc / 2) + 1) * sizeof(struct cidr));
544 if (cl == NULL) {
545 return (NULL);
546 }
547 qsort(bl, blc, sizeof(struct bl), cmpbl);
548 cli = 0;
549 cl[cli] = NULL;
550 for (i = 0; i < blc;) {
551 laststate = state;
552 addr = bl[i].addr;
553
554 do {
555 bs += bl[i].b;
556 ws += bl[i].w;
557 i++;
558 } while (bl[i].addr == addr);
559 if (state == 1 && bs == 0)
560 state = 0;
561 else if (state == 0 && bs > 0)
562 state = 1;
563 if (ws > 0)
564 state = 0;
565 if (laststate == 0 && state == 1) {
566 /* start blacklist */
567 bstart = addr;
568 }
569 if (laststate == 1 && state == 0) {
570 /* end blacklist */
571 cl[cli++] = range2cidrlist(bstart, addr - 1);
572 cl[cli] = NULL;
573 }
574 laststate = state;
575 }
576 return (cl);
577 }
578
579 int
configure_spamd(u_short dport,char * name,char * message,struct cidr ** blacklists)580 configure_spamd(u_short dport, char *name, char *message,
581 struct cidr **blacklists)
582 {
583 int lport = IPPORT_RESERVED - 1, s;
584 struct sockaddr_in sin;
585 FILE* sdc;
586
587 s = rresvport(&lport);
588 if (s == -1)
589 return (-1);
590 memset(&sin, 0, sizeof sin);
591 sin.sin_len = sizeof(sin);
592 sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
593 sin.sin_family = AF_INET;
594 sin.sin_port = htons(dport);
595 if (connect(s, (struct sockaddr *)&sin, sizeof sin) == -1)
596 return (-1);
597 sdc = fdopen(s, "w");
598 if (sdc == NULL) {
599 close(s);
600 return (-1);
601 }
602 fprintf(sdc, "%s", name);
603 do_message(sdc, message);
604 while (*blacklists != NULL) {
605 struct cidr *b = *blacklists;
606 while (b->addr != 0) {
607 fprintf(sdc, ";%s/%u", atop(b->addr), (b->bits));
608 b++;
609 }
610 blacklists++;
611 }
612 fputc('\n', sdc);
613 fclose(sdc);
614 close(s);
615 return (0);
616 }
617
618
619 int
configure_pf(struct cidr ** blacklists)620 configure_pf(struct cidr **blacklists)
621 {
622 char *argv[9]= {"pfctl", "-q", "-t", "spamd", "-T", "replace",
623 "-f" "-", NULL};
624 static FILE *pf = NULL;
625 int pdes[2];
626
627 if (pf == NULL) {
628 if (pipe(pdes) != 0)
629 return (-1);
630 switch (fork()) {
631 case -1:
632 close(pdes[0]);
633 close(pdes[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
646 /* parent */
647 close(pdes[0]);
648 pf = fdopen(pdes[1], "w");
649 if (pf == NULL) {
650 close(pdes[1]);
651 return (-1);
652 }
653 }
654 while (*blacklists != NULL) {
655 struct cidr *b = *blacklists;
656
657 while (b->addr != 0) {
658 fprintf(pf, "%s/%u\n", atop(b->addr), (b->bits));
659 b++;
660 }
661 blacklists++;
662 }
663 return (0);
664 }
665
666 int
getlist(char ** db_array,char * name,struct blacklist * blist,struct blacklist * blistnew)667 getlist(char ** db_array, char *name, struct blacklist *blist,
668 struct blacklist *blistnew)
669 {
670 char *buf, *method, *file, *message;
671 int fd, black = 0;
672 size_t blc, bls;
673 struct bl *bl = NULL;
674 gzFile gzf;
675
676 if (cgetent(&buf, db_array, name) != 0)
677 err(1, "Can't find \"%s\" in spamd config", name);
678 buf = fix_quoted_colons(buf);
679 if (cgetcap(buf, "black", ':') != NULL) {
680 /* use new list */
681 black = 1;
682 blc = blistnew->blc;
683 bls = blistnew->bls;
684 bl = blistnew->bl;
685 } else if (cgetcap(buf, "white", ':') != NULL) {
686 /* apply to most recent blacklist */
687 black = 0;
688 blc = blist->blc;
689 bls = blist->bls;
690 bl = blist->bl;
691 } else
692 errx(1, "Must have \"black\" or \"white\" in %s", name);
693
694 switch (cgetstr(buf, "msg", &message)) {
695 case -1:
696 if (black)
697 errx(1, "No msg for blacklist \"%s\"", name);
698 break;
699 case -2:
700 errx(1, "malloc failed");
701 }
702
703 switch (cgetstr(buf, "method", &method)) {
704 case -1:
705 method = NULL;
706 break;
707 case -2:
708 errx(1, "malloc failed");
709 }
710
711 switch (cgetstr(buf, "file", &file)) {
712 case -1:
713 errx(1, "No file given for %slist %s",
714 black ? "black" : "white", name);
715 case -2:
716 errx(1, "malloc failed");
717 default:
718 fd = open_file(method, file);
719 if (fd == -1)
720 err(1, "Can't open %s by %s method",
721 file, method ? method : "file");
722 free(method);
723 free(file);
724 gzf = gzdopen(fd, "r");
725 if (gzf == NULL)
726 errx(1, "gzdopen");
727 }
728 free(buf);
729 bl = add_blacklist(bl, &blc, &bls, gzf, !black);
730 gzclose(gzf);
731 if (bl == NULL) {
732 warn("Could not add %slist %s", black ? "black" : "white",
733 name);
734 return (0);
735 }
736 if (black) {
737 blistnew->message = message;
738 blistnew->name = name;
739 blistnew->black = black;
740 blistnew->bl = bl;
741 blistnew->blc = blc;
742 blistnew->bls = bls;
743 } else {
744 /* whitelist applied to last active blacklist */
745 blist->bl = bl;
746 blist->blc = blc;
747 blist->bls = bls;
748 }
749 if (debug)
750 fprintf(stderr, "%slist %s %zu entries\n",
751 black ? "black" : "white", name, blc / 2);
752 return (black);
753 }
754
755 void
send_blacklist(struct blacklist * blist,in_port_t port)756 send_blacklist(struct blacklist *blist, in_port_t port)
757 {
758 struct cidr **cidrs, **tmp;
759
760 if (blist->blc > 0) {
761 cidrs = collapse_blacklist(blist->bl, blist->blc);
762 if (cidrs == NULL)
763 errx(1, "malloc failed");
764 if (!dryrun) {
765 if (configure_spamd(port, blist->name,
766 blist->message, cidrs) == -1)
767 err(1, "Can't connect to spamd on port %d",
768 port);
769 if (!greyonly && configure_pf(cidrs) == -1)
770 err(1, "pfctl failed");
771 }
772 for (tmp = cidrs; *tmp != NULL; tmp++)
773 free(*tmp);
774 free(cidrs);
775 free(blist->bl);
776 }
777 }
778
779 __dead void
usage(void)780 usage(void)
781 {
782
783 fprintf(stderr, "usage: %s [-bdn]\n", __progname);
784 exit(1);
785 }
786
787 int
main(int argc,char * argv[])788 main(int argc, char *argv[])
789 {
790 size_t dbs, dbc, blc, bls, black, white;
791 char **db_array, *buf, *name;
792 struct blacklist *blists;
793 struct servent *ent;
794 int i, ch;
795
796 while ((ch = getopt(argc, argv, "bdn")) != -1) {
797 switch (ch) {
798 case 'n':
799 dryrun = 1;
800 break;
801 case 'd':
802 debug = 1;
803 break;
804 case 'b':
805 greyonly = 0;
806 break;
807 default:
808 usage();
809 break;
810 }
811 }
812 argc -= optind;
813 argv += optind;
814 if (argc != 0)
815 usage();
816
817 if ((ent = getservbyname("spamd-cfg", "tcp")) == NULL)
818 errx(1, "cannot find service \"spamd-cfg\" in /etc/services");
819 ent->s_port = ntohs(ent->s_port);
820
821 dbs = argc + 2;
822 dbc = 0;
823 db_array = calloc(dbs, sizeof(char *));
824 if (db_array == NULL)
825 errx(1, "malloc failed");
826
827 db_array[dbc]= PATH_SPAMD_CONF;
828 dbc++;
829 for (i = 1; i < argc; i++)
830 db_array[dbc++] = argv[i];
831
832 blists = NULL;
833 blc = bls = 0;
834 if (cgetent(&buf, db_array, "all") != 0)
835 err(1, "Can't find \"all\" in spamd config");
836 name = strsep(&buf, ": \t"); /* skip "all" at start */
837 blc = 0;
838 while ((name = strsep(&buf, ": \t")) != NULL) {
839 if (*name) {
840 /* extract config in order specified in "all" tag */
841 if (blc == bls) {
842 struct blacklist *tmp;
843
844 bls += 32;
845 tmp = realloc(blists,
846 bls * sizeof(struct blacklist));
847 if (tmp == NULL)
848 errx(1, "malloc failed");
849 blists = tmp;
850 }
851 if (blc == 0)
852 black = white = 0;
853 else {
854 white = blc - 1;
855 black = blc;
856 }
857 memset(&blists[black], 0, sizeof(struct blacklist));
858 black = getlist(db_array, name, &blists[white],
859 &blists[black]);
860 if (black && blc > 0) {
861 /* collapse and free previous blacklist */
862 send_blacklist(&blists[blc - 1], ent->s_port);
863 }
864 blc += black;
865 }
866 }
867 /* collapse and free last blacklist */
868 if (blc > 0)
869 send_blacklist(&blists[blc - 1], ent->s_port);
870 return (0);
871 }
872