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