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