1 /* $OpenBSD: spamd-setup.c,v 1.14 2003/08/22 21:50:34 david Exp $ */ 2 /* 3 * Copyright (c) 2003 Bob Beck. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include <sys/types.h> 27 #include <sys/socket.h> 28 #include <netinet/in.h> 29 #include <arpa/inet.h> 30 #include <errno.h> 31 #include <fcntl.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <unistd.h> 36 #include <err.h> 37 #include <netinet/ip_ipsp.h> 38 #include <netdb.h> 39 #include <machine/endian.h> 40 41 #define PATH_FTP "/usr/bin/ftp" 42 #define PATH_PFCTL "/sbin/pfctl" 43 #define PATH_SPAMD_CONF "/etc/spamd.conf" 44 #define SPAMD_ARG_MAX 256 /* max # of args to an exec */ 45 46 struct cidr { 47 u_int32_t addr; 48 u_int8_t bits; 49 }; 50 51 struct bl { 52 u_int32_t addr; 53 int8_t b; 54 int8_t w; 55 }; 56 57 struct blacklist { 58 char *name; 59 char *message; 60 struct bl *bl; 61 size_t blc, bls; 62 u_int8_t black; 63 }; 64 65 u_int32_t imask(u_int8_t b); 66 u_int8_t maxblock(u_int32_t addr, u_int8_t bits); 67 u_int8_t maxdiff(u_int32_t a, u_int32_t b); 68 struct cidr *range2cidrlist(u_int32_t start, u_int32_t end); 69 void cidr2range(struct cidr cidr, u_int32_t *start, u_int32_t *end); 70 char *atop(u_int32_t addr); 71 u_int32_t ptoa(char *cp); 72 int parse_netblock(char *buf, struct bl *start, struct bl *end, 73 int white); 74 int open_child(char *file, char **argv); 75 int fetch(char *url); 76 int open_file(char *method, char *file); 77 char *fix_quoted_colons(char *buf); 78 void do_message(FILE *sdc, char *msg); 79 struct bl *add_blacklist(struct bl *bl, int *blc, int *bls, int fd, 80 int white); 81 int cmpbl(const void *a, const void *b); 82 struct cidr **collapse_blacklist(struct bl *bl, int blc); 83 int configure_spamd(u_short dport, char *name, char *message, 84 struct cidr **blacklists); 85 int configure_pf(struct cidr **blacklists); 86 int getlist(char ** db_array, char *name, struct blacklist *blist, 87 struct blacklist *blistnew); 88 89 u_int32_t 90 imask(u_int8_t b) 91 { 92 u_int32_t j = 0; 93 int i; 94 95 for (i = 31; i > 31 - b; --i) 96 j |= (1 << i); 97 return(j); 98 } 99 100 u_int8_t 101 maxblock(u_int32_t addr, u_int8_t bits) 102 { 103 while (bits > 0) { 104 u_int32_t m = imask(bits - 1); 105 106 if ((addr & m) != addr) 107 return (bits); 108 bits--; 109 } 110 return(bits); 111 } 112 113 u_int8_t 114 maxdiff(u_int32_t a, u_int32_t b) 115 { 116 u_int8_t bits = 0; 117 118 b++; 119 while (bits < 32) { 120 u_int32_t m = imask(bits); 121 if ((a & m) != (b & m)) 122 return (bits); 123 bits++; 124 } 125 return(bits); 126 } 127 128 struct cidr * 129 range2cidrlist(u_int32_t start, u_int32_t end) 130 { 131 struct cidr *list = NULL; 132 size_t cs = 0, cu = 0; 133 134 while (end >= start) { 135 u_int8_t maxsize = maxblock(start, 32); 136 u_int8_t diff = maxdiff(start, end); 137 138 maxsize = MAX(maxsize, diff); 139 if (cs == cu) { 140 struct cidr *tmp; 141 142 tmp = realloc(list, (cs + 32) * sizeof(struct cidr)); 143 if (tmp == NULL) 144 errx(1, "malloc failed"); 145 list = tmp; 146 cs += 32; 147 } 148 list[cu].addr = start; 149 list[cu].bits = maxsize; 150 cu++; 151 list[cu].addr = 0; 152 list[cu].bits = 0; 153 start = start + (1 << (32 - maxsize)); 154 } 155 return(list); 156 } 157 158 void 159 cidr2range(struct cidr cidr, u_int32_t *start, u_int32_t *end) 160 { 161 *start = cidr.addr; 162 *end = cidr.addr + (1 << (32 - cidr.bits)) - 1; 163 } 164 165 char * 166 atop(u_int32_t addr) 167 { 168 struct in_addr in; 169 170 memset(&in, 0, sizeof(in)); 171 in.s_addr = htonl(addr); 172 return(inet_ntoa(in)); 173 } 174 175 u_int32_t 176 ptoa(char *cp) 177 { 178 struct in_addr in; 179 180 memset(&in, 0, sizeof(in)); 181 (void) inet_aton(cp, &in); 182 return ntohl(in.s_addr); 183 } 184 185 int 186 parse_netblock(char *buf, struct bl *start, struct bl *end, int white) 187 { 188 char astring[16], astring2[16]; 189 unsigned maskbits; 190 191 /* skip leading spaces */ 192 while (*buf == ' ') 193 buf++; 194 /* bail if it's a comment */ 195 if (*buf == '#') 196 return(0); 197 /* otherwise, look for a netblock of some sort */ 198 if (sscanf(buf, "%15[^/]/%u", astring, &maskbits) == 2) { 199 /* looks like a cidr */ 200 struct cidr c; 201 memset(&c.addr, 0, sizeof(c.addr)); 202 if (inet_net_pton(AF_INET, astring, &c.addr, sizeof(c.addr)) 203 == -1) 204 return(0); 205 c.addr = ntohl(c.addr); 206 if (maskbits > 32) 207 return(0); 208 c.bits = maskbits; 209 cidr2range(c, &start->addr, &end->addr); 210 end->addr += 1; 211 } else if (sscanf(buf, "%15[0123456789.]%*[ -]%15[0123456789.]", 212 astring, astring2) == 2) { 213 /* looks like start - end */ 214 memset(&start->addr, 0, sizeof(start->addr)); 215 memset(&end->addr, 0, sizeof(end->addr)); 216 if (inet_net_pton(AF_INET, astring, &start->addr, 217 sizeof(start->addr)) == -1) 218 return(0); 219 start->addr = ntohl(start->addr); 220 if (inet_net_pton(AF_INET, astring2, &end->addr, 221 sizeof(end->addr)) == -1) 222 return(0); 223 end->addr = ntohl(end->addr) + 1; 224 if (start > end) 225 return(0); 226 } else if (sscanf(buf, "%15[0123456789.]", astring) == 1) { 227 /* just a single address */ 228 memset(&start->addr, 0, sizeof(start->addr)); 229 if (inet_net_pton(AF_INET, astring, &start->addr, 230 sizeof(start->addr)) == -1) 231 return(0); 232 start->addr = ntohl(start->addr); 233 end->addr = start->addr + 1; 234 } else 235 return(0); 236 237 if (white) { 238 start->b = 0; 239 start->w = 1; 240 end->b = 0; 241 end->w = -1; 242 } else { 243 start->b = 1; 244 start->w = 0; 245 end->b = -1; 246 end->w = 0; 247 } 248 return(1); 249 } 250 251 int 252 open_child(char *file, char **argv) 253 { 254 pid_t pid; 255 int pdes[2]; 256 257 if (pipe(pdes) != 0) 258 return(-1); 259 switch(pid = fork()) { 260 case -1: 261 return(-1); 262 case 0: 263 /* child */ 264 close(pdes[0]); 265 if (pdes[1] != STDOUT_FILENO) { 266 dup2(pdes[1], STDOUT_FILENO); 267 close(pdes[1]); 268 } 269 execvp(file, argv); 270 _exit(1); 271 } 272 /* parent */ 273 close(pdes[1]); 274 return(pdes[0]); 275 } 276 277 int 278 fetch(char *url) 279 { 280 char *argv[6]= {"ftp", "-V", "-o", "-", url, NULL}; 281 282 return open_child(PATH_FTP, argv); 283 } 284 285 int 286 open_file(char *method, char *file) 287 { 288 char *url; 289 290 if ((method == NULL) || (strcmp(method, "file") == 0)) 291 return(open(file, O_RDONLY)); 292 if ((strcmp(method, "http") == 0) || 293 strcmp(method, "ftp") == 0) { 294 int i; 295 296 asprintf(&url, "%s://%s", method, file); 297 if (url == NULL) 298 return(-1); 299 i = fetch(url); 300 free(url); 301 return(i); 302 } else if (strcmp(method, "exec") == 0) { 303 char **ap, **argv; 304 int len, i, oerrno; 305 306 len = strlen(file); 307 argv = malloc(len * sizeof(char *)); 308 if (argv == NULL) 309 errx(1, "malloc failed"); 310 for (ap = argv; ap < &argv[len - 1] && 311 (*ap = strsep(&file, " \t")) != NULL;) { 312 if (**ap != '\0') 313 ap++; 314 } 315 *ap = NULL; 316 i = open_child(argv[0], argv); 317 oerrno = errno; 318 free(argv); 319 errno = oerrno; 320 return(i); 321 } 322 errx(1, "Unknown method %s", method); 323 return(-1); /* NOTREACHED */ 324 } 325 326 /* 327 * fix_quoted_colons walks through a buffer returned by cgetent. We 328 * look for quoted strings, to escape colons (:) in quoted strings for 329 * getcap by replacing them with \C so cgetstr() deals with it correctly 330 * without having to see the \C bletchery in a configuration file that 331 * needs to have urls in it. Frees the buffer passed to it, passes back 332 * another larger one, with can be used with cgetxxx(), like the original 333 * buffer, it must be freed by the caller. 334 * This should really be a temporary fix until there is a sanctioned 335 * way to make getcap(3) handle quoted strings like this in a nicer 336 * way. 337 */ 338 char * 339 fix_quoted_colons(char *buf) 340 { 341 int nbs = 0, i = 0, j = 0, in = 0; 342 char *newbuf, last; 343 344 nbs = strlen(buf) + 128; 345 newbuf = malloc(nbs); 346 if (newbuf == NULL) 347 return NULL; 348 last = '\0'; 349 for (i = 0; i < strlen(buf); i++) { 350 switch (buf[i]) { 351 case ':': 352 if (in) { 353 newbuf[j++] = '\\'; 354 newbuf[j++] = 'C'; 355 } else 356 newbuf[j++] = buf[i]; 357 break; 358 case '"': 359 if (last != '\\') 360 in = !in; 361 newbuf[j++] = buf[i]; 362 break; 363 default: 364 newbuf[j++] = buf[i]; 365 } 366 if (j == nbs) { 367 char *tmp; 368 369 nbs += 128; 370 tmp = realloc(newbuf, nbs); 371 if (tmp == NULL) 372 errx(1, "malloc failed"); 373 newbuf = tmp; 374 } 375 } 376 free(buf); 377 newbuf[j] = '\0'; 378 return(newbuf); 379 } 380 381 void 382 do_message(FILE *sdc, char *msg) 383 { 384 int i, n, bu = 0, bs = 0, len; 385 char *buf = NULL, last; 386 387 len = strlen(msg); 388 if (msg[0] == '"' && msg[len - 1] == '"') { 389 /* quoted msg, escape newlines and send it out */ 390 msg[len - 1] = '\0'; 391 buf = msg+1; 392 bu = len - 2; 393 goto sendit; 394 } else { 395 /* message isn't quoted - try to open a local 396 * file and read the message from it. 397 */ 398 int fd; 399 400 fd = open(msg, O_RDONLY); 401 if (fd == -1) 402 err(1, "Can't open message from %s", msg); 403 for (;;) { 404 if (bu == bs) { 405 char *tmp; 406 407 tmp = realloc(buf, bs + 8192); 408 if (tmp == NULL) 409 errx(1, "malloc failed"); 410 bs += 8192; 411 buf = tmp; 412 } 413 414 n = read(fd, buf + bu, bs - bu); 415 if (n == 0) { 416 goto sendit; 417 } else if (n == -1) { 418 err(1, "Can't read from %s", msg); 419 } else 420 bu += n; 421 } 422 buf[bu]='\0'; 423 } 424 sendit: 425 fprintf(sdc, ";\""); 426 last = '\0'; 427 for (i = 0; i < bu; i++) { 428 /* handle escaping the things spamd wants */ 429 switch(buf[i]) { 430 case 'n': 431 if (last == '\\') 432 fprintf(sdc, "\\\\n"); 433 else 434 fputc('n', sdc); 435 last = '\0'; 436 break; 437 case '\n': 438 fprintf(sdc, "\\n"); 439 last = '\0'; 440 break; 441 case '"': 442 fputc('\\', sdc); 443 /* fall through */ 444 default: 445 fputc(buf[i], sdc); 446 last = '\0'; 447 } 448 } 449 fputc('"', sdc); 450 if (bs != 0) 451 free(buf); 452 } 453 454 /* retrieve a list from fd. add to blacklist bl */ 455 struct bl * 456 add_blacklist(struct bl *bl, int *blc, int *bls, int fd, int white) 457 { 458 int i, n, start, bu = 0, bs = 0, serrno = 0; 459 char *buf = NULL; 460 461 for (;;) { 462 /* read in fd, then parse */ 463 if (bu == bs) { 464 char *tmp; 465 466 tmp = realloc(buf, bs + 8192); 467 if (tmp == NULL) { 468 free(buf); 469 buf = NULL; 470 bs = 0; 471 serrno = errno; 472 goto bldone; 473 } 474 bs += 8192; 475 buf = tmp; 476 } 477 478 n = read(fd, buf + bu, bs - bu); 479 if (n == 0) 480 goto parse; 481 else if (n == -1) { 482 serrno = errno; 483 goto bldone; 484 } else 485 bu += n; 486 } 487 parse: 488 start = 0; 489 for (i = 0; i < bu; i++) { 490 if (*blc == *bls) { 491 struct bl *tmp; 492 493 *bls += 1024; 494 tmp = realloc(bl, *bls * sizeof(struct bl)); 495 if (tmp == NULL) { 496 *bls -= 1024; 497 serrno = errno; 498 goto bldone; 499 } 500 bl = tmp; 501 } 502 if (buf[i] == '\n') { 503 buf[i] = '\0'; 504 if (parse_netblock (buf + start, 505 bl + *blc, bl + *blc + 1, white)) 506 *blc+=2; 507 start = i+1; 508 } 509 } 510 if (bu == 0) 511 errno = EIO; 512 bldone: 513 if (buf) 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, int blc) 538 { 539 int bs = 0, ws = 0, state=0, cli, i; 540 struct cidr ** cl; 541 u_int32_t bstart = 0; 542 543 if (blc == 0) 544 return(NULL); 545 cl = malloc((blc / 2) * sizeof(struct cidr)); 546 if (cl == NULL) { 547 return (NULL); 548 } 549 qsort(bl, blc, sizeof(struct bl), cmpbl); 550 cli = 0; 551 cl[cli] = NULL; 552 for (i = 0; i < blc;) { 553 int laststate = state; 554 u_int32_t addr = bl[i].addr; 555 556 do { 557 bs += bl[i].b; 558 ws += bl[i].w; 559 i++ ; 560 } while (bl[i].addr == addr); 561 if (state == 1 && bs == 0) 562 state = 0; 563 else if (state == 0 && bs > 0) 564 state = 1; 565 if (ws > 0) 566 state = 0; 567 if (laststate == 0 && state == 1) { 568 /* start blacklist */ 569 bstart = addr; 570 } 571 if (laststate == 1 && state == 0) { 572 /* end blacklist */ 573 cl[cli++] = range2cidrlist(bstart, addr - 1); 574 cl[cli] = NULL; 575 } 576 laststate = state; 577 } 578 return (cl); 579 } 580 581 int 582 configure_spamd(u_short dport, char *name, char *message, 583 struct cidr **blacklists) 584 { 585 int lport = IPPORT_RESERVED - 1; 586 struct sockaddr_in sin; 587 FILE* sdc; 588 int s; 589 590 s = rresvport(&lport); 591 if (s == -1) 592 return(-1); 593 memset(&sin, 0, sizeof sin); 594 sin.sin_len = sizeof(sin); 595 sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 596 sin.sin_family = AF_INET; 597 sin.sin_port = htons(dport); 598 if (connect(s, (struct sockaddr *)&sin, sizeof sin) == -1) 599 return(-1); 600 sdc = fdopen(s, "w"); 601 if (sdc == NULL) 602 return(-1); 603 fprintf(sdc, "%s", name); 604 do_message(sdc, message); 605 while (*blacklists != NULL) { 606 struct cidr *b = *blacklists; 607 while (b->addr != 0) { 608 fprintf(sdc, ";%s/%u", atop(b->addr), (b->bits)); 609 b++; 610 } 611 blacklists++; 612 } 613 fputc('\n', sdc); 614 fclose(sdc); 615 close(s); 616 return(0); 617 } 618 619 620 int 621 configure_pf(struct cidr **blacklists) 622 { 623 char *argv[9]= {"pfctl", "-q", "-t", "spamd", "-T", "replace", 624 "-f" "-", NULL}; 625 static FILE *pf = NULL; 626 int pdes[2]; 627 pid_t pid; 628 629 if (pf == NULL) { 630 if (pipe(pdes) != 0) 631 return(-1); 632 switch(pid = fork()) { 633 case -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 /* parent */ 646 close(pdes[0]); 647 pf = fdopen(pdes[1], "w"); 648 if (pf == NULL) 649 return(-1); 650 } 651 while (*blacklists != NULL) { 652 struct cidr *b = *blacklists; 653 while (b->addr != 0) { 654 fprintf(pf, "%s/%u\n", atop(b->addr), (b->bits)); 655 b++; 656 } 657 blacklists++; 658 } 659 return(0); 660 } 661 662 int 663 getlist(char ** db_array, char *name, struct blacklist *blist, 664 struct blacklist *blistnew) 665 { 666 char *buf, *method, *file, *message; 667 int blc, bls, fd, black = 0; 668 struct bl *bl = NULL; 669 670 if (cgetent(&buf, db_array, name) != 0) 671 err(1, "Can't find \"%s\" in spamd config", name); 672 buf = fix_quoted_colons(buf); 673 if (cgetcap(buf, "black", ':') != NULL) { 674 /* use new list */ 675 black = 1; 676 blc = blistnew->blc; 677 bls = blistnew->bls; 678 bl = blistnew->bl; 679 } else if (cgetcap(buf, "white", ':') != NULL) { 680 /* apply to most recent blacklist */ 681 black = 0; 682 blc = blist->blc; 683 bls = blist->bls; 684 bl = blist->bl; 685 } else 686 errx(1, "Must have \"black\" or \"white\" in %s", name); 687 688 switch (cgetstr(buf, "msg", &message)) { 689 case -1: 690 if (black) 691 errx(1, "No msg for blacklist \"%s\"", name); 692 break; 693 case -2: 694 errx(1, "malloc failed"); 695 } 696 697 switch (cgetstr(buf, "method", &method)) { 698 case -1: 699 method = NULL; 700 break; 701 case -2: 702 errx(1, "malloc failed"); 703 } 704 705 switch (cgetstr(buf, "file", &file)) { 706 case -1: 707 errx(1, "No file given for %slist %s", black?"black":"white", 708 name); 709 case -2: 710 errx(1, "malloc failed"); 711 default: 712 fd = open_file(method, file); 713 if (fd == -1) 714 err(1, "Can't open %s by %s method", 715 file, method ? method:"file"); 716 free(method); 717 free(file); 718 } 719 bl = add_blacklist(bl, &blc, &bls, fd, !black); 720 if (bl == NULL) { 721 warn("Could not add %slist %s", black ? "black" : "white", 722 name); 723 return(0); 724 } 725 if (black) { 726 blistnew->message = message; 727 blistnew->name = name; 728 blistnew->black = black; 729 blistnew->bl = bl; 730 blistnew->blc = blc; 731 blistnew->bls = bls; 732 } else { 733 /* whitelist applied to last active blacklist */ 734 blist->bl = bl; 735 blist->blc = blc; 736 blist->bls = bls; 737 } 738 return(black); 739 } 740 741 int 742 main(int argc, char *argv[]) 743 { 744 size_t dbs, dbc, blc, bls, black, white; 745 struct blacklist *blists; 746 char **db_array, *buf; 747 char *name; 748 int i; 749 struct servent *ent; 750 751 if ((ent = getservbyname("spamd-cfg", "tcp")) == NULL) 752 errx(1, "Can't find service \"spamd-cfg\" in /etc/services"); 753 ent->s_port = ntohs(ent->s_port); 754 755 dbs = argc + 2; 756 dbc = 0; 757 db_array = calloc(dbs, sizeof(char *)); 758 if (db_array == NULL) 759 errx(1, "malloc failed"); 760 761 db_array[dbc]= PATH_SPAMD_CONF; 762 dbc++; 763 for (i = 1; i < argc; i++) 764 db_array[dbc++] = argv[i]; 765 766 blists = NULL; 767 blc = bls = 0; 768 if (cgetent(&buf, db_array, "all") != 0) 769 err(1, "Can't find \"all\" in spamd config"); 770 name = strsep(&buf, ": \t"); /* skip "all" at start */ 771 blc = 0; 772 while ((name = strsep(&buf, ": \t")) != NULL) { 773 if (*name) { 774 /* extract config in order specified in "all" tag */ 775 if (blc == bls) { 776 struct blacklist *tmp; 777 bls += 1024; 778 tmp = realloc(blists, 779 bls * sizeof(struct blacklist)); 780 if (tmp == NULL) 781 errx(1, "malloc failed"); 782 blists = tmp; 783 } 784 if (blc == 0) 785 black = white = 0; 786 else { 787 white = blc - 1; 788 black = blc; 789 } 790 memset(&blists[black], 0, sizeof(struct blacklist)); 791 blc += getlist(db_array, name, &blists[white], 792 &blists[black]); 793 } 794 } 795 for (i = 0; i < blc; i++) { 796 struct cidr **cidrs, **tmp; 797 if (blists[i].blc > 0) { 798 cidrs = collapse_blacklist(blists[i].bl, 799 blists[i].blc); 800 if (cidrs == NULL) 801 errx(1, "malloc failed"); 802 if (configure_spamd(ent->s_port, blists[i].name, 803 blists[i].message, cidrs) == -1) 804 err(1, "Can't connect to spamd on port %d", 805 ent->s_port); 806 if (configure_pf(cidrs) == -1) 807 err(1, "pfctl failed"); 808 tmp = cidrs; 809 while (*tmp != NULL) 810 free(*tmp++); 811 free(cidrs); 812 free(blists[i].bl); 813 } 814 } 815 return (0); 816 } 817