1 /* $OpenBSD: spamd.c,v 1.112 2012/06/19 17:43:40 deraadt Exp $ */ 2 3 /* 4 * Copyright (c) 2002-2007 Bob Beck. All rights reserved. 5 * Copyright (c) 2002 Theo de Raadt. All rights reserved. 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <sys/param.h> 21 #include <sys/file.h> 22 #include <sys/wait.h> 23 #include <sys/socket.h> 24 #include <sys/sysctl.h> 25 #include <sys/resource.h> 26 27 #include <netinet/in.h> 28 #include <arpa/inet.h> 29 30 #include <err.h> 31 #include <errno.h> 32 #include <getopt.h> 33 #include <pwd.h> 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <syslog.h> 38 #include <unistd.h> 39 40 #include <netdb.h> 41 42 #include "sdl.h" 43 #include "grey.h" 44 #include "sync.h" 45 46 extern int server_lookup(struct sockaddr *, struct sockaddr *, 47 struct sockaddr *); 48 49 struct con { 50 int fd; 51 int state; 52 int laststate; 53 int af; 54 struct sockaddr_storage ss; 55 void *ia; 56 char addr[32]; 57 char caddr[32]; 58 char helo[MAX_MAIL], mail[MAX_MAIL], rcpt[MAX_MAIL]; 59 struct sdlist **blacklists; 60 61 /* 62 * we will do stuttering by changing these to time_t's of 63 * now + n, and only advancing when the time is in the past/now 64 */ 65 time_t r; 66 time_t w; 67 time_t s; 68 69 char ibuf[8192]; 70 char *ip; 71 int il; 72 char rend[5]; /* any chars in here causes input termination */ 73 74 char *obuf; 75 char *lists; 76 size_t osize; 77 char *op; 78 int ol; 79 int data_lines; 80 int data_body; 81 int stutter; 82 int badcmd; 83 int sr; 84 } *con; 85 86 void usage(void); 87 char *grow_obuf(struct con *, int); 88 int parse_configline(char *); 89 void parse_configs(void); 90 void do_config(void); 91 int append_error_string (struct con *, size_t, char *, int, void *); 92 void build_reply(struct con *); 93 void doreply(struct con *); 94 void setlog(char *, size_t, char *); 95 void initcon(struct con *, int, struct sockaddr *); 96 void closecon(struct con *); 97 int match(const char *, const char *); 98 void nextstate(struct con *); 99 void handler(struct con *); 100 void handlew(struct con *, int one); 101 102 char hostname[MAXHOSTNAMELEN]; 103 struct syslog_data sdata = SYSLOG_DATA_INIT; 104 char *nreply = "450"; 105 char *spamd = "spamd IP-based SPAM blocker"; 106 int greypipe[2]; 107 int trappipe[2]; 108 FILE *grey; 109 FILE *trapcfg; 110 time_t passtime = PASSTIME; 111 time_t greyexp = GREYEXP; 112 time_t whiteexp = WHITEEXP; 113 time_t trapexp = TRAPEXP; 114 struct passwd *pw; 115 pid_t jail_pid = -1; 116 u_short cfg_port; 117 u_short sync_port; 118 119 extern struct sdlist *blacklists; 120 extern int pfdev; 121 extern char *low_prio_mx_ip; 122 123 time_t slowdowntill; 124 125 int conffd = -1; 126 int trapfd = -1; 127 char *cb; 128 size_t cbs, cbu; 129 130 time_t t; 131 132 #define MAXCON 800 133 int maxfiles; 134 int maxcon = MAXCON; 135 int maxblack = MAXCON; 136 int blackcount; 137 int clients; 138 int debug; 139 int greylist = 1; 140 int grey_stutter = 10; 141 int verbose; 142 int stutter = 1; 143 int window; 144 int syncrecv; 145 int syncsend; 146 #define MAXTIME 400 147 148 void 149 usage(void) 150 { 151 extern char *__progname; 152 153 fprintf(stderr, 154 "usage: %s [-45bdv] [-B maxblack] [-c maxcon] " 155 "[-G passtime:greyexp:whiteexp]\n" 156 "\t[-h hostname] [-l address] [-M address] [-n name] [-p port]\n" 157 "\t[-S secs] [-s secs] " 158 "[-w window] [-Y synctarget] [-y synclisten]\n", 159 __progname); 160 161 exit(1); 162 } 163 164 char * 165 grow_obuf(struct con *cp, int off) 166 { 167 char *tmp; 168 169 tmp = realloc(cp->obuf, cp->osize + 8192); 170 if (tmp == NULL) { 171 free(cp->obuf); 172 cp->obuf = NULL; 173 cp->osize = 0; 174 return (NULL); 175 } else { 176 cp->osize += 8192; 177 cp->obuf = tmp; 178 return (cp->obuf + off); 179 } 180 } 181 182 int 183 parse_configline(char *line) 184 { 185 char *cp, prev, *name, *msg; 186 static char **av = NULL; 187 static size_t ac = 0; 188 size_t au = 0; 189 int mdone = 0; 190 191 name = line; 192 193 for (cp = name; *cp && *cp != ';'; cp++) 194 ; 195 if (*cp != ';') 196 goto parse_error; 197 *cp++ = '\0'; 198 if (!*cp) { 199 sdl_del(name); 200 return (0); 201 } 202 msg = cp; 203 if (*cp++ != '"') 204 goto parse_error; 205 prev = '\0'; 206 for (; !mdone; cp++) { 207 switch (*cp) { 208 case '\\': 209 if (!prev) 210 prev = *cp; 211 else 212 prev = '\0'; 213 break; 214 case '"': 215 if (prev != '\\') { 216 cp++; 217 if (*cp == ';') { 218 mdone = 1; 219 *cp = '\0'; 220 } else 221 goto parse_error; 222 } 223 break; 224 case '\0': 225 goto parse_error; 226 default: 227 prev = '\0'; 228 break; 229 } 230 } 231 232 do { 233 if (ac == au) { 234 char **tmp; 235 236 tmp = realloc(av, (ac + 2048) * sizeof(char *)); 237 if (tmp == NULL) { 238 free(av); 239 av = NULL; 240 ac = 0; 241 return (-1); 242 } 243 av = tmp; 244 ac += 2048; 245 } 246 } while ((av[au++] = strsep(&cp, ";")) != NULL); 247 248 /* toss empty last entry to allow for trailing ; */ 249 while (au > 0 && (av[au - 1] == NULL || av[au - 1][0] == '\0')) 250 au--; 251 252 if (au < 1) 253 goto parse_error; 254 else 255 sdl_add(name, msg, av, au); 256 return (0); 257 258 parse_error: 259 if (debug > 0) 260 printf("bogus config line - need 'tag;message;a/m;a/m;a/m...'\n"); 261 return (-1); 262 } 263 264 void 265 parse_configs(void) 266 { 267 char *start, *end; 268 int i; 269 270 if (cbu == cbs) { 271 char *tmp; 272 273 tmp = realloc(cb, cbs + 8192); 274 if (tmp == NULL) { 275 if (debug > 0) 276 warn("realloc"); 277 free(cb); 278 cb = NULL; 279 cbs = cbu = 0; 280 return; 281 } 282 cbs += 8192; 283 cb = tmp; 284 } 285 cb[cbu++] = '\0'; 286 287 start = cb; 288 end = start; 289 for (i = 0; i < cbu; i++) { 290 if (*end == '\n') { 291 *end = '\0'; 292 if (end > start + 1) 293 parse_configline(start); 294 start = ++end; 295 } else 296 ++end; 297 } 298 if (end > start + 1) 299 parse_configline(start); 300 } 301 302 void 303 do_config(void) 304 { 305 int n; 306 307 if (debug > 0) 308 printf("got configuration connection\n"); 309 310 if (cbu == cbs) { 311 char *tmp; 312 313 tmp = realloc(cb, cbs + 8192); 314 if (tmp == NULL) { 315 if (debug > 0) 316 warn("realloc"); 317 free(cb); 318 cb = NULL; 319 cbs = 0; 320 goto configdone; 321 } 322 cbs += 8192; 323 cb = tmp; 324 } 325 326 n = read(conffd, cb + cbu, cbs - cbu); 327 if (debug > 0) 328 printf("read %d config bytes\n", n); 329 if (n == 0) { 330 parse_configs(); 331 goto configdone; 332 } else if (n == -1) { 333 if (debug > 0) 334 warn("read"); 335 goto configdone; 336 } else 337 cbu += n; 338 return; 339 340 configdone: 341 cbu = 0; 342 close(conffd); 343 conffd = -1; 344 slowdowntill = 0; 345 } 346 347 int 348 read_configline(FILE *config) 349 { 350 char *buf; 351 size_t len; 352 353 if ((buf = fgetln(config, &len))) { 354 if (buf[len - 1] == '\n') 355 buf[len - 1] = '\0'; 356 else 357 return (-1); /* all valid lines end in \n */ 358 parse_configline(buf); 359 } else { 360 syslog_r(LOG_DEBUG, &sdata, "read_configline: fgetln (%m)"); 361 return (-1); 362 } 363 return (0); 364 } 365 366 int 367 append_error_string(struct con *cp, size_t off, char *fmt, int af, void *ia) 368 { 369 char sav = '\0'; 370 static int lastcont = 0; 371 char *c = cp->obuf + off; 372 char *s = fmt; 373 size_t len = cp->osize - off; 374 int i = 0; 375 376 if (off == 0) 377 lastcont = 0; 378 379 if (lastcont != 0) 380 cp->obuf[lastcont] = '-'; 381 snprintf(c, len, "%s ", nreply); 382 i += strlen(c); 383 lastcont = off + i - 1; 384 if (*s == '"') 385 s++; 386 while (*s) { 387 /* 388 * Make sure we at minimum, have room to add a 389 * format code (4 bytes), and a v6 address(39 bytes) 390 * and a byte saved in sav. 391 */ 392 if (i >= len - 46) { 393 c = grow_obuf(cp, off); 394 if (c == NULL) 395 return (-1); 396 len = cp->osize - (off + i); 397 } 398 399 if (c[i-1] == '\n') { 400 if (lastcont != 0) 401 cp->obuf[lastcont] = '-'; 402 snprintf(c + i, len, "%s ", nreply); 403 i += strlen(c); 404 lastcont = off + i - 1; 405 } 406 407 switch (*s) { 408 case '\\': 409 case '%': 410 if (!sav) 411 sav = *s; 412 else { 413 c[i++] = sav; 414 sav = '\0'; 415 c[i] = '\0'; 416 } 417 break; 418 case '"': 419 case 'A': 420 case 'n': 421 if (*(s+1) == '\0') { 422 break; 423 } 424 if (sav == '\\' && *s == 'n') { 425 c[i++] = '\n'; 426 sav = '\0'; 427 c[i] = '\0'; 428 break; 429 } else if (sav == '\\' && *s == '"') { 430 c[i++] = '"'; 431 sav = '\0'; 432 c[i] = '\0'; 433 break; 434 } else if (sav == '%' && *s == 'A') { 435 inet_ntop(af, ia, c + i, (len - i)); 436 i += strlen(c + i); 437 sav = '\0'; 438 break; 439 } 440 /* FALLTHROUGH */ 441 default: 442 if (sav) 443 c[i++] = sav; 444 c[i++] = *s; 445 sav = '\0'; 446 c[i] = '\0'; 447 break; 448 } 449 s++; 450 } 451 return (i); 452 } 453 454 char * 455 loglists(struct con *cp) 456 { 457 static char matchlists[80]; 458 struct sdlist **matches; 459 int s = sizeof(matchlists) - 4; 460 461 matchlists[0] = '\0'; 462 matches = cp->blacklists; 463 if (matches == NULL) 464 return (NULL); 465 for (; *matches; matches++) { 466 467 /* don't report an insane amount of lists in the logs. 468 * just truncate and indicate with ... 469 */ 470 if (strlen(matchlists) + strlen(matches[0]->tag) + 1 >= s) 471 strlcat(matchlists, " ...", sizeof(matchlists)); 472 else { 473 strlcat(matchlists, " ", s); 474 strlcat(matchlists, matches[0]->tag, s); 475 } 476 } 477 return matchlists; 478 } 479 480 void 481 build_reply(struct con *cp) 482 { 483 struct sdlist **matches; 484 int off = 0; 485 486 matches = cp->blacklists; 487 if (matches == NULL) 488 goto nomatch; 489 for (; *matches; matches++) { 490 int used = 0; 491 char *c = cp->obuf + off; 492 int left = cp->osize - off; 493 494 used = append_error_string(cp, off, matches[0]->string, 495 cp->af, cp->ia); 496 if (used == -1) 497 goto bad; 498 off += used; 499 left -= used; 500 if (cp->obuf[off - 1] != '\n') { 501 if (left < 1) { 502 c = grow_obuf(cp, off); 503 if (c == NULL) 504 goto bad; 505 } 506 cp->obuf[off++] = '\n'; 507 cp->obuf[off] = '\0'; 508 } 509 } 510 return; 511 nomatch: 512 /* No match. give generic reply */ 513 free(cp->obuf); 514 cp->obuf = NULL; 515 cp->osize = 0; 516 if (cp->blacklists != NULL) 517 asprintf(&cp->obuf, 518 "%s-Sorry %s\n" 519 "%s-You are trying to send mail from an address " 520 "listed by one\n" 521 "%s or more IP-based registries as being a SPAM source.\n", 522 nreply, cp->addr, nreply, nreply); 523 else 524 asprintf(&cp->obuf, 525 "451 Temporary failure, please try again later.\r\n"); 526 if (cp->obuf != NULL) 527 cp->osize = strlen(cp->obuf) + 1; 528 else 529 cp->osize = 0; 530 return; 531 bad: 532 if (cp->obuf != NULL) { 533 free(cp->obuf); 534 cp->obuf = NULL; 535 cp->osize = 0; 536 } 537 } 538 539 void 540 doreply(struct con *cp) 541 { 542 build_reply(cp); 543 } 544 545 void 546 setlog(char *p, size_t len, char *f) 547 { 548 char *s; 549 550 s = strsep(&f, ":"); 551 if (!f) 552 return; 553 while (*f == ' ' || *f == '\t') 554 f++; 555 s = strsep(&f, " \t"); 556 if (s == NULL) 557 return; 558 strlcpy(p, s, len); 559 s = strsep(&p, " \t\n\r"); 560 if (s == NULL) 561 return; 562 s = strsep(&p, " \t\n\r"); 563 if (s) 564 *s = '\0'; 565 } 566 567 /* 568 * Get address client connected to, by doing a DIOCNATLOOK call. 569 * Uses server_lookup code from ftp-proxy. 570 */ 571 void 572 getcaddr(struct con *cp) 573 { 574 struct sockaddr_storage spamd_end; 575 struct sockaddr *sep = (struct sockaddr *) &spamd_end; 576 struct sockaddr_storage original_destination; 577 struct sockaddr *odp = (struct sockaddr *) &original_destination; 578 socklen_t len = sizeof(struct sockaddr_storage); 579 int error; 580 581 cp->caddr[0] = '\0'; 582 if (getsockname(cp->fd, sep, &len) == -1) 583 return; 584 if (server_lookup((struct sockaddr *)&cp->ss, sep, odp) != 0) 585 return; 586 error = getnameinfo(odp, odp->sa_len, cp->caddr, sizeof(cp->caddr), 587 NULL, 0, NI_NUMERICHOST); 588 if (error) 589 cp->caddr[0] = '\0'; 590 } 591 592 void 593 gethelo(char *p, size_t len, char *f) 594 { 595 char *s; 596 597 /* skip HELO/EHLO */ 598 f+=4; 599 /* skip whitespace */ 600 while (*f == ' ' || *f == '\t') 601 f++; 602 s = strsep(&f, " \t"); 603 if (s == NULL) 604 return; 605 strlcpy(p, s, len); 606 s = strsep(&p, " \t\n\r"); 607 if (s == NULL) 608 return; 609 s = strsep(&p, " \t\n\r"); 610 if (s) 611 *s = '\0'; 612 } 613 614 void 615 initcon(struct con *cp, int fd, struct sockaddr *sa) 616 { 617 socklen_t len = sa->sa_len; 618 time_t tt; 619 char *tmp; 620 int error; 621 622 time(&tt); 623 free(cp->obuf); 624 cp->obuf = NULL; 625 cp->osize = 0; 626 free(cp->blacklists); 627 cp->blacklists = NULL; 628 free(cp->lists); 629 cp->lists = NULL; 630 bzero(cp, sizeof(struct con)); 631 if (grow_obuf(cp, 0) == NULL) 632 err(1, "malloc"); 633 cp->fd = fd; 634 if (len > sizeof(cp->ss)) 635 errx(1, "sockaddr size"); 636 if (sa->sa_family != AF_INET) 637 errx(1, "not supported yet"); 638 memcpy(&cp->ss, sa, sa->sa_len); 639 cp->af = sa->sa_family; 640 cp->ia = &((struct sockaddr_in *)&cp->ss)->sin_addr; 641 cp->blacklists = sdl_lookup(blacklists, cp->af, cp->ia); 642 cp->stutter = (greylist && !grey_stutter && cp->blacklists == NULL) ? 643 0 : stutter; 644 error = getnameinfo(sa, sa->sa_len, cp->addr, sizeof(cp->addr), NULL, 0, 645 NI_NUMERICHOST); 646 #ifdef useless 647 if (error) 648 errx(1, "%s", gai_strerror(error)); 649 #endif 650 tmp = strdup(ctime(&t)); 651 if (tmp == NULL) 652 err(1, "malloc"); 653 tmp[strlen(tmp) - 1] = '\0'; /* nuke newline */ 654 snprintf(cp->obuf, cp->osize, "220 %s ESMTP %s; %s\r\n", 655 hostname, spamd, tmp); 656 free(tmp); 657 cp->op = cp->obuf; 658 cp->ol = strlen(cp->op); 659 cp->w = tt + cp->stutter; 660 cp->s = tt; 661 strlcpy(cp->rend, "\n", sizeof cp->rend); 662 clients++; 663 if (cp->blacklists != NULL) { 664 blackcount++; 665 if (greylist && blackcount > maxblack) 666 cp->stutter = 0; 667 cp->lists = strdup(loglists(cp)); 668 } 669 else 670 cp->lists = NULL; 671 } 672 673 void 674 closecon(struct con *cp) 675 { 676 time_t tt; 677 678 close(cp->fd); 679 slowdowntill = 0; 680 681 time(&tt); 682 syslog_r(LOG_INFO, &sdata, "%s: disconnected after %ld seconds.%s%s", 683 cp->addr, (long)(tt - cp->s), 684 ((cp->lists == NULL) ? "" : " lists:"), 685 ((cp->lists == NULL) ? "": cp->lists)); 686 if (debug > 0) 687 printf("%s connected for %ld seconds.\n", cp->addr, 688 (long)(tt - cp->s)); 689 if (cp->lists != NULL) { 690 free(cp->lists); 691 cp->lists = NULL; 692 } 693 if (cp->blacklists != NULL) { 694 blackcount--; 695 free(cp->blacklists); 696 cp->blacklists = NULL; 697 } 698 if (cp->obuf != NULL) { 699 free(cp->obuf); 700 cp->obuf = NULL; 701 cp->osize = 0; 702 } 703 clients--; 704 cp->fd = -1; 705 } 706 707 int 708 match(const char *s1, const char *s2) 709 { 710 return (strncasecmp(s1, s2, strlen(s2)) == 0); 711 } 712 713 void 714 nextstate(struct con *cp) 715 { 716 if (match(cp->ibuf, "QUIT") && cp->state < 99) { 717 snprintf(cp->obuf, cp->osize, "221 %s\r\n", hostname); 718 cp->op = cp->obuf; 719 cp->ol = strlen(cp->op); 720 cp->w = t + cp->stutter; 721 cp->laststate = cp->state; 722 cp->state = 99; 723 return; 724 } 725 726 if (match(cp->ibuf, "RSET") && cp->state > 2 && cp->state < 50) { 727 snprintf(cp->obuf, cp->osize, 728 "250 Ok to start over.\r\n"); 729 cp->op = cp->obuf; 730 cp->ol = strlen(cp->op); 731 cp->w = t + cp->stutter; 732 cp->laststate = cp->state; 733 cp->state = 2; 734 return; 735 } 736 switch (cp->state) { 737 case 0: 738 /* banner sent; wait for input */ 739 cp->ip = cp->ibuf; 740 cp->il = sizeof(cp->ibuf) - 1; 741 cp->laststate = cp->state; 742 cp->state = 1; 743 cp->r = t; 744 break; 745 case 1: 746 /* received input: parse, and select next state */ 747 if (match(cp->ibuf, "HELO") || 748 match(cp->ibuf, "EHLO")) { 749 int nextstate = 2; 750 cp->helo[0] = '\0'; 751 gethelo(cp->helo, sizeof cp->helo, cp->ibuf); 752 if (cp->helo[0] == '\0') { 753 nextstate = 0; 754 snprintf(cp->obuf, cp->osize, 755 "501 helo requires domain name.\r\n"); 756 } else { 757 snprintf(cp->obuf, cp->osize, 758 "250 Hello, spam sender. " 759 "Pleased to be wasting your time.\r\n"); 760 } 761 cp->op = cp->obuf; 762 cp->ol = strlen(cp->op); 763 cp->laststate = cp->state; 764 cp->state = nextstate; 765 cp->w = t + cp->stutter; 766 break; 767 } 768 goto mail; 769 case 2: 770 /* sent 250 Hello, wait for input */ 771 cp->ip = cp->ibuf; 772 cp->il = sizeof(cp->ibuf) - 1; 773 cp->laststate = cp->state; 774 cp->state = 3; 775 cp->r = t; 776 break; 777 case 3: 778 mail: 779 if (match(cp->ibuf, "MAIL")) { 780 setlog(cp->mail, sizeof cp->mail, cp->ibuf); 781 snprintf(cp->obuf, cp->osize, 782 "250 You are about to try to deliver spam. " 783 "Your time will be spent, for nothing.\r\n"); 784 cp->op = cp->obuf; 785 cp->ol = strlen(cp->op); 786 cp->laststate = cp->state; 787 cp->state = 4; 788 cp->w = t + cp->stutter; 789 break; 790 } 791 goto rcpt; 792 case 4: 793 /* sent 250 Sender ok */ 794 cp->ip = cp->ibuf; 795 cp->il = sizeof(cp->ibuf) - 1; 796 cp->laststate = cp->state; 797 cp->state = 5; 798 cp->r = t; 799 break; 800 case 5: 801 rcpt: 802 if (match(cp->ibuf, "RCPT")) { 803 setlog(cp->rcpt, sizeof(cp->rcpt), cp->ibuf); 804 snprintf(cp->obuf, cp->osize, 805 "250 This is hurting you more than it is " 806 "hurting me.\r\n"); 807 cp->op = cp->obuf; 808 cp->ol = strlen(cp->op); 809 cp->laststate = cp->state; 810 cp->state = 6; 811 cp->w = t + cp->stutter; 812 813 if (cp->mail[0] && cp->rcpt[0]) { 814 if (verbose) 815 syslog_r(LOG_INFO, &sdata, 816 "(%s) %s: %s -> %s", 817 cp->blacklists ? "BLACK" : "GREY", 818 cp->addr, cp->mail, 819 cp->rcpt); 820 if (debug) 821 fprintf(stderr, "(%s) %s: %s -> %s\n", 822 cp->blacklists ? "BLACK" : "GREY", 823 cp->addr, cp->mail, cp->rcpt); 824 if (greylist && cp->blacklists == NULL) { 825 /* send this info to the greylister */ 826 getcaddr(cp); 827 fprintf(grey, 828 "CO:%s\nHE:%s\nIP:%s\nFR:%s\nTO:%s\n", 829 cp->caddr, cp->helo, cp->addr, 830 cp->mail, cp->rcpt); 831 fflush(grey); 832 } 833 } 834 break; 835 } 836 goto spam; 837 case 6: 838 /* sent 250 blah */ 839 cp->ip = cp->ibuf; 840 cp->il = sizeof(cp->ibuf) - 1; 841 cp->laststate = cp->state; 842 cp->state = 5; 843 cp->r = t; 844 break; 845 846 case 50: 847 spam: 848 if (match(cp->ibuf, "DATA")) { 849 snprintf(cp->obuf, cp->osize, 850 "354 Enter spam, end with \".\" on a line by " 851 "itself\r\n"); 852 cp->state = 60; 853 if (window && setsockopt(cp->fd, SOL_SOCKET, SO_RCVBUF, 854 &window, sizeof(window)) == -1) { 855 syslog_r(LOG_DEBUG, &sdata,"setsockopt: %m"); 856 /* don't fail if this doesn't work. */ 857 } 858 cp->ip = cp->ibuf; 859 cp->il = sizeof(cp->ibuf) - 1; 860 cp->op = cp->obuf; 861 cp->ol = strlen(cp->op); 862 cp->w = t + cp->stutter; 863 if (greylist && cp->blacklists == NULL) { 864 cp->laststate = cp->state; 865 cp->state = 98; 866 goto done; 867 } 868 } else { 869 if (match(cp->ibuf, "NOOP")) 870 snprintf(cp->obuf, cp->osize, 871 "250 2.0.0 OK I did nothing\r\n"); 872 else { 873 snprintf(cp->obuf, cp->osize, 874 "500 5.5.1 Command unrecognized\r\n"); 875 cp->badcmd++; 876 if (cp->badcmd > 20) { 877 cp->laststate = cp->state; 878 cp->state = 98; 879 goto done; 880 } 881 } 882 cp->state = cp->laststate; 883 cp->ip = cp->ibuf; 884 cp->il = sizeof(cp->ibuf) - 1; 885 cp->op = cp->obuf; 886 cp->ol = strlen(cp->op); 887 cp->w = t + cp->stutter; 888 } 889 break; 890 case 60: 891 /* sent 354 blah */ 892 cp->ip = cp->ibuf; 893 cp->il = sizeof(cp->ibuf) - 1; 894 cp->laststate = cp->state; 895 cp->state = 70; 896 cp->r = t; 897 break; 898 case 70: { 899 char *p, *q; 900 901 for (p = q = cp->ibuf; q <= cp->ip; ++q) 902 if (*q == '\n' || q == cp->ip) { 903 *q = 0; 904 if (q > p && q[-1] == '\r') 905 q[-1] = 0; 906 if (!strcmp(p, ".") || 907 (cp->data_body && ++cp->data_lines >= 10)) { 908 cp->laststate = cp->state; 909 cp->state = 98; 910 goto done; 911 } 912 if (!cp->data_body && !*p) 913 cp->data_body = 1; 914 if (verbose && cp->data_body && *p) 915 syslog_r(LOG_DEBUG, &sdata, "%s: " 916 "Body: %s", cp->addr, p); 917 else if (verbose && (match(p, "FROM:") || 918 match(p, "TO:") || match(p, "SUBJECT:"))) 919 syslog_r(LOG_INFO, &sdata, "%s: %s", 920 cp->addr, p); 921 p = ++q; 922 } 923 cp->ip = cp->ibuf; 924 cp->il = sizeof(cp->ibuf) - 1; 925 cp->r = t; 926 break; 927 } 928 case 98: 929 done: 930 doreply(cp); 931 cp->op = cp->obuf; 932 cp->ol = strlen(cp->op); 933 cp->w = t + cp->stutter; 934 cp->laststate = cp->state; 935 cp->state = 99; 936 break; 937 case 99: 938 closecon(cp); 939 break; 940 default: 941 errx(1, "illegal state %d", cp->state); 942 break; 943 } 944 } 945 946 void 947 handler(struct con *cp) 948 { 949 int end = 0; 950 int n; 951 952 if (cp->r) { 953 n = read(cp->fd, cp->ip, cp->il); 954 if (n == 0) 955 closecon(cp); 956 else if (n == -1) { 957 if (debug > 0) 958 warn("read"); 959 closecon(cp); 960 } else { 961 cp->ip[n] = '\0'; 962 if (cp->rend[0]) 963 if (strpbrk(cp->ip, cp->rend)) 964 end = 1; 965 cp->ip += n; 966 cp->il -= n; 967 } 968 } 969 if (end || cp->il == 0) { 970 while (cp->ip > cp->ibuf && 971 (cp->ip[-1] == '\r' || cp->ip[-1] == '\n')) 972 cp->ip--; 973 *cp->ip = '\0'; 974 cp->r = 0; 975 nextstate(cp); 976 } 977 } 978 979 void 980 handlew(struct con *cp, int one) 981 { 982 int n; 983 984 /* kill stutter on greylisted connections after initial delay */ 985 if (cp->stutter && greylist && cp->blacklists == NULL && 986 (t - cp->s) > grey_stutter) 987 cp->stutter=0; 988 989 if (cp->w) { 990 if (*cp->op == '\n' && !cp->sr) { 991 /* insert \r before \n */ 992 n = write(cp->fd, "\r", 1); 993 if (n == 0) { 994 closecon(cp); 995 goto handled; 996 } else if (n == -1) { 997 if (debug > 0 && errno != EPIPE) 998 warn("write"); 999 closecon(cp); 1000 goto handled; 1001 } 1002 } 1003 if (*cp->op == '\r') 1004 cp->sr = 1; 1005 else 1006 cp->sr = 0; 1007 n = write(cp->fd, cp->op, (one && cp->stutter) ? 1 : cp->ol); 1008 if (n == 0) 1009 closecon(cp); 1010 else if (n == -1) { 1011 if (debug > 0 && errno != EPIPE) 1012 warn("write"); 1013 closecon(cp); 1014 } else { 1015 cp->op += n; 1016 cp->ol -= n; 1017 } 1018 } 1019 handled: 1020 cp->w = t + cp->stutter; 1021 if (cp->ol == 0) { 1022 cp->w = 0; 1023 nextstate(cp); 1024 } 1025 } 1026 1027 static int 1028 get_maxfiles(void) 1029 { 1030 int mib[2], maxfiles; 1031 size_t len; 1032 1033 mib[0] = CTL_KERN; 1034 mib[1] = KERN_MAXFILES; 1035 len = sizeof(maxfiles); 1036 if (sysctl(mib, 2, &maxfiles, &len, NULL, 0) == -1) 1037 return(MAXCON); 1038 if ((maxfiles - 200) < 10) 1039 errx(1, "kern.maxfiles is only %d, can not continue\n", 1040 maxfiles); 1041 else 1042 return(maxfiles - 200); 1043 } 1044 1045 int 1046 main(int argc, char *argv[]) 1047 { 1048 fd_set *fdsr = NULL, *fdsw = NULL; 1049 struct sockaddr_in sin; 1050 struct sockaddr_in lin; 1051 int ch, s, conflisten = 0, syncfd = 0, i, omax = 0, one = 1; 1052 u_short port; 1053 struct servent *ent; 1054 struct rlimit rlp; 1055 char *bind_address = NULL; 1056 const char *errstr; 1057 char *sync_iface = NULL; 1058 char *sync_baddr = NULL; 1059 1060 tzset(); 1061 openlog_r("spamd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata); 1062 1063 if ((ent = getservbyname("spamd", "tcp")) == NULL) 1064 errx(1, "Can't find service \"spamd\" in /etc/services"); 1065 port = ntohs(ent->s_port); 1066 if ((ent = getservbyname("spamd-cfg", "tcp")) == NULL) 1067 errx(1, "Can't find service \"spamd-cfg\" in /etc/services"); 1068 cfg_port = ntohs(ent->s_port); 1069 if ((ent = getservbyname("spamd-sync", "udp")) == NULL) 1070 errx(1, "Can't find service \"spamd-sync\" in /etc/services"); 1071 sync_port = ntohs(ent->s_port); 1072 1073 if (gethostname(hostname, sizeof hostname) == -1) 1074 err(1, "gethostname"); 1075 maxfiles = get_maxfiles(); 1076 if (maxcon > maxfiles) 1077 maxcon = maxfiles; 1078 if (maxblack > maxfiles) 1079 maxblack = maxfiles; 1080 while ((ch = 1081 getopt(argc, argv, "45l:c:B:p:bdG:h:s:S:M:n:vw:y:Y:")) != -1) { 1082 switch (ch) { 1083 case '4': 1084 nreply = "450"; 1085 break; 1086 case '5': 1087 nreply = "550"; 1088 break; 1089 case 'l': 1090 bind_address = optarg; 1091 break; 1092 case 'B': 1093 i = atoi(optarg); 1094 maxblack = i; 1095 break; 1096 case 'c': 1097 i = atoi(optarg); 1098 if (i > maxfiles) { 1099 fprintf(stderr, 1100 "%d > system max of %d connections\n", 1101 i, maxfiles); 1102 usage(); 1103 } 1104 maxcon = i; 1105 break; 1106 case 'p': 1107 i = atoi(optarg); 1108 port = i; 1109 break; 1110 case 'd': 1111 debug = 1; 1112 break; 1113 case 'b': 1114 greylist = 0; 1115 break; 1116 case 'G': 1117 if (sscanf(optarg, "%d:%d:%d", &passtime, &greyexp, 1118 &whiteexp) != 3) 1119 usage(); 1120 /* convert to seconds from minutes */ 1121 passtime *= 60; 1122 /* convert to seconds from hours */ 1123 whiteexp *= (60 * 60); 1124 /* convert to seconds from hours */ 1125 greyexp *= (60 * 60); 1126 break; 1127 case 'h': 1128 bzero(&hostname, sizeof(hostname)); 1129 if (strlcpy(hostname, optarg, sizeof(hostname)) >= 1130 sizeof(hostname)) 1131 errx(1, "-h arg too long"); 1132 break; 1133 case 's': 1134 i = strtonum(optarg, 0, 10, &errstr); 1135 if (errstr) 1136 usage(); 1137 stutter = i; 1138 break; 1139 case 'S': 1140 i = strtonum(optarg, 0, 90, &errstr); 1141 if (errstr) 1142 usage(); 1143 grey_stutter = i; 1144 break; 1145 case 'M': 1146 low_prio_mx_ip = optarg; 1147 break; 1148 case 'n': 1149 spamd = optarg; 1150 break; 1151 case 'v': 1152 verbose = 1; 1153 break; 1154 case 'w': 1155 window = atoi(optarg); 1156 if (window <= 0) 1157 usage(); 1158 break; 1159 case 'Y': 1160 if (sync_addhost(optarg, sync_port) != 0) 1161 sync_iface = optarg; 1162 syncsend++; 1163 break; 1164 case 'y': 1165 sync_baddr = optarg; 1166 syncrecv++; 1167 break; 1168 default: 1169 usage(); 1170 break; 1171 } 1172 } 1173 1174 setproctitle("[priv]%s%s", 1175 greylist ? " (greylist)" : "", 1176 (syncrecv || syncsend) ? " (sync)" : ""); 1177 1178 if (!greylist) 1179 maxblack = maxcon; 1180 else if (maxblack > maxcon) 1181 usage(); 1182 1183 rlp.rlim_cur = rlp.rlim_max = maxcon + 15; 1184 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1) 1185 err(1, "setrlimit"); 1186 1187 con = calloc(maxcon, sizeof(*con)); 1188 if (con == NULL) 1189 err(1, "calloc"); 1190 1191 con->obuf = malloc(8192); 1192 1193 if (con->obuf == NULL) 1194 err(1, "malloc"); 1195 con->osize = 8192; 1196 1197 for (i = 0; i < maxcon; i++) 1198 con[i].fd = -1; 1199 1200 signal(SIGPIPE, SIG_IGN); 1201 1202 s = socket(AF_INET, SOCK_STREAM, 0); 1203 if (s == -1) 1204 err(1, "socket"); 1205 1206 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, 1207 sizeof(one)) == -1) 1208 return (-1); 1209 1210 conflisten = socket(AF_INET, SOCK_STREAM, 0); 1211 if (conflisten == -1) 1212 err(1, "socket"); 1213 1214 if (setsockopt(conflisten, SOL_SOCKET, SO_REUSEADDR, &one, 1215 sizeof(one)) == -1) 1216 return (-1); 1217 1218 memset(&sin, 0, sizeof sin); 1219 sin.sin_len = sizeof(sin); 1220 if (bind_address) { 1221 if (inet_pton(AF_INET, bind_address, &sin.sin_addr) != 1) 1222 err(1, "inet_pton"); 1223 } else 1224 sin.sin_addr.s_addr = htonl(INADDR_ANY); 1225 sin.sin_family = AF_INET; 1226 sin.sin_port = htons(port); 1227 1228 if (bind(s, (struct sockaddr *)&sin, sizeof sin) == -1) 1229 err(1, "bind"); 1230 1231 memset(&lin, 0, sizeof sin); 1232 lin.sin_len = sizeof(sin); 1233 lin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 1234 lin.sin_family = AF_INET; 1235 lin.sin_port = htons(cfg_port); 1236 1237 if (bind(conflisten, (struct sockaddr *)&lin, sizeof lin) == -1) 1238 err(1, "bind local"); 1239 1240 if (syncsend || syncrecv) { 1241 syncfd = sync_init(sync_iface, sync_baddr, sync_port); 1242 if (syncfd == -1) 1243 err(1, "sync init"); 1244 } 1245 1246 if ((pw = getpwnam("_spamd")) == NULL) 1247 errx(1, "no such user _spamd"); 1248 1249 if (debug == 0) { 1250 if (daemon(1, 1) == -1) 1251 err(1, "daemon"); 1252 } 1253 1254 if (greylist) { 1255 pfdev = open("/dev/pf", O_RDWR); 1256 if (pfdev == -1) { 1257 syslog_r(LOG_ERR, &sdata, "open /dev/pf: %m"); 1258 exit(1); 1259 } 1260 1261 maxblack = (maxblack >= maxcon) ? maxcon - 100 : maxblack; 1262 if (maxblack < 0) 1263 maxblack = 0; 1264 1265 /* open pipe to talk to greylister */ 1266 if (pipe(greypipe) == -1) { 1267 syslog(LOG_ERR, "pipe (%m)"); 1268 exit(1); 1269 } 1270 /* open pipe to recieve spamtrap configs */ 1271 if (pipe(trappipe) == -1) { 1272 syslog(LOG_ERR, "pipe (%m)"); 1273 exit(1); 1274 } 1275 jail_pid = fork(); 1276 switch (jail_pid) { 1277 case -1: 1278 syslog(LOG_ERR, "fork (%m)"); 1279 exit(1); 1280 case 0: 1281 /* child - continue */ 1282 signal(SIGPIPE, SIG_IGN); 1283 grey = fdopen(greypipe[1], "w"); 1284 if (grey == NULL) { 1285 syslog(LOG_ERR, "fdopen (%m)"); 1286 _exit(1); 1287 } 1288 close(greypipe[0]); 1289 trapfd = trappipe[0]; 1290 trapcfg = fdopen(trappipe[0], "r"); 1291 if (trapcfg == NULL) { 1292 syslog(LOG_ERR, "fdopen (%m)"); 1293 _exit(1); 1294 } 1295 close(trappipe[1]); 1296 goto jail; 1297 } 1298 /* parent - run greylister */ 1299 grey = fdopen(greypipe[0], "r"); 1300 if (grey == NULL) { 1301 syslog(LOG_ERR, "fdopen (%m)"); 1302 exit(1); 1303 } 1304 close(greypipe[1]); 1305 trapcfg = fdopen(trappipe[1], "w"); 1306 if (trapcfg == NULL) { 1307 syslog(LOG_ERR, "fdopen (%m)"); 1308 exit(1); 1309 } 1310 close(trappipe[0]); 1311 return (greywatcher()); 1312 /* NOTREACHED */ 1313 } 1314 1315 jail: 1316 if (chroot("/var/empty") == -1 || chdir("/") == -1) { 1317 syslog(LOG_ERR, "cannot chdir to /var/empty."); 1318 exit(1); 1319 } 1320 1321 if (pw) 1322 if (setgroups(1, &pw->pw_gid) || 1323 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 1324 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 1325 err(1, "failed to drop privs"); 1326 1327 if (listen(s, 10) == -1) 1328 err(1, "listen"); 1329 1330 if (listen(conflisten, 10) == -1) 1331 err(1, "listen"); 1332 1333 if (debug != 0) 1334 printf("listening for incoming connections.\n"); 1335 syslog_r(LOG_WARNING, &sdata, "listening for incoming connections."); 1336 1337 while (1) { 1338 struct timeval tv, *tvp; 1339 int max, n; 1340 int writers; 1341 1342 max = MAX(s, conflisten); 1343 if (syncrecv) 1344 max = MAX(max, syncfd); 1345 max = MAX(max, conffd); 1346 max = MAX(max, trapfd); 1347 1348 time(&t); 1349 for (i = 0; i < maxcon; i++) 1350 if (con[i].fd != -1) 1351 max = MAX(max, con[i].fd); 1352 1353 if (max > omax) { 1354 free(fdsr); 1355 fdsr = NULL; 1356 free(fdsw); 1357 fdsw = NULL; 1358 fdsr = (fd_set *)calloc(howmany(max+1, NFDBITS), 1359 sizeof(fd_mask)); 1360 if (fdsr == NULL) 1361 err(1, "calloc"); 1362 fdsw = (fd_set *)calloc(howmany(max+1, NFDBITS), 1363 sizeof(fd_mask)); 1364 if (fdsw == NULL) 1365 err(1, "calloc"); 1366 omax = max; 1367 } else { 1368 memset(fdsr, 0, howmany(max+1, NFDBITS) * 1369 sizeof(fd_mask)); 1370 memset(fdsw, 0, howmany(max+1, NFDBITS) * 1371 sizeof(fd_mask)); 1372 } 1373 1374 writers = 0; 1375 for (i = 0; i < maxcon; i++) { 1376 if (con[i].fd != -1 && con[i].r) { 1377 if (con[i].r + MAXTIME <= t) { 1378 closecon(&con[i]); 1379 continue; 1380 } 1381 FD_SET(con[i].fd, fdsr); 1382 } 1383 if (con[i].fd != -1 && con[i].w) { 1384 if (con[i].w + MAXTIME <= t) { 1385 closecon(&con[i]); 1386 continue; 1387 } 1388 if (con[i].w <= t) 1389 FD_SET(con[i].fd, fdsw); 1390 writers = 1; 1391 } 1392 } 1393 if (slowdowntill == 0) { 1394 FD_SET(s, fdsr); 1395 1396 /* only one active config conn at a time */ 1397 if (conffd == -1) 1398 FD_SET(conflisten, fdsr); 1399 else 1400 FD_SET(conffd, fdsr); 1401 } 1402 1403 if (trapfd != -1) 1404 FD_SET(trapfd, fdsr); 1405 if (syncrecv) 1406 FD_SET(syncfd, fdsr); 1407 1408 /* If we are not listening, wake up at least once a second */ 1409 if (writers == 0 && slowdowntill == 0) { 1410 tvp = NULL; 1411 } else { 1412 tv.tv_sec = 1; 1413 tv.tv_usec = 0; 1414 tvp = &tv; 1415 } 1416 1417 n = select(max+1, fdsr, fdsw, NULL, tvp); 1418 if (n == -1) { 1419 if (errno != EINTR) 1420 err(1, "select"); 1421 continue; 1422 } 1423 1424 /* Check if we can speed up accept() calls */ 1425 if (slowdowntill && slowdowntill > t) 1426 slowdowntill = 0; 1427 1428 for (i = 0; i < maxcon; i++) { 1429 if (con[i].fd != -1 && FD_ISSET(con[i].fd, fdsr)) 1430 handler(&con[i]); 1431 if (con[i].fd != -1 && FD_ISSET(con[i].fd, fdsw)) 1432 handlew(&con[i], clients + 5 < maxcon); 1433 } 1434 if (FD_ISSET(s, fdsr)) { 1435 socklen_t sinlen; 1436 int s2; 1437 1438 sinlen = sizeof(sin); 1439 s2 = accept(s, (struct sockaddr *)&sin, &sinlen); 1440 if (s2 == -1) { 1441 switch (errno) { 1442 case EINTR: 1443 case ECONNABORTED: 1444 break; 1445 case EMFILE: 1446 case ENFILE: 1447 slowdowntill = time(NULL) + 1; 1448 break; 1449 default: 1450 errx(1, "accept"); 1451 } 1452 } else { 1453 /* Check if we hit the chosen fd limit */ 1454 for (i = 0; i < maxcon; i++) 1455 if (con[i].fd == -1) 1456 break; 1457 if (i == maxcon) { 1458 close(s2); 1459 slowdowntill = 0; 1460 } else { 1461 initcon(&con[i], s2, 1462 (struct sockaddr *)&sin); 1463 syslog_r(LOG_INFO, &sdata, 1464 "%s: connected (%d/%d)%s%s", 1465 con[i].addr, clients, blackcount, 1466 ((con[i].lists == NULL) ? "" : 1467 ", lists:"), 1468 ((con[i].lists == NULL) ? "": 1469 con[i].lists)); 1470 } 1471 } 1472 } 1473 if (FD_ISSET(conflisten, fdsr)) { 1474 socklen_t sinlen; 1475 1476 sinlen = sizeof(lin); 1477 conffd = accept(conflisten, (struct sockaddr *)&lin, 1478 &sinlen); 1479 if (conffd == -1) { 1480 switch (errno) { 1481 case EINTR: 1482 case ECONNABORTED: 1483 break; 1484 case EMFILE: 1485 case ENFILE: 1486 slowdowntill = time(NULL) + 1; 1487 break; 1488 default: 1489 errx(1, "accept"); 1490 } 1491 } else if (ntohs(lin.sin_port) >= IPPORT_RESERVED) { 1492 close(conffd); 1493 conffd = -1; 1494 slowdowntill = 0; 1495 } 1496 } else if (conffd != -1 && FD_ISSET(conffd, fdsr)) 1497 do_config(); 1498 if (trapfd != -1 && FD_ISSET(trapfd, fdsr)) 1499 read_configline(trapcfg); 1500 if (syncrecv && FD_ISSET(syncfd, fdsr)) 1501 sync_recv(); 1502 } 1503 exit(1); 1504 } 1505