1 /* $OpenBSD: spamd-setup.c,v 1.35 2008/10/03 18:58:52 jmc 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 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 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 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 * 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 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 * 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 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 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 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 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 = calloc(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 * 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 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 * 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 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 ** 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 = calloc(((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 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 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 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 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 780 usage(void) 781 { 782 783 fprintf(stderr, "usage: %s [-bDdn]\n", __progname); 784 exit(1); 785 } 786 787 int 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 daemonize = 0, i, ch; 795 796 while ((ch = getopt(argc, argv, "bdDn")) != -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 case 'D': 808 daemonize = 1; 809 break; 810 default: 811 usage(); 812 break; 813 } 814 } 815 argc -= optind; 816 argv += optind; 817 if (argc != 0) 818 usage(); 819 820 if (daemonize) 821 daemon(0, 0); 822 823 if ((ent = getservbyname("spamd-cfg", "tcp")) == NULL) 824 errx(1, "cannot find service \"spamd-cfg\" in /etc/services"); 825 ent->s_port = ntohs(ent->s_port); 826 827 dbs = argc + 2; 828 dbc = 0; 829 db_array = calloc(dbs, sizeof(char *)); 830 if (db_array == NULL) 831 errx(1, "malloc failed"); 832 833 db_array[dbc]= PATH_SPAMD_CONF; 834 dbc++; 835 for (i = 1; i < argc; i++) 836 db_array[dbc++] = argv[i]; 837 838 blists = NULL; 839 blc = bls = 0; 840 if (cgetent(&buf, db_array, "all") != 0) 841 err(1, "Can't find \"all\" in spamd config"); 842 name = strsep(&buf, ": \t"); /* skip "all" at start */ 843 blc = 0; 844 while ((name = strsep(&buf, ": \t")) != NULL) { 845 if (*name) { 846 /* extract config in order specified in "all" tag */ 847 if (blc == bls) { 848 struct blacklist *tmp; 849 850 bls += 32; 851 tmp = realloc(blists, 852 bls * sizeof(struct blacklist)); 853 if (tmp == NULL) 854 errx(1, "malloc failed"); 855 blists = tmp; 856 } 857 if (blc == 0) 858 black = white = 0; 859 else { 860 white = blc - 1; 861 black = blc; 862 } 863 memset(&blists[black], 0, sizeof(struct blacklist)); 864 black = getlist(db_array, name, &blists[white], 865 &blists[black]); 866 if (black && blc > 0) { 867 /* collapse and free previous blacklist */ 868 send_blacklist(&blists[blc - 1], ent->s_port); 869 } 870 blc += black; 871 } 872 } 873 /* collapse and free last blacklist */ 874 if (blc > 0) 875 send_blacklist(&blists[blc - 1], ent->s_port); 876 return (0); 877 } 878