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