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