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