1 /* $OpenBSD: grey.c,v 1.55 2013/11/27 21:25:25 deraadt Exp $ */ 2 3 /* 4 * Copyright (c) 2004-2006 Bob Beck. All rights reserved. 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 #include <sys/socket.h> 21 #include <sys/ioctl.h> 22 #include <sys/fcntl.h> 23 #include <sys/wait.h> 24 #include <net/if.h> 25 #include <netinet/in.h> 26 #include <net/pfvar.h> 27 #include <arpa/inet.h> 28 #include <ctype.h> 29 #include <db.h> 30 #include <err.h> 31 #include <errno.h> 32 #include <fcntl.h> 33 #include <pwd.h> 34 #include <signal.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <syslog.h> 39 #include <time.h> 40 #include <unistd.h> 41 #include <netdb.h> 42 43 #include "grey.h" 44 #include "sync.h" 45 46 extern time_t passtime, greyexp, whiteexp, trapexp; 47 extern struct syslog_data sdata; 48 extern struct passwd *pw; 49 extern u_short cfg_port; 50 extern pid_t jail_pid; 51 extern FILE *trapcfg; 52 extern FILE *grey; 53 extern int debug; 54 extern int syncsend; 55 56 /* From netinet/in.h, but only _KERNEL_ gets them. */ 57 #define satosin(sa) ((struct sockaddr_in *)(sa)) 58 #define satosin6(sa) ((struct sockaddr_in6 *)(sa)) 59 int server_lookup4(struct sockaddr_in *, struct sockaddr_in *, 60 struct sockaddr_in *); 61 int server_lookup6(struct sockaddr_in6 *, struct sockaddr_in6 *, 62 struct sockaddr_in6 *); 63 64 void configure_spamd(char **, size_t, FILE *); 65 int server_lookup(struct sockaddr *, struct sockaddr *, 66 struct sockaddr *); 67 int configure_pf(char **, int); 68 char *dequotetolower(const char *); 69 void readsuffixlists(void); 70 void freeaddrlists(void); 71 int addwhiteaddr(char *); 72 int addtrapaddr(char *); 73 int db_addrstate(DB *, char *); 74 int greyscan(char *); 75 int trapcheck(DB *, char *); 76 int twupdate(char *, char *, char *, char *, char *); 77 int twread(char *); 78 int greyreader(void); 79 void greyscanner(void); 80 81 82 size_t whitecount, whitealloc; 83 size_t trapcount, trapalloc; 84 char **whitelist; 85 char **traplist; 86 87 char *traplist_name = "spamd-greytrap"; 88 char *traplist_msg = "\"Your address %A has mailed to spamtraps here\\n\""; 89 90 pid_t db_pid = -1; 91 int pfdev; 92 93 struct db_change { 94 SLIST_ENTRY(db_change) entry; 95 char * key; 96 void * data; 97 size_t dsiz; 98 int act; 99 }; 100 101 #define DBC_ADD 1 102 #define DBC_DEL 2 103 104 /* db pending changes list */ 105 SLIST_HEAD(, db_change) db_changes = SLIST_HEAD_INITIALIZER(db_changes); 106 107 struct mail_addr { 108 SLIST_ENTRY(mail_addr) entry; 109 char addr[MAX_MAIL]; 110 }; 111 112 /* list of suffixes that must match TO: */ 113 SLIST_HEAD(, mail_addr) match_suffix = SLIST_HEAD_INITIALIZER(match_suffix); 114 char *alloweddomains_file = PATH_SPAMD_ALLOWEDDOMAINS; 115 116 char *low_prio_mx_ip; 117 time_t startup; 118 119 static char *pargv[11]= { 120 "pfctl", "-p", "/dev/pf", "-q", "-t", 121 "spamd-white", "-T", "replace", "-f", "-", NULL 122 }; 123 124 /* If the parent gets a signal, kill off the children and exit */ 125 /* ARGSUSED */ 126 static void 127 sig_term_chld(int sig) 128 { 129 if (db_pid != -1) 130 kill(db_pid, SIGTERM); 131 if (jail_pid != -1) 132 kill(jail_pid, SIGTERM); 133 _exit(1); 134 } 135 136 /* 137 * Greatly simplified version from spamd_setup.c - only 138 * sends one blacklist to an already open stream. Has no need 139 * to collapse cidr ranges since these are only ever single 140 * host hits. 141 */ 142 void 143 configure_spamd(char **addrs, size_t count, FILE *sdc) 144 { 145 size_t i; 146 147 fprintf(sdc, "%s;", traplist_name); 148 if (count != 0) { 149 fprintf(sdc, "%s;", traplist_msg); 150 for (i = 0; i < count; i++) 151 fprintf(sdc, "%s/32;", addrs[i]); 152 } 153 fprintf(sdc, "\n"); 154 if (fflush(sdc) == EOF) 155 syslog_r(LOG_DEBUG, &sdata, "configure_spamd: fflush failed (%m)"); 156 } 157 158 159 /* Stolen from ftp-proxy */ 160 int 161 server_lookup(struct sockaddr *client, struct sockaddr *proxy, 162 struct sockaddr *server) 163 { 164 if (client->sa_family == AF_INET) 165 return (server_lookup4(satosin(client), satosin(proxy), 166 satosin(server))); 167 168 if (client->sa_family == AF_INET6) 169 return (server_lookup6(satosin6(client), satosin6(proxy), 170 satosin6(server))); 171 172 errno = EPROTONOSUPPORT; 173 return (-1); 174 } 175 176 int 177 server_lookup4(struct sockaddr_in *client, struct sockaddr_in *proxy, 178 struct sockaddr_in *server) 179 { 180 struct pfioc_natlook pnl; 181 182 memset(&pnl, 0, sizeof pnl); 183 pnl.direction = PF_OUT; 184 pnl.af = AF_INET; 185 pnl.proto = IPPROTO_TCP; 186 memcpy(&pnl.saddr.v4, &client->sin_addr.s_addr, sizeof pnl.saddr.v4); 187 memcpy(&pnl.daddr.v4, &proxy->sin_addr.s_addr, sizeof pnl.daddr.v4); 188 pnl.sport = client->sin_port; 189 pnl.dport = proxy->sin_port; 190 191 if (ioctl(pfdev, DIOCNATLOOK, &pnl) == -1) 192 return (-1); 193 194 memset(server, 0, sizeof(struct sockaddr_in)); 195 server->sin_len = sizeof(struct sockaddr_in); 196 server->sin_family = AF_INET; 197 memcpy(&server->sin_addr.s_addr, &pnl.rdaddr.v4, 198 sizeof server->sin_addr.s_addr); 199 server->sin_port = pnl.rdport; 200 201 return (0); 202 } 203 204 int 205 server_lookup6(struct sockaddr_in6 *client, struct sockaddr_in6 *proxy, 206 struct sockaddr_in6 *server) 207 { 208 struct pfioc_natlook pnl; 209 210 memset(&pnl, 0, sizeof pnl); 211 pnl.direction = PF_OUT; 212 pnl.af = AF_INET6; 213 pnl.proto = IPPROTO_TCP; 214 memcpy(&pnl.saddr.v6, &client->sin6_addr.s6_addr, sizeof pnl.saddr.v6); 215 memcpy(&pnl.daddr.v6, &proxy->sin6_addr.s6_addr, sizeof pnl.daddr.v6); 216 pnl.sport = client->sin6_port; 217 pnl.dport = proxy->sin6_port; 218 219 if (ioctl(pfdev, DIOCNATLOOK, &pnl) == -1) 220 return (-1); 221 222 memset(server, 0, sizeof(struct sockaddr_in6)); 223 server->sin6_len = sizeof(struct sockaddr_in6); 224 server->sin6_family = AF_INET6; 225 memcpy(&server->sin6_addr.s6_addr, &pnl.rdaddr.v6, 226 sizeof server->sin6_addr); 227 server->sin6_port = pnl.rdport; 228 229 return (0); 230 } 231 232 int 233 configure_pf(char **addrs, int count) 234 { 235 FILE *pf = NULL; 236 int i, pdes[2], status; 237 pid_t pid; 238 char *fdpath; 239 struct sigaction sa; 240 241 sigfillset(&sa.sa_mask); 242 sa.sa_flags = SA_RESTART; 243 sa.sa_handler = sig_term_chld; 244 245 if (debug) 246 fprintf(stderr, "configure_pf - device on fd %d\n", pfdev); 247 if (pfdev < 1 || pfdev > 63) 248 return(-1); 249 if (asprintf(&fdpath, "/dev/fd/%d", pfdev) == -1) 250 return(-1); 251 pargv[2] = fdpath; 252 if (pipe(pdes) != 0) { 253 syslog_r(LOG_INFO, &sdata, "pipe failed (%m)"); 254 free(fdpath); 255 fdpath = NULL; 256 return(-1); 257 } 258 signal(SIGCHLD, SIG_DFL); 259 switch (pid = fork()) { 260 case -1: 261 syslog_r(LOG_INFO, &sdata, "fork failed (%m)"); 262 free(fdpath); 263 fdpath = NULL; 264 close(pdes[0]); 265 close(pdes[1]); 266 sigaction(SIGCHLD, &sa, NULL); 267 return(-1); 268 case 0: 269 /* child */ 270 close(pdes[1]); 271 if (pdes[0] != STDIN_FILENO) { 272 dup2(pdes[0], STDIN_FILENO); 273 close(pdes[0]); 274 } 275 execvp(PATH_PFCTL, pargv); 276 syslog_r(LOG_ERR, &sdata, "can't exec %s:%m", PATH_PFCTL); 277 _exit(1); 278 } 279 280 /* parent */ 281 free(fdpath); 282 fdpath = NULL; 283 close(pdes[0]); 284 pf = fdopen(pdes[1], "w"); 285 if (pf == NULL) { 286 syslog_r(LOG_INFO, &sdata, "fdopen failed (%m)"); 287 close(pdes[1]); 288 sigaction(SIGCHLD, &sa, NULL); 289 return(-1); 290 } 291 for (i = 0; i < count; i++) 292 if (addrs[i] != NULL) 293 fprintf(pf, "%s/32\n", addrs[i]); 294 fclose(pf); 295 296 waitpid(pid, &status, 0); 297 if (WIFEXITED(status) && WEXITSTATUS(status) != 0) 298 syslog_r(LOG_ERR, &sdata, "%s returned status %d", PATH_PFCTL, 299 WEXITSTATUS(status)); 300 else if (WIFSIGNALED(status)) 301 syslog_r(LOG_ERR, &sdata, "%s died on signal %d", PATH_PFCTL, 302 WTERMSIG(status)); 303 304 sigaction(SIGCHLD, &sa, NULL); 305 return(0); 306 } 307 308 char * 309 dequotetolower(const char *addr) 310 { 311 static char buf[MAX_MAIL]; 312 char *cp; 313 314 if (*addr == '<') 315 addr++; 316 (void) strlcpy(buf, addr, sizeof(buf)); 317 cp = strrchr(buf, '>'); 318 if (cp != NULL && cp[1] == '\0') 319 *cp = '\0'; 320 cp = buf; 321 while (*cp != '\0') { 322 *cp = tolower((unsigned char)*cp); 323 cp++; 324 } 325 return(buf); 326 } 327 328 void 329 readsuffixlists(void) 330 { 331 FILE *fp; 332 char *buf; 333 size_t len; 334 struct mail_addr *m; 335 336 while (!SLIST_EMPTY(&match_suffix)) { 337 m = SLIST_FIRST(&match_suffix); 338 SLIST_REMOVE_HEAD(&match_suffix, entry); 339 free(m); 340 } 341 if ((fp = fopen(alloweddomains_file, "r")) != NULL) { 342 while ((buf = fgetln(fp, &len))) { 343 /* strip white space-characters */ 344 while (len > 0 && isspace((unsigned char)buf[len-1])) 345 len--; 346 while (len > 0 && isspace((unsigned char)*buf)) { 347 buf++; 348 len--; 349 } 350 if (len == 0) 351 continue; 352 /* jump over comments and blank lines */ 353 if (*buf == '#' || *buf == '\n') 354 continue; 355 if (buf[len-1] == '\n') 356 len--; 357 if ((len + 1) > sizeof(m->addr)) { 358 syslog_r(LOG_ERR, &sdata, 359 "line too long in %s - file ignored", 360 alloweddomains_file); 361 goto bad; 362 } 363 if ((m = malloc(sizeof(struct mail_addr))) == NULL) 364 goto bad; 365 memcpy(m->addr, buf, len); 366 m->addr[len]='\0'; 367 syslog_r(LOG_ERR, &sdata, "got suffix %s", m->addr); 368 SLIST_INSERT_HEAD(&match_suffix, m, entry); 369 } 370 } 371 return; 372 bad: 373 while (!SLIST_EMPTY(&match_suffix)) { 374 m = SLIST_FIRST(&match_suffix); 375 SLIST_REMOVE_HEAD(&match_suffix, entry); 376 free(m); 377 } 378 } 379 380 void 381 freeaddrlists(void) 382 { 383 int i; 384 385 if (whitelist != NULL) 386 for (i = 0; i < whitecount; i++) { 387 free(whitelist[i]); 388 whitelist[i] = NULL; 389 } 390 whitecount = 0; 391 if (traplist != NULL) { 392 for (i = 0; i < trapcount; i++) { 393 free(traplist[i]); 394 traplist[i] = NULL; 395 } 396 } 397 trapcount = 0; 398 } 399 400 /* validate, then add to list of addrs to whitelist */ 401 int 402 addwhiteaddr(char *addr) 403 { 404 struct addrinfo hints, *res; 405 406 memset(&hints, 0, sizeof(hints)); 407 hints.ai_family = AF_INET; /*for now*/ 408 hints.ai_socktype = SOCK_DGRAM; /*dummy*/ 409 hints.ai_protocol = IPPROTO_UDP; /*dummy*/ 410 hints.ai_flags = AI_NUMERICHOST; 411 412 if (getaddrinfo(addr, NULL, &hints, &res) == 0) { 413 if (whitecount == whitealloc) { 414 char **tmp; 415 416 tmp = realloc(whitelist, 417 (whitealloc + 1024) * sizeof(char *)); 418 if (tmp == NULL) { 419 freeaddrinfo(res); 420 return(-1); 421 } 422 whitelist = tmp; 423 whitealloc += 1024; 424 } 425 whitelist[whitecount] = strdup(addr); 426 if (whitelist[whitecount] == NULL) { 427 freeaddrinfo(res); 428 return(-1); 429 } 430 whitecount++; 431 freeaddrinfo(res); 432 } else 433 return(-1); 434 return(0); 435 } 436 437 /* validate, then add to list of addrs to traplist */ 438 int 439 addtrapaddr(char *addr) 440 { 441 struct addrinfo hints, *res; 442 443 memset(&hints, 0, sizeof(hints)); 444 hints.ai_family = AF_INET; /*for now*/ 445 hints.ai_socktype = SOCK_DGRAM; /*dummy*/ 446 hints.ai_protocol = IPPROTO_UDP; /*dummy*/ 447 hints.ai_flags = AI_NUMERICHOST; 448 449 if (getaddrinfo(addr, NULL, &hints, &res) == 0) { 450 if (trapcount == trapalloc) { 451 char **tmp; 452 453 tmp = realloc(traplist, 454 (trapalloc + 1024) * sizeof(char *)); 455 if (tmp == NULL) { 456 freeaddrinfo(res); 457 return(-1); 458 } 459 traplist = tmp; 460 trapalloc += 1024; 461 } 462 traplist[trapcount] = strdup(addr); 463 if (traplist[trapcount] == NULL) { 464 freeaddrinfo(res); 465 return(-1); 466 } 467 trapcount++; 468 freeaddrinfo(res); 469 } else 470 return(-1); 471 return(0); 472 } 473 474 static int 475 queue_change(char *key, char *data, size_t dsiz, int act) 476 { 477 struct db_change *dbc; 478 479 if ((dbc = malloc(sizeof(*dbc))) == NULL) { 480 syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)"); 481 return(-1); 482 } 483 if ((dbc->key = strdup(key)) == NULL) { 484 syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)"); 485 free(dbc); 486 return(-1); 487 } 488 if ((dbc->data = malloc(dsiz)) == NULL) { 489 syslog_r(LOG_DEBUG, &sdata, "malloc failed (queue change)"); 490 free(dbc->key); 491 free(dbc); 492 return(-1); 493 } 494 memcpy(dbc->data, data, dsiz); 495 dbc->dsiz = dsiz; 496 dbc->act = act; 497 syslog_r(LOG_DEBUG, &sdata, 498 "queueing %s of %s", ((act == DBC_ADD) ? "add" : "deletion"), 499 dbc->key); 500 SLIST_INSERT_HEAD(&db_changes, dbc, entry); 501 return(0); 502 } 503 504 static int 505 do_changes(DB *db) 506 { 507 DBT dbk, dbd; 508 struct db_change *dbc; 509 int ret = 0; 510 511 while (!SLIST_EMPTY(&db_changes)) { 512 dbc = SLIST_FIRST(&db_changes); 513 switch (dbc->act) { 514 case DBC_ADD: 515 memset(&dbk, 0, sizeof(dbk)); 516 dbk.size = strlen(dbc->key); 517 dbk.data = dbc->key; 518 memset(&dbd, 0, sizeof(dbd)); 519 dbd.size = dbc->dsiz; 520 dbd.data = dbc->data; 521 if (db->put(db, &dbk, &dbd, 0)) { 522 db->sync(db, 0); 523 syslog_r(LOG_ERR, &sdata, 524 "can't add %s to spamd db (%m)", dbc->key); 525 ret = -1; 526 } 527 db->sync(db, 0); 528 break; 529 case DBC_DEL: 530 memset(&dbk, 0, sizeof(dbk)); 531 dbk.size = strlen(dbc->key); 532 dbk.data = dbc->key; 533 if (db->del(db, &dbk, 0)) { 534 syslog_r(LOG_ERR, &sdata, 535 "can't delete %s from spamd db (%m)", 536 dbc->key); 537 ret = -1; 538 } 539 break; 540 default: 541 syslog_r(LOG_ERR, &sdata, "Unrecognized db change"); 542 ret = -1; 543 } 544 free(dbc->key); 545 dbc->key = NULL; 546 free(dbc->data); 547 dbc->data = NULL; 548 dbc->act = 0; 549 dbc->dsiz = 0; 550 SLIST_REMOVE_HEAD(&db_changes, entry); 551 free(dbc); 552 553 } 554 return(ret); 555 } 556 557 /* -1=error, 0=notfound, 1=TRAPPED, 2=WHITE */ 558 int 559 db_addrstate(DB *db, char *key) 560 { 561 DBT dbk, dbd; 562 struct gdata gd; 563 564 memset(&dbk, 0, sizeof(dbk)); 565 dbk.size = strlen(key); 566 dbk.data = key; 567 memset(&dbd, 0, sizeof(dbd)); 568 switch (db->get(db, &dbk, &dbd, 0)) { 569 case 1: 570 /* not found */ 571 return (0); 572 case 0: 573 if (gdcopyin(&dbd, &gd) != -1) 574 return (gd.pcount == -1 ? 1 : 2); 575 /* FALLTHROUGH */ 576 default: 577 /* error */ 578 return (-1); 579 } 580 } 581 582 583 int 584 greyscan(char *dbname) 585 { 586 HASHINFO hashinfo; 587 DBT dbk, dbd; 588 DB *db; 589 struct gdata gd; 590 int r; 591 char *a = NULL; 592 size_t asiz = 0; 593 time_t now = time(NULL); 594 595 /* walk db, expire, and whitelist */ 596 memset(&hashinfo, 0, sizeof(hashinfo)); 597 db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo); 598 if (db == NULL) { 599 syslog_r(LOG_INFO, &sdata, "dbopen failed (%m)"); 600 return(-1); 601 } 602 memset(&dbk, 0, sizeof(dbk)); 603 memset(&dbd, 0, sizeof(dbd)); 604 for (r = db->seq(db, &dbk, &dbd, R_FIRST); !r; 605 r = db->seq(db, &dbk, &dbd, R_NEXT)) { 606 if ((dbk.size < 1) || gdcopyin(&dbd, &gd) == -1) { 607 syslog_r(LOG_ERR, &sdata, "bogus entry in spamd database"); 608 goto bad; 609 } 610 if (asiz < dbk.size + 1) { 611 char *tmp; 612 613 tmp = realloc(a, dbk.size * 2); 614 if (tmp == NULL) 615 goto bad; 616 a = tmp; 617 asiz = dbk.size * 2; 618 } 619 memset(a, 0, asiz); 620 memcpy(a, dbk.data, dbk.size); 621 if (gd.expire <= now && gd.pcount != -2) { 622 /* get rid of entry */ 623 if (queue_change(a, NULL, 0, DBC_DEL) == -1) 624 goto bad; 625 } else if (gd.pcount == -1) { 626 /* this is a greytrap hit */ 627 if ((addtrapaddr(a) == -1) && 628 (queue_change(a, NULL, 0, DBC_DEL) == -1)) 629 goto bad; 630 } else if (gd.pcount >= 0 && gd.pass <= now) { 631 int tuple = 0; 632 char *cp; 633 int state; 634 635 /* 636 * if not already TRAPPED, 637 * add address to whitelist 638 * add an address-keyed entry to db 639 */ 640 cp = strchr(a, '\n'); 641 if (cp != NULL) { 642 tuple = 1; 643 *cp = '\0'; 644 } 645 646 state = db_addrstate(db, a); 647 if (state != 1 && addwhiteaddr(a) == -1) { 648 if (cp != NULL) 649 *cp = '\n'; 650 if (queue_change(a, NULL, 0, DBC_DEL) == -1) 651 goto bad; 652 } 653 654 if (tuple && state <= 0) { 655 if (cp != NULL) 656 *cp = '\0'; 657 /* re-add entry, keyed only by ip */ 658 gd.expire = now + whiteexp; 659 dbd.size = sizeof(gd); 660 dbd.data = &gd; 661 if (queue_change(a, (void *) &gd, sizeof(gd), 662 DBC_ADD) == -1) 663 goto bad; 664 syslog_r(LOG_DEBUG, &sdata, 665 "whitelisting %s in %s", a, dbname); 666 } 667 if (debug) 668 fprintf(stderr, "whitelisted %s\n", a); 669 } 670 } 671 (void) do_changes(db); 672 db->close(db); 673 db = NULL; 674 configure_pf(whitelist, whitecount); 675 configure_spamd(traplist, trapcount, trapcfg); 676 677 freeaddrlists(); 678 free(a); 679 a = NULL; 680 asiz = 0; 681 return(0); 682 bad: 683 (void) do_changes(db); 684 db->close(db); 685 db = NULL; 686 freeaddrlists(); 687 free(a); 688 a = NULL; 689 asiz = 0; 690 return(-1); 691 } 692 693 int 694 trapcheck(DB *db, char *to) 695 { 696 int i, j, smatch = 0; 697 DBT dbk, dbd; 698 struct mail_addr *m; 699 char * trap; 700 size_t s; 701 702 trap = dequotetolower(to); 703 if (!SLIST_EMPTY(&match_suffix)) { 704 s = strlen(trap); 705 SLIST_FOREACH(m, &match_suffix, entry) { 706 j = s - strlen(m->addr); 707 if ((j >= 0) && (strcasecmp(trap+j, m->addr) == 0)) 708 smatch = 1; 709 } 710 if (!smatch) 711 /* no suffixes match, so trap it */ 712 return (0); 713 } 714 memset(&dbk, 0, sizeof(dbk)); 715 dbk.size = strlen(trap); 716 dbk.data = trap; 717 memset(&dbd, 0, sizeof(dbd)); 718 i = db->get(db, &dbk, &dbd, 0); 719 if (i == -1) 720 return (-1); 721 if (i) 722 /* didn't exist - so this doesn't match a known spamtrap */ 723 return (1); 724 else 725 /* To: address is a spamtrap, so add as a greytrap entry */ 726 return (0); 727 } 728 729 int 730 twupdate(char *dbname, char *what, char *ip, char *source, char *expires) 731 { 732 /* we got a TRAP or WHITE update from someone else */ 733 HASHINFO hashinfo; 734 DBT dbk, dbd; 735 DB *db; 736 struct gdata gd; 737 time_t now, expire; 738 int r, spamtrap; 739 740 now = time(NULL); 741 /* expiry times have to be in the future */ 742 expire = strtonum(expires, now, 743 sizeof(time_t) == sizeof(int) ? INT_MAX : LLONG_MAX, NULL); 744 if (expire == 0) 745 return(-1); 746 747 if (strcmp(what, "TRAP") == 0) 748 spamtrap = 1; 749 else if (strcmp(what, "WHITE") == 0) 750 spamtrap = 0; 751 else 752 return(-1); 753 754 memset(&hashinfo, 0, sizeof(hashinfo)); 755 db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo); 756 if (db == NULL) 757 return(-1); 758 759 memset(&dbk, 0, sizeof(dbk)); 760 dbk.size = strlen(ip); 761 dbk.data = ip; 762 memset(&dbd, 0, sizeof(dbd)); 763 r = db->get(db, &dbk, &dbd, 0); 764 if (r == -1) 765 goto bad; 766 if (r) { 767 /* new entry */ 768 memset(&gd, 0, sizeof(gd)); 769 gd.first = now; 770 gd.pcount = spamtrap ? -1 : 0; 771 gd.expire = expire; 772 memset(&dbk, 0, sizeof(dbk)); 773 dbk.size = strlen(ip); 774 dbk.data = ip; 775 memset(&dbd, 0, sizeof(dbd)); 776 dbd.size = sizeof(gd); 777 dbd.data = &gd; 778 r = db->put(db, &dbk, &dbd, 0); 779 db->sync(db, 0); 780 if (r) 781 goto bad; 782 if (debug) 783 fprintf(stderr, "added %s %s\n", 784 spamtrap ? "trap entry for" : "", ip); 785 syslog_r(LOG_DEBUG, &sdata, 786 "new %s from %s for %s, expires %s", what, source, ip, 787 expires); 788 } else { 789 /* existing entry */ 790 if (gdcopyin(&dbd, &gd) == -1) { 791 /* whatever this is, it doesn't belong */ 792 db->del(db, &dbk, 0); 793 db->sync(db, 0); 794 goto bad; 795 } 796 if (spamtrap) { 797 gd.pcount = -1; 798 gd.bcount++; 799 } else 800 gd.pcount++; 801 memset(&dbk, 0, sizeof(dbk)); 802 dbk.size = strlen(ip); 803 dbk.data = ip; 804 memset(&dbd, 0, sizeof(dbd)); 805 dbd.size = sizeof(gd); 806 dbd.data = &gd; 807 r = db->put(db, &dbk, &dbd, 0); 808 db->sync(db, 0); 809 if (r) 810 goto bad; 811 if (debug) 812 fprintf(stderr, "updated %s\n", ip); 813 } 814 db->close(db); 815 return(0); 816 bad: 817 db->close(db); 818 return(-1); 819 820 } 821 822 int 823 greyupdate(char *dbname, char *helo, char *ip, char *from, char *to, int sync, 824 char *cip) 825 { 826 HASHINFO hashinfo; 827 DBT dbk, dbd; 828 DB *db; 829 char *key = NULL; 830 char *lookup; 831 struct gdata gd; 832 time_t now, expire; 833 int r, spamtrap; 834 835 now = time(NULL); 836 837 /* open with lock, find record, update, close, unlock */ 838 memset(&hashinfo, 0, sizeof(hashinfo)); 839 db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo); 840 if (db == NULL) 841 return(-1); 842 if (asprintf(&key, "%s\n%s\n%s\n%s", ip, helo, from, to) == -1) 843 goto bad; 844 r = trapcheck(db, to); 845 switch (r) { 846 case 1: 847 /* do not trap */ 848 spamtrap = 0; 849 lookup = key; 850 expire = greyexp; 851 break; 852 case 0: 853 /* trap */ 854 spamtrap = 1; 855 lookup = ip; 856 expire = trapexp; 857 syslog_r(LOG_DEBUG, &sdata, "Trapping %s for tuple %s", ip, 858 key); 859 break; 860 default: 861 goto bad; 862 break; 863 } 864 memset(&dbk, 0, sizeof(dbk)); 865 dbk.size = strlen(lookup); 866 dbk.data = lookup; 867 memset(&dbd, 0, sizeof(dbd)); 868 r = db->get(db, &dbk, &dbd, 0); 869 if (r == -1) 870 goto bad; 871 if (r) { 872 /* new entry */ 873 if (sync && low_prio_mx_ip && 874 (strcmp(cip, low_prio_mx_ip) == 0) && 875 ((startup + 60) < now)) { 876 /* we haven't seen a greylist entry for this tuple, 877 * and yet the connection was to a low priority MX 878 * which we know can't be hit first if the client 879 * is adhering to the RFC's - soo.. kill it! 880 */ 881 spamtrap = 1; 882 lookup = ip; 883 expire = trapexp; 884 syslog_r(LOG_DEBUG, &sdata, 885 "Trapping %s for trying %s first for tuple %s", 886 ip, low_prio_mx_ip, key); 887 } 888 memset(&gd, 0, sizeof(gd)); 889 gd.first = now; 890 gd.bcount = 1; 891 gd.pcount = spamtrap ? -1 : 0; 892 gd.pass = now + expire; 893 gd.expire = now + expire; 894 memset(&dbk, 0, sizeof(dbk)); 895 dbk.size = strlen(lookup); 896 dbk.data = lookup; 897 memset(&dbd, 0, sizeof(dbd)); 898 dbd.size = sizeof(gd); 899 dbd.data = &gd; 900 r = db->put(db, &dbk, &dbd, 0); 901 db->sync(db, 0); 902 if (r) 903 goto bad; 904 if (debug) 905 fprintf(stderr, "added %s %s\n", 906 spamtrap ? "greytrap entry for" : "", lookup); 907 syslog_r(LOG_DEBUG, &sdata, 908 "new %sentry %s from %s to %s, helo %s", 909 spamtrap ? "greytrap " : "", ip, from, to, helo); 910 } else { 911 /* existing entry */ 912 if (gdcopyin(&dbd, &gd) == -1) { 913 /* whatever this is, it doesn't belong */ 914 db->del(db, &dbk, 0); 915 db->sync(db, 0); 916 goto bad; 917 } 918 gd.bcount++; 919 gd.pcount = spamtrap ? -1 : 0; 920 if (gd.first + passtime < now) 921 gd.pass = now; 922 memset(&dbk, 0, sizeof(dbk)); 923 dbk.size = strlen(lookup); 924 dbk.data = lookup; 925 memset(&dbd, 0, sizeof(dbd)); 926 dbd.size = sizeof(gd); 927 dbd.data = &gd; 928 r = db->put(db, &dbk, &dbd, 0); 929 db->sync(db, 0); 930 if (r) 931 goto bad; 932 if (debug) 933 fprintf(stderr, "updated %s\n", lookup); 934 } 935 free(key); 936 key = NULL; 937 db->close(db); 938 db = NULL; 939 940 /* Entry successfully update, sent out sync message */ 941 if (syncsend && sync) { 942 if (spamtrap) { 943 syslog_r(LOG_DEBUG, &sdata, 944 "sync_trap %s", ip); 945 sync_trapped(now, now + expire, ip); 946 } 947 else 948 sync_update(now, helo, ip, from, to); 949 } 950 return(0); 951 bad: 952 free(key); 953 key = NULL; 954 db->close(db); 955 db = NULL; 956 return(-1); 957 } 958 959 int 960 twread(char *buf) 961 { 962 if ((strncmp(buf, "WHITE:", 6) == 0) || 963 (strncmp(buf, "TRAP:", 5) == 0)) { 964 char **ap, *argv[5]; 965 int argc = 0; 966 967 for (ap = argv; 968 ap < &argv[4] && (*ap = strsep(&buf, ":")) != NULL;) { 969 if (**ap != '\0') 970 ap++; 971 argc++; 972 } 973 *ap = NULL; 974 if (argc != 4) 975 return (-1); 976 twupdate(PATH_SPAMD_DB, argv[0], argv[1], argv[2], argv[3]); 977 return (0); 978 } else 979 return (-1); 980 } 981 982 int 983 greyreader(void) 984 { 985 char cip[32], ip[32], helo[MAX_MAIL], from[MAX_MAIL], to[MAX_MAIL]; 986 char *buf; 987 size_t len; 988 int state, sync; 989 struct addrinfo hints, *res; 990 991 memset(&hints, 0, sizeof(hints)); 992 hints.ai_family = AF_INET; /*for now*/ 993 hints.ai_socktype = SOCK_DGRAM; /*dummy*/ 994 hints.ai_protocol = IPPROTO_UDP; /*dummy*/ 995 hints.ai_flags = AI_NUMERICHOST; 996 997 state = 0; 998 sync = 1; 999 if (grey == NULL) { 1000 syslog_r(LOG_ERR, &sdata, "No greylist pipe stream!\n"); 1001 return (-1); 1002 } 1003 1004 /* grab trap suffixes */ 1005 readsuffixlists(); 1006 1007 while ((buf = fgetln(grey, &len))) { 1008 if (buf[len - 1] == '\n') 1009 buf[len - 1] = '\0'; 1010 else 1011 /* all valid lines end in \n */ 1012 continue; 1013 if (strlen(buf) < 4) 1014 continue; 1015 1016 if (strcmp(buf, "SYNC") == 0) { 1017 sync = 0; 1018 continue; 1019 } 1020 1021 switch (state) { 1022 case 0: 1023 if (twread(buf) == 0) { 1024 state = 0; 1025 break; 1026 } 1027 if (strncmp(buf, "HE:", 3) != 0) { 1028 if (strncmp(buf, "CO:", 3) == 0) 1029 strlcpy(cip, buf+3, sizeof(cip)); 1030 state = 0; 1031 break; 1032 } 1033 strlcpy(helo, buf+3, sizeof(helo)); 1034 state = 1; 1035 break; 1036 case 1: 1037 if (strncmp(buf, "IP:", 3) != 0) 1038 break; 1039 strlcpy(ip, buf+3, sizeof(ip)); 1040 if (getaddrinfo(ip, NULL, &hints, &res) == 0) { 1041 freeaddrinfo(res); 1042 state = 2; 1043 } else 1044 state = 0; 1045 break; 1046 case 2: 1047 if (strncmp(buf, "FR:", 3) != 0) { 1048 state = 0; 1049 break; 1050 } 1051 strlcpy(from, buf+3, sizeof(from)); 1052 state = 3; 1053 break; 1054 case 3: 1055 if (strncmp(buf, "TO:", 3) != 0) { 1056 state = 0; 1057 break; 1058 } 1059 strlcpy(to, buf+3, sizeof(to)); 1060 if (debug) 1061 fprintf(stderr, 1062 "Got Grey HELO %s, IP %s from %s to %s\n", 1063 helo, ip, from, to); 1064 greyupdate(PATH_SPAMD_DB, helo, ip, from, to, sync, cip); 1065 sync = 1; 1066 state = 0; 1067 break; 1068 } 1069 } 1070 return (0); 1071 } 1072 1073 void 1074 greyscanner(void) 1075 { 1076 for (;;) { 1077 if (greyscan(PATH_SPAMD_DB) == -1) 1078 syslog_r(LOG_NOTICE, &sdata, "scan of %s failed", 1079 PATH_SPAMD_DB); 1080 sleep(DB_SCAN_INTERVAL); 1081 } 1082 /* NOTREACHED */ 1083 } 1084 1085 static void 1086 drop_privs(void) 1087 { 1088 /* 1089 * lose root, continue as non-root user 1090 */ 1091 if (pw) { 1092 setgroups(1, &pw->pw_gid); 1093 setegid(pw->pw_gid); 1094 setgid(pw->pw_gid); 1095 seteuid(pw->pw_uid); 1096 setuid(pw->pw_uid); 1097 } 1098 } 1099 1100 static void 1101 check_spamd_db(void) 1102 { 1103 HASHINFO hashinfo; 1104 int i = -1; 1105 DB *db; 1106 1107 /* check to see if /var/db/spamd exists, if not, create it */ 1108 memset(&hashinfo, 0, sizeof(hashinfo)); 1109 db = dbopen(PATH_SPAMD_DB, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo); 1110 1111 if (db == NULL) { 1112 switch (errno) { 1113 case ENOENT: 1114 i = open(PATH_SPAMD_DB, O_RDWR|O_CREAT, 0644); 1115 if (i == -1) { 1116 syslog_r(LOG_ERR, &sdata, 1117 "create %s failed (%m)", PATH_SPAMD_DB); 1118 exit(1); 1119 } 1120 /* if we are dropping privs, chown to that user */ 1121 if (pw && (fchown(i, pw->pw_uid, pw->pw_gid) == -1)) { 1122 syslog_r(LOG_ERR, &sdata, 1123 "chown %s failed (%m)", PATH_SPAMD_DB); 1124 exit(1); 1125 } 1126 close(i); 1127 drop_privs(); 1128 return; 1129 break; 1130 default: 1131 syslog_r(LOG_ERR, &sdata, "open of %s failed (%m)", 1132 PATH_SPAMD_DB); 1133 exit(1); 1134 } 1135 } 1136 db->sync(db, 0); 1137 db->close(db); 1138 drop_privs(); 1139 } 1140 1141 1142 int 1143 greywatcher(void) 1144 { 1145 struct sigaction sa; 1146 1147 check_spamd_db(); 1148 1149 startup = time(NULL); 1150 db_pid = fork(); 1151 switch (db_pid) { 1152 case -1: 1153 syslog_r(LOG_ERR, &sdata, "fork failed (%m)"); 1154 exit(1); 1155 case 0: 1156 /* 1157 * child, talks to jailed spamd over greypipe, 1158 * updates db. has no access to pf. 1159 */ 1160 close(pfdev); 1161 setproctitle("(%s update)", PATH_SPAMD_DB); 1162 if (greyreader() == -1) { 1163 syslog_r(LOG_ERR, &sdata, "greyreader failed (%m)"); 1164 _exit(1); 1165 } 1166 _exit(0); 1167 } 1168 1169 1170 fclose(grey); 1171 /* 1172 * parent, scans db periodically for changes and updates 1173 * pf whitelist table accordingly. 1174 */ 1175 1176 sigfillset(&sa.sa_mask); 1177 sa.sa_flags = SA_RESTART; 1178 sa.sa_handler = sig_term_chld; 1179 sigaction(SIGTERM, &sa, NULL); 1180 sigaction(SIGHUP, &sa, NULL); 1181 sigaction(SIGCHLD, &sa, NULL); 1182 sigaction(SIGINT, &sa, NULL); 1183 1184 setproctitle("(pf <spamd-white> update)"); 1185 greyscanner(); 1186 /* NOTREACHED */ 1187 exit(1); 1188 } 1189