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(&timestamp, &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