1/* This file is part of Mailfromd. -*- c -*- 2 Copyright (C) 2006-2021 Sergey Poznyakoff 3 4 This program is free software; you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation; either version 3, or (at your option) 7 any later version. 8 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 16 17MF_BUILTIN_MODULE 18#define DEFAULT_DB_MODE 0640 19#include <fnmatch.h> 20 21struct db_prop { /* Database properties */ 22 char *pat; /* Database name pattern */ 23 mode_t mode; /* File mode */ 24 int null; /* Null byte */ 25 mu_url_t hint; /* Hint to use instead of the default one */ 26}; 27 28static mu_list_t db_prop_list; 29 30static int 31db_prop_compare(const void *item, const void *data) 32{ 33 const struct db_prop *prop = item; 34 const char *name = data; 35 return strcmp(prop->pat, name); 36} 37 38static int 39strtomode(char *str, mode_t *pmode) 40{ 41 mode_t mode = 0; 42 struct { char c; unsigned mask; } modetab[] = { 43 { 'r', S_IRUSR }, 44 { 'w', S_IWUSR }, 45 { 'x', S_IXUSR }, 46 { 'r', S_IRGRP }, 47 { 'w', S_IWGRP }, 48 { 'x', S_IXGRP }, 49 { 'r', S_IROTH }, 50 { 'w', S_IWOTH }, 51 { 'x', S_IXOTH }, 52 { 0 } 53 }; 54 int i; 55 56 for (i = 0; modetab[i].c; i++) { 57 if (!str[i]) 58 return i + 1; 59 if (str[i] == modetab[i].c) 60 mode |= modetab[i].mask; 61 else if (str[i] != '-') 62 return i + 1; 63 } 64 if (str[i]) 65 return i + 1; 66 *pmode = mode; 67 return 0; 68} 69 70static int 71is_url(const char *p) 72{ 73 for (; *p && mu_isalnum(*p); p++) 74 ; 75 return *p == ':'; 76} 77 78/* #pragma dbprop <name> [null] [mode] [hint] 79 At least one of the bracketed parameters must be present. Two or more 80 parameters can be given in arbitrary order. 81 */ 82MF_PRAGMA(dbprop, 3, 5) 83{ 84 int null = 0; 85 mode_t mode = DEFAULT_DB_MODE; 86 struct db_prop *prop; 87 int rc; 88 char *pat; 89 mu_url_t hint = NULL; 90 91 --argc; 92 pat = *++argv; 93 94 while (--argc) { 95 char *p = *++argv; 96 97 if (strcmp(p, "null") == 0) 98 null = 1; 99 else if (mu_isdigit(*p)) { 100 unsigned long n = strtoul(p, &p, 8); 101 if (*p || (mode = n) != n) { 102 parse_error(_("bad numeric file mode")); 103 return; 104 } 105 } else if (is_url(p)) { 106 rc = mu_url_create(&hint, p); 107 if (rc) { 108 parse_error(_("not a valid URL: %s"), 109 mu_strerror(rc)); 110 return; 111 } 112 } else if (rc = strtomode(p, &mode)) { 113 parse_error(_("bad symbolic file mode (near %s)"), 114 p + rc - 1); 115 return; 116 } 117 } 118 119 if (!db_prop_list) { 120 rc = mu_list_create(&db_prop_list); 121 if (rc) { 122 parse_error(_("cannot create list: %s"), 123 mu_strerror(rc)); 124 return; 125 } 126 mu_list_set_comparator(db_prop_list, db_prop_compare); 127 } 128 129 if (mu_list_locate(db_prop_list, pat, (void**) &prop)) { 130 prop = mu_alloc(sizeof(*prop)); 131 prop->pat = mu_strdup(pat); 132 rc = mu_list_append(db_prop_list, prop); 133 if (rc) { 134 parse_error(_("Cannot create list: %s"), 135 mu_strerror(rc)); 136 return; 137 } 138 } 139 prop->mode = mode; 140 prop->null = null; 141 prop->hint = hint; 142} 143 144const struct db_prop * 145db_prop_lookup(const char *name) 146{ 147 mu_iterator_t itr; 148 const struct db_prop *found = NULL; 149 150 if (db_prop_list 151 && mu_list_get_iterator(db_prop_list, &itr) == 0) { 152 for (mu_iterator_first(itr); 153 !mu_iterator_is_done(itr); 154 mu_iterator_next(itr)) { 155 const struct db_prop *p; 156 mu_iterator_current(itr, (void**)&p); 157 if (strcmp(p->pat, name) == 0) { 158 found = p; 159 break; 160 } else if (fnmatch(p->pat, name, 0) == 0) 161 found = p; 162 } 163 mu_iterator_destroy(&itr); 164 } 165 return found; 166} 167 168int 169_open_dbm(mu_dbm_file_t *pdb, char *dbname, int access, int mode, 170 mu_url_t hint) 171{ 172 mu_dbm_file_t db; 173 int rc; 174 mu_url_t url; 175 176 if (!hint) 177 hint = mu_dbm_get_hint(); 178 rc = mu_url_create_hint(&url, dbname, 0, hint); 179 if (rc) { 180 mu_error(_("cannot create database URL for %s: %s"), 181 dbname, mu_strerror(rc)); 182 return rc; 183 } 184 rc = mu_dbm_create_from_url(url, &db, 185 mf_file_mode_to_safety_criteria(mode)); 186 mu_url_destroy(&url); 187 if (rc) 188 return rc; 189 190 rc = mu_dbm_safety_check(db); 191 if (rc && rc != ENOENT) { 192 mu_error(_("%s fails safety check: %s"), 193 dbname, mu_strerror(rc)); 194 mu_dbm_destroy(&db); 195 return rc; 196 } 197 198 rc = mu_dbm_open(db, access, mode); 199 if (rc) { 200 mu_dbm_destroy(&db); 201 return rc; 202 } 203 *pdb = db; 204 return rc; 205} 206 207 208#define LOOKUP_NULL_BYTE 0x1 209#define LOOKUP_TEST_ONLY 0x2 210 211MF_DSEXP_SUPPRESS([<dbmap_lookup>],[< 212static int 213dbmap_lookup(eval_environ_t env, char *dbname, const char *keystr, 214 const char *defval, const struct db_prop *prop, int flags) 215{ 216 int rc; 217 mu_dbm_file_t db; 218 struct mu_dbm_datum key; 219 struct mu_dbm_datum contents; 220 221 if (!defval) 222 defval = ""; 223 rc = _open_dbm(&db, dbname, MU_STREAM_READ, 224 prop ? prop->mode : DEFAULT_DB_MODE, 225 prop ? prop->hint : NULL); 226 if (rc) 227 MF_THROW(mfe_dbfailure, 228 _("mf_dbm_open(%s) failed: %s"), 229 dbname, 230 mu_strerror(rc)); 231 232 memset(&key, 0, sizeof key); 233 memset(&contents, 0, sizeof contents); 234 key.mu_dptr = (void*) keystr; 235 key.mu_dsize = strlen(keystr); 236 if (flags & LOOKUP_NULL_BYTE) 237 key.mu_dsize++; 238 rc = mu_dbm_fetch(db, &key, &contents); 239 if (rc && rc != MU_ERR_NOENT) { 240 mu_dbm_destroy(&db); 241 MF_THROW(mfe_dbfailure, 242 _("cannot fetch data: %s"), 243 mu_dbm_strerror(db)); 244 } 245 MF_DEBUG(MU_DEBUG_TRACE5, 246 ("Looking up %s: %s", keystr, rc ? "false" : "true")); 247 if (flags & LOOKUP_TEST_ONLY) 248 push(env, (STKVAL) (rc == 0)); 249 else { 250 if (rc) { 251 if (defval) 252 pushs(env, defval); 253 else 254 push(env, (STKVAL) 0L); 255 } else if (((char*)contents.mu_dptr)[contents.mu_dsize-1]) { 256 size_t off; 257 size_t len = contents.mu_dsize; 258 char *s = MF_ALLOC_HEAP(off, len + 1); 259 memcpy(s, contents.mu_dptr, len); 260 s[len] = 0; 261 push(env, (STKVAL) off); 262 } else 263 pushs(env, contents.mu_dptr); 264 } 265 mu_dbm_datum_free(&contents); 266 mu_dbm_destroy(&db); 267 return rc; 268} 269>]) 270 271MF_DSEXP 272MF_DEFUN(dbmap, NUMBER, STRING dbname, STRING key, OPTIONAL, NUMBER null) 273{ 274 const struct db_prop *prop = db_prop_lookup(dbname); 275 dbmap_lookup(env, dbname, key, NULL, 276 prop, 277 LOOKUP_TEST_ONLY | 278 (MF_OPTVAL(null, prop && prop->null) 279 ? LOOKUP_NULL_BYTE : 0)); 280} 281END 282 283MF_DEFUN(dbget, STRING, STRING dbname, STRING key, OPTIONAL, 284 STRING defval, NUMBER null) 285{ 286 const struct db_prop *prop = db_prop_lookup(dbname); 287 dbmap_lookup(env, dbname, key, 288 MF_OPTVAL(defval), 289 prop, 290 MF_OPTVAL(null, prop && prop->null) 291 ? LOOKUP_NULL_BYTE : 0); 292} 293END 294 295MF_DEFUN(dbput, VOID, STRING dbname, STRING keystr, STRING value, 296 OPTIONAL, NUMBER null, NUMBER mode) 297{ 298 int rc; 299 mu_dbm_file_t db; 300 struct mu_dbm_datum key; 301 struct mu_dbm_datum contents; 302 const struct db_prop *prop = db_prop_lookup(dbname); 303 304 rc = _open_dbm(&db, dbname, MU_STREAM_RDWR, 305 MF_OPTVAL(mode, 306 (prop ? prop->mode : DEFAULT_DB_MODE)), 307 prop ? prop->hint : NULL); 308 if (rc) 309 MF_THROW(mfe_dbfailure, 310 _("mf_dbm_open(%s) failed: %s"), 311 dbname, 312 mu_strerror(rc)); 313 memset(&key, 0, sizeof key); 314 key.mu_dptr = keystr; 315 key.mu_dsize = strlen(keystr); 316 if (MF_OPTVAL(null, prop && prop->null)) 317 key.mu_dsize++; 318 319 memset(&contents, 0, sizeof contents); 320 contents.mu_dptr = value; 321 contents.mu_dsize = strlen(value) + 1; 322 323 rc = mu_dbm_store(db, &key, &contents, 1); 324 if (rc) { 325 const char *errstr = mu_dbm_strerror(db); 326 mu_dbm_destroy(&db); 327 MF_THROW(mfe_dbfailure, 328 _("failed to insert data to %s: %s %s: %s"), 329 dbname, 330 keystr, 331 value, 332 errstr); 333 } 334 mu_dbm_destroy(&db); 335} 336END 337 338MF_DEFUN(dbinsert, VOID, STRING dbname, STRING keystr, STRING value, 339 OPTIONAL, NUMBER replace, NUMBER null, NUMBER mode) 340{ 341 int rc; 342 mu_dbm_file_t db; 343 struct mu_dbm_datum key; 344 struct mu_dbm_datum contents; 345 const struct db_prop *prop = db_prop_lookup(dbname); 346 const char *errstr; 347 348 rc = _open_dbm(&db, dbname, MU_STREAM_RDWR, 349 MF_OPTVAL(mode, 350 (prop ? prop->mode : DEFAULT_DB_MODE)), 351 prop ? prop->hint : NULL); 352 if (rc) 353 MF_THROW(mfe_dbfailure, 354 _("mf_dbm_open(%s) failed: %s"), 355 dbname, 356 mu_strerror(rc)); 357 memset(&key, 0, sizeof key); 358 key.mu_dptr = keystr; 359 key.mu_dsize = strlen(keystr); 360 if (MF_OPTVAL(null, prop && prop->null)) 361 key.mu_dsize++; 362 363 memset(&contents, 0, sizeof contents); 364 contents.mu_dptr = value; 365 contents.mu_dsize = strlen(value) + 1; 366 367 rc = mu_dbm_store(db, &key, &contents, MF_OPTVAL(replace)); 368 if (rc && rc != MU_ERR_EXISTS) 369 errstr = mu_dbm_strerror(db); 370 mu_dbm_destroy(&db); 371 if (rc == MU_ERR_EXISTS) 372 MF_THROW(mfe_exists, _("record already exists")); 373 374 MF_ASSERT(rc == 0, 375 mfe_dbfailure, 376 _("failed to insert data to %s: %s %s: %s"), 377 dbname, 378 keystr, 379 value, 380 errstr); 381} 382END 383 384MF_DEFUN(dbdel, VOID, STRING dbname, STRING keystr, OPTIONAL, NUMBER null, 385 NUMBER mode) 386{ 387 mu_dbm_file_t db; 388 struct mu_dbm_datum key; 389 int rc; 390 const struct db_prop *prop = db_prop_lookup(dbname); 391 392 rc = _open_dbm(&db, dbname, MU_STREAM_RDWR, 393 MF_OPTVAL(mode, 394 (prop ? prop->mode : DEFAULT_DB_MODE)), 395 prop ? prop->hint : NULL); 396 MF_ASSERT(rc == 0, 397 mfe_dbfailure, 398 _("mf_dbm_open(%s) failed: %s"), 399 dbname, 400 mu_strerror(rc)); 401 memset(&key, 0, sizeof key); 402 key.mu_dptr = keystr; 403 key.mu_dsize = strlen(keystr); 404 if (MF_OPTVAL(null, prop && prop->null)) 405 key.mu_dsize++; 406 rc = mu_dbm_delete(db, &key); 407 mu_dbm_destroy(&db); 408 MF_ASSERT(rc == 0 || rc == MU_ERR_NOENT, 409 mfe_dbfailure, 410 _("failed to delete data `%s' from `%s': %s"), 411 keystr, 412 dbname, 413 mu_strerror(rc)); 414} 415END 416 417 418#define NUMDB 128 419 420struct db_tab { 421 int used; 422 mu_dbm_file_t db; 423 struct mu_dbm_datum key; 424}; 425 426static void * 427alloc_db_tab() 428{ 429 return mu_calloc(NUMDB, sizeof(struct db_tab)); 430} 431 432static void 433close_db_tab(struct db_tab *dbt) 434{ 435 if (dbt->used) { 436 mu_dbm_datum_free(&dbt->key); 437 mu_dbm_destroy(&dbt->db); 438 dbt->used = 0; 439 } 440} 441 442static void 443destroy_db_tab(void *data) 444{ 445 int i; 446 struct db_tab *db = data; 447 for (i = 0; i < NUMDB; i++) 448 close_db_tab(db + i); 449 free(db); 450} 451 452MF_DECLARE_DATA(DBTAB, alloc_db_tab, destroy_db_tab); 453 454static int 455new_db_tab(struct db_tab *dbt) 456{ 457 int i; 458 for (i = 0; i < NUMDB; i++) 459 if (!dbt[i].used) { 460 dbt[i].used = 1; 461 return i; 462 } 463 return -1; 464} 465 466 467 468MF_DEFUN(dbfirst, NUMBER, STRING dbname) 469{ 470 int rc; 471 int n; 472 struct db_tab *dbt = MF_GET_DATA; 473 mu_dbm_file_t db; 474 struct mu_dbm_datum key; 475 const struct db_prop *prop = db_prop_lookup(dbname); 476 477 rc = _open_dbm(&db, dbname, MU_STREAM_READ, 478 prop ? prop->mode : DEFAULT_DB_MODE, 479 prop ? prop->hint : NULL); 480 MF_ASSERT(rc == 0, 481 mfe_dbfailure, 482 _("mf_dbm_open(%s) failed: %s"), 483 dbname, 484 mu_strerror(rc)); 485 memset(&key, 0, sizeof key); 486 rc = mu_dbm_firstkey(db, &key); 487 if (rc) { 488 if (rc == MU_ERR_NOENT) { 489 mu_dbm_destroy(&db); 490 MF_RETURN(0); 491 } else if (rc) { 492 mu_dbm_destroy(&db); 493 MF_THROW(mfe_dbfailure, 494 _("mf_dbm_firstkey failed: %s"), 495 mu_strerror(rc)); 496 } 497 } 498 n = new_db_tab(dbt); 499 MF_ASSERT(n >= 0, 500 mfe_failure, 501 _("no more database entries available")); 502 dbt += n; 503 dbt->db = db; 504 dbt->key = key; 505 MF_RETURN(n); 506} 507END 508 509MF_DEFUN(dbnext, NUMBER, NUMBER dn) 510{ 511 struct db_tab *dbt = MF_GET_DATA + dn; 512 struct mu_dbm_datum nextkey; 513 int rc; 514 515 MF_ASSERT(dn >= 0 && dn < NUMDB && dbt->used, 516 mfe_range, 517 _("invalid database descriptor")); 518 519 memset (&nextkey, 0, sizeof nextkey); 520 rc = mu_dbm_nextkey(dbt->db, &nextkey); 521 if (rc) { 522 if (rc == MU_ERR_FAILURE) 523 mu_error(_("mu_dbm_nextkey: %s"), 524 mu_dbm_strerror(dbt->db)); 525 close_db_tab(dbt); 526 MF_RETURN(0); 527 } 528 mu_dbm_datum_free(&nextkey); 529 dbt->key = nextkey; 530 MF_RETURN(1); 531} 532END 533 534MF_DEFUN(dbkey, STRING, NUMBER dn) 535{ 536 size_t off, len; 537 char *s; 538 struct db_tab *dbt = MF_GET_DATA + dn; 539 540 MF_ASSERT(dn >= 0 && dn < NUMDB && dbt->used, 541 mfe_range, 542 _("invalid database descriptor")); 543 544 len = dbt->key.mu_dsize; 545 s = MF_ALLOC_HEAP(off, len + 1); 546 memcpy(s, dbt->key.mu_dptr, len); 547 s[len] = 0; 548 MF_RETURN(off, size); 549} 550END 551 552MF_DEFUN(dbvalue, STRING, NUMBER dn) 553{ 554 int rc; 555 size_t off, len; 556 char *s; 557 struct db_tab *dbt = MF_GET_DATA + dn; 558 struct mu_dbm_datum contents; 559 560 MF_ASSERT(dn >= 0 && dn < NUMDB && dbt->used, 561 mfe_range, 562 _("invalid database descriptor")); 563 564 memset(&contents, 0, sizeof contents); 565 rc = mu_dbm_fetch(dbt->db, &dbt->key, &contents); 566 MF_ASSERT(rc == 0, 567 mfe_dbfailure, 568 _("cannot fetch key: %s"), 569 rc == MU_ERR_FAILURE ? 570 mu_dbm_strerror(dbt->db) : mu_strerror (rc)); 571 572 len = contents.mu_dsize; 573 s = MF_ALLOC_HEAP(off, len + 1); 574 memcpy(s, contents.mu_dptr, len); 575 s[len] = 0; 576 mu_dbm_datum_free(&contents); 577 MF_RETURN(off, size); 578} 579END 580 581 582enum greylist_semantics 583{ 584 greylist_traditional, 585 greylist_ct 586}; 587 588static enum greylist_semantics greylist_semantics = greylist_traditional; 589 590/* #pragma greylist {traditional|gray|ct|con-tassios}*/ 591MF_PRAGMA(greylist, 2, 2) 592{ 593 if (strcmp(argv[1], "traditional") == 0 594 || strcmp(argv[1], "gray") == 0) 595 greylist_semantics = greylist_traditional; 596 else if (strcmp(argv[1], "ct") == 0 597 || strcmp(argv[1], "con-tassios") == 0) 598 greylist_semantics = greylist_ct; 599 else 600 /* TRANSLATORS: Do not translate keywords: 601 traditional, gray, ct, con-tassios */ 602 parse_error(_("unknown semantics; allowed values are: " 603 "traditional (or gray) and " 604 "ct (or con-tassios)")); 605} 606 607/* FIXME: Duplicated in lib/cache.c */ 608static char * 609format_timestr(time_t timestamp, char *timebuf, size_t bufsize) 610{ 611 struct tm tm; 612 gmtime_r(×tamp, &tm); 613 strftime(timebuf, bufsize, "%c", &tm); 614 return timebuf; 615} 616 617MF_VAR(greylist_seconds_left, NUMBER); 618 619/* The traditional (aka gray's) greylist implementation: the greylist 620 database keeps the time the greylisting was activated. 621 */ 622static int 623do_greylist_traditional(eval_environ_t env, char *email, long interval) 624{ 625 int rc; 626 mu_dbm_file_t db; 627 struct mu_dbm_datum key; 628 struct mu_dbm_datum contents; 629 int readonly = 0; 630 time_t now; 631 632 rc = _open_dbm(&db, greylist_format->dbname, MU_STREAM_RDWR, 0600, 633 NULL); 634 if (rc) { 635 rc = _open_dbm(&db, greylist_format->dbname, 636 MU_STREAM_READ, 0600, NULL); 637 readonly = 1; 638 } 639 MF_ASSERT(rc == 0, mfe_dbfailure, _("mf_dbm_open(%s) failed: %s"), 640 greylist_format->dbname, mu_strerror(rc)); 641 642 memset(&key, 0, sizeof key); 643 memset(&contents, 0, sizeof contents); 644 key.mu_dptr = email; 645 key.mu_dsize = strlen(email)+1; 646 647 time(&now); 648 rc = mu_dbm_fetch(db, &key, &contents); 649 if (rc == 0) { 650 time_t timestamp, diff; 651 652 MF_ASSERT(contents.mu_dsize == sizeof timestamp, 653 mfe_dbfailure, 654 _("greylist database %s has wrong data size"), 655 greylist_format->dbname); 656 657 timestamp = *(time_t*) contents.mu_dptr; 658 diff = now - timestamp; 659 660 if (mu_debug_level_p(debug_handle, MU_DEBUG_TRACE5)) { 661 char timebuf[32]; 662 mu_debug_log("%s entered greylist database on %s, " 663 "%ld seconds ago", 664 email, 665 format_timestr(timestamp, timebuf, 666 sizeof timebuf), 667 (long) diff); 668 } 669 670 if (diff < interval) { 671 diff = interval - diff; 672 673 MF_VAR_REF(greylist_seconds_left, ulong, diff);//FIXME 674 675 MF_DEBUG(MU_DEBUG_TRACE6, 676 ("%s still greylisted (for %lu sec.)", 677 email, 678 (unsigned long) diff)); 679 rc = 1; 680 } else if (diff > greylist_format->expire_interval) { 681 MF_DEBUG(MU_DEBUG_TRACE6, 682 ("greylist record for %s expired", email)); 683 MF_VAR_REF(greylist_seconds_left, long, interval); 684 if (!readonly) { 685 memcpy(contents.mu_dptr, &now, sizeof now); 686 rc = mu_dbm_store(db, &key, &contents, 1); 687 if (rc) 688 mu_error(_("cannot insert datum `%-.*s' in " 689 "greylist database %s: %s"), 690 (int)key.mu_dsize, 691 (char*)key.mu_dptr, 692 greylist_format->dbname, 693 rc == MU_ERR_FAILURE ? 694 mu_dbm_strerror(db) : 695 mu_strerror(rc)); 696 } else 697 MF_DEBUG(MU_DEBUG_TRACE6, 698 ("database opened in readonly mode: " 699 "not updating")); 700 rc = 1; 701 } else { 702 MF_DEBUG(MU_DEBUG_TRACE6, 703 ("%s finished greylisting period", email)); 704 rc = 0; 705 } 706 mu_dbm_datum_free(&contents); 707 } else if (!readonly) { 708 MF_DEBUG(MU_DEBUG_TRACE6, ("greylisting %s", email)); 709 MF_VAR_REF(greylist_seconds_left, long, interval); 710 contents.mu_dptr = (void*)&now; 711 contents.mu_dsize = sizeof now; 712 rc = mu_dbm_store(db, &key, &contents, 1); 713 if (rc) 714 mu_error(_("Cannot insert datum `%-.*s' in greylist " 715 "database %s: %s"), 716 (int)key.mu_dsize, (char*)key.mu_dptr, 717 greylist_format->dbname, 718 rc == MU_ERR_FAILURE ? 719 mu_dbm_strerror(db) : mu_strerror(rc)); 720 rc = 1; 721 } else 722 rc = 0; 723 724 mu_dbm_destroy(&db); 725 726 return rc; 727} 728 729/* Implementation of the is_greylisted predicate has no sense for 730 traditional greylist databases, because greylisting interval is 731 not known beforehand. 732 FIXME: keep the reference below up to date. 733 */ 734static int 735is_greylisted_traditional(eval_environ_t env, char *email) 736{ 737 MF_THROW(mfe_failure, 738 _("is_greylisted is not implemented for traditional greylist databases; " 739 "see documentation, chapter %s %s for more info"), 740 "5.30", "Greylisting functions"); 741 return 0; 742} 743 744/* New greylist implementation (by Con Tassios): the database keeps 745 the time the greylisting period is set to expire (`interval' seconds 746 from now) 747*/ 748static int 749do_greylist_ct(eval_environ_t env, char *email, long interval) 750{ 751 int rc; 752 mu_dbm_file_t db; 753 struct mu_dbm_datum key; 754 struct mu_dbm_datum contents; 755 int readonly = 0; 756 time_t now; 757 758 rc = _open_dbm(&db, greylist_format->dbname, MU_STREAM_RDWR, 0600, 759 NULL); 760 if (rc) { 761 rc = _open_dbm(&db, greylist_format->dbname, 762 MU_STREAM_READ, 0600, NULL); 763 readonly = 1; 764 } 765 MF_ASSERT(rc == 0, mfe_dbfailure, _("mf_dbm_open(%s) failed: %s"), 766 greylist_format->dbname, mu_strerror(rc)); 767 768 memset(&key, 0, sizeof key); 769 memset(&contents, 0, sizeof contents); 770 key.mu_dptr = email; 771 key.mu_dsize = strlen(email) + 1; 772 773 time(&now); 774 rc = mu_dbm_fetch(db, &key, &contents); 775 if (rc == 0) { 776 time_t timestamp; 777 778 MF_ASSERT(contents.mu_dsize == sizeof timestamp, 779 mfe_dbfailure, 780 _("greylist database %s has wrong data size"), 781 greylist_format->dbname); 782 783 timestamp = *(time_t*) contents.mu_dptr; 784 785 if (now < timestamp) { 786 time_t diff = timestamp - now; 787 MF_VAR_REF(greylist_seconds_left, long, diff); 788 789 MF_DEBUG(MU_DEBUG_TRACE6, 790 ("%s still greylisted (for %lu sec.)", 791 email, 792 (unsigned long) diff)); 793 rc = 1; 794 } else if (now - timestamp > 795 greylist_format->expire_interval) { 796 MF_DEBUG(MU_DEBUG_TRACE6, 797 ("greylist record for %s expired", email)); 798 MF_VAR_REF(greylist_seconds_left, long, interval); 799 if (!readonly) { 800 now += interval; 801 memcpy(contents.mu_dptr, &now, sizeof now); 802 rc = mu_dbm_store(db, &key, &contents, 1); 803 if (rc) 804 mu_error(_("Cannot insert datum " 805 "`%-.*s' in greylist " 806 "database %s: %s"), 807 (int)key.mu_dsize, 808 (char*)key.mu_dptr, 809 greylist_format->dbname, 810 rc == MU_ERR_FAILURE ? 811 mu_dbm_strerror(db) : 812 mu_strerror(rc)); 813 } else 814 MF_DEBUG(MU_DEBUG_TRACE6, 815 ("database opened in readonly mode: " 816 "not updating")); 817 rc = 1; 818 } else { 819 MF_DEBUG(MU_DEBUG_TRACE6, 820 ("%s finished greylisting period", email)); 821 rc = 0; 822 } 823 mu_dbm_datum_free(&contents); 824 } else if (!readonly) { 825 MF_DEBUG(MU_DEBUG_TRACE6, ("greylisting %s", email)); 826 MF_VAR_REF(greylist_seconds_left, long, interval); 827 now += interval; 828 contents.mu_dptr = (void*)&now; 829 contents.mu_dsize = sizeof now; 830 rc = mu_dbm_store(db, &key, &contents, 1); 831 if (rc) 832 mu_error(_("Cannot insert datum `%-.*s' in greylist " 833 "database %s: %s"), 834 (int)key.mu_dsize, (char*)key.mu_dptr, 835 greylist_format->dbname, 836 rc == MU_ERR_FAILURE ? 837 mu_dbm_strerror(db) : mu_strerror(rc)); 838 rc = 1; 839 } else 840 rc = 0; 841 842 mu_dbm_destroy(&db); 843 844 return rc; 845} 846 847/* The `is_greylisted' predicate for new databases */ 848static int 849is_greylisted_ct(eval_environ_t env, char *email) 850{ 851 int rc; 852 mu_dbm_file_t db; 853 struct mu_dbm_datum key; 854 struct mu_dbm_datum contents; 855 time_t now; 856 857 rc = _open_dbm(&db, greylist_format->dbname, MU_STREAM_RDWR, 0600, 858 NULL); 859 if (rc) 860 rc = _open_dbm(&db, greylist_format->dbname, 861 MU_STREAM_READ, 0600, NULL); 862 MF_ASSERT(rc == 0, mfe_dbfailure, _("mf_dbm_open(%s) failed: %s"), 863 greylist_format->dbname, mu_strerror(rc)); 864 865 memset(&key, 0, sizeof key); 866 memset(&contents, 0, sizeof contents); 867 key.mu_dptr = email; 868 key.mu_dsize = strlen(email) + 1; 869 870 time(&now); 871 rc = mu_dbm_fetch(db, &key, &contents); 872 if (rc == 0) { 873 time_t timestamp; 874 875 MF_ASSERT(contents.mu_dsize == sizeof timestamp, 876 mfe_dbfailure, 877 _("greylist database %s has wrong data size"), 878 greylist_format->dbname); 879 880 timestamp = *(time_t*) contents.mu_dptr; 881 882 rc = timestamp > now; 883 if (rc) 884 MF_VAR_REF(greylist_seconds_left, long, timestamp - now); 885 886 mu_dbm_datum_free(&contents); 887 } else 888 rc = 0; 889 890 mu_dbm_destroy(&db); 891 892 return rc; 893} 894 895struct greylist_class { 896 int (*gl_fun)(eval_environ_t, char *, long); 897 int (*gl_pred)(eval_environ_t, char *); 898}; 899 900struct greylist_class greylist_class[] = { 901 { do_greylist_traditional, is_greylisted_traditional }, 902 { do_greylist_ct, is_greylisted_ct } 903}; 904 905/* greylist(key, interval) 906 907 Returns true if the key is greylisted, false if it's OK to 908 deliver mail. 909 */ 910MF_DEFUN(greylist, NUMBER, STRING email, NUMBER interval) 911{ 912 MF_RETURN(greylist_class[greylist_semantics].gl_fun(env, email, 913 interval)); 914} 915END 916 917/* is_greylisted(key) 918 919 Returns true if the key is greylisted, otherwise false 920 921*/ 922MF_DEFUN(is_greylisted, NUMBER, STRING email) 923{ 924 MF_RETURN(greylist_class[greylist_semantics].gl_pred(env, email)); 925} 926END 927 928MF_DEFUN(db_name, STRING, STRING fmtid) 929{ 930 struct db_format *fmt = db_format_lookup(fmtid); 931 MF_ASSERT(fmt != NULL, 932 mfe_not_found, 933 _("no such db format: %s"), fmtid); 934 MF_RETURN(fmt->dbname); 935} 936END 937 938MF_DEFUN(db_get_active, NUMBER, STRING fmtid) 939{ 940 struct db_format *fmt = db_format_lookup(fmtid); 941 MF_ASSERT(fmt != NULL, 942 mfe_not_found, 943 _("no such db format: %s"), fmtid); 944 MF_RETURN(fmt->enabled); 945} 946END 947 948MF_DEFUN(db_set_active, VOID, STRING fmtid, NUMBER active) 949{ 950 struct db_format *fmt = db_format_lookup(fmtid); 951 MF_ASSERT(fmt != NULL, 952 mfe_not_found, 953 _("no such db format: %s"), fmtid); 954 fmt->enabled = active; 955} 956END 957 958MF_DEFUN(db_expire_interval, NUMBER, STRING fmtid) 959{ 960 struct db_format *fmt = db_format_lookup(fmtid); 961 MF_ASSERT(fmt != NULL, 962 mfe_not_found, 963 _("no such db format: %s"), fmtid); 964 MF_RETURN(fmt->expire_interval); 965} 966END 967 968