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