1 commit 2357aa78ccd7182cad14307eb89cb1065f078356
2 Author: Jeremy Harris <jgh146exb@wizmail.org>
3 Date: Sun Aug 1 18:15:39 2021 +0100
7 diff --git a/src/src/acl.c b/src/src/acl.c
8 index f47259ca0..be17b5768 100644
11 @@ -103,6 +103,7 @@ enum { ACLC_ACL,
19 @@ -288,6 +289,7 @@ static condition_def conditions[] = {
20 ACL_BIT_MIME | ACL_BIT_NOTSMTP |
21 ACL_BIT_NOTSMTP_START),
23 + [ACLC_SEEN] = { US"seen", TRUE, FALSE, 0 },
24 [ACLC_SENDER_DOMAINS] = { US"sender_domains", FALSE, FALSE,
25 ACL_BIT_AUTH | ACL_BIT_CONNECT |
27 @@ -2815,6 +2817,143 @@ return rc;
31 +/*************************************************
32 +* Handle a check for previously-seen *
33 +*************************************************/
36 +ACL clauses like: seen = -5m / key=$foo / readonly
38 +Return is true for condition-true - but the semantics
39 +depend heavily on the actual use-case.
41 +Negative times test for seen-before, positive for seen-more-recently-than
42 +(the given interval before current time).
44 +All are subject to history not having been cleaned from the DB.
46 +Default for seen-before is to create if not present, and to
47 +update if older than 10d (with the seen-test time).
48 +Default for seen-since is to always create or update.
51 + key=value. Default key is $sender_host_address
54 + refresh=<interval>: update an existing DB entry older than given
55 + amount. Default refresh lacking this option is 10d.
56 + The update sets the record timestamp to the seen-test time.
58 +XXX do we need separate nocreate, noupdate controls?
61 + arg the option string for seen=
62 + where ACL_WHERE_xxxx indicating which ACL this is
63 + log_msgptr for error messages
65 +Returns: OK - Condition is true
66 + FAIL - Condition is false
67 + DEFER - Problem opening history database
68 + ERROR - Syntax error in options
72 +acl_seen(const uschar * arg, int where, uschar ** log_msgptr)
74 +enum { SEEN_DEFAULT, SEEN_READONLY, SEEN_WRITE };
76 +const uschar * list = arg;
77 +int slash = '/', equal = '=', interval, mode = SEEN_DEFAULT, yield = FAIL;
79 +int refresh = 10 * 24 * 60 * 60; /* 10 days */
80 +const uschar * ele, * key = sender_host_address;
81 +open_db dbblock, * dbm;
85 +/* Parse the first element, the time-relation. */
87 +if (!(ele = string_nextinlist(&list, &slash, NULL, 0)))
89 +if ((before = *ele == '-'))
91 +if ((interval = readconf_readtime(ele, 0, FALSE)) < 0)
94 +/* Remaining elements are options */
96 +while ((ele = string_nextinlist(&list, &slash, NULL, 0)))
97 + if (Ustrncmp(ele, "key=", 4) == 0)
99 + else if (Ustrcmp(ele, "readonly") == 0)
100 + mode = SEEN_READONLY;
101 + else if (Ustrcmp(ele, "write") == 0)
103 + else if (Ustrncmp(ele, "refresh=", 8) == 0)
105 + if ((refresh = readconf_readtime(ele + 8, 0, FALSE)) < 0)
111 +if (!(dbm = dbfn_open(US"seen", O_RDWR, &dbblock, TRUE, TRUE)))
113 + HDEBUG(D_acl) debug_printf_indent("database for 'seen' not available\n");
114 + *log_msgptr = US"database for 'seen' not available";
118 +dbd = dbfn_read_with_length(dbm, key, NULL);
120 +if (dbd) /* an existing record */
122 + time_t diff = now - dbd->time_stamp; /* time since the record was written */
124 + if (before ? diff >= interval : diff < interval)
127 + if (mode == SEEN_READONLY)
128 + { HDEBUG(D_acl) debug_printf_indent("seen db not written (readonly)\n"); }
129 + else if (mode == SEEN_WRITE || !before)
131 + dbd->time_stamp = now;
132 + dbfn_write(dbm, key, dbd, sizeof(*dbd));
133 + HDEBUG(D_acl) debug_printf_indent("seen db written (update)\n");
135 + else if (diff >= refresh)
137 + dbd->time_stamp = now - interval;
138 + dbfn_write(dbm, key, dbd, sizeof(*dbd));
139 + HDEBUG(D_acl) debug_printf_indent("seen db written (refresh)\n");
143 + { /* No record found, yield always FAIL */
144 + if (mode != SEEN_READONLY)
146 + dbdata_seen d = {.time_stamp = now};
147 + dbfn_write(dbm, key, &d, sizeof(*dbd));
148 + HDEBUG(D_acl) debug_printf_indent("seen db written (create)\n");
151 + HDEBUG(D_acl) debug_printf_indent("seen db not written (readonly)\n");
159 + *log_msgptr = string_sprintf("failed to parse '%s'", arg);
162 + *log_msgptr = string_sprintf("unrecognised option '%s' in '%s'", ele, arg);
168 /*************************************************
169 * The udpsend ACL modifier *
170 *************************************************/
171 @@ -3740,6 +3879,10 @@ for (; cb; cb = cb->next)
172 setup_remove_header(arg);
176 + rc = acl_seen(arg, where, log_msgptr);
179 case ACLC_SENDER_DOMAINS:
182 diff --git a/src/src/dbstuff.h b/src/src/dbstuff.h
183 index 2f00dffb4..94db7f7fd 100644
184 --- a/src/src/dbstuff.h
185 +++ b/src/src/dbstuff.h
186 @@ -788,6 +788,12 @@ typedef struct {
187 uschar bloom[40]; /* Bloom filter which may be larger than this */
188 } dbdata_ratelimit_unique;
191 +/* For "seen" ACL condition */
196 #ifndef DISABLE_PIPE_CONNECT
197 /* This structure records the EHLO responses, cleartext and crypted,
198 for an IP, as bitmasks (cf. OPTION_TLS). For LIMITS, also values
199 diff --git a/src/src/exim_dbutil.c b/src/src/exim_dbutil.c
200 index 13f74540e..45b778fc0 100644
201 --- a/src/src/exim_dbutil.c
202 +++ b/src/src/exim_dbutil.c
203 @@ -21,7 +21,9 @@ argument is the name of the database file. The available names are:
204 misc: miscellaneous hints data
205 wait-<t>: message waiting information; <t> is a transport name
206 callout: callout verification cache
207 + ratelimit: ACL 'ratelimit' condition
208 tls: TLS session resumption cache
209 + seen: ACL 'seen' condition
211 There are a number of common subroutines, followed by three main programs,
212 whose inclusion is controlled by -D on the compilation command. */
213 @@ -38,6 +40,7 @@ whose inclusion is controlled by -D on the compilation command. */
214 #define type_callout 4
215 #define type_ratelimit 5
220 /* This is used by our cut-down dbfn_open(). */
221 @@ -126,7 +129,7 @@ static void
222 usage(uschar *name, uschar *options)
224 printf("Usage: exim_%s%s <spool-directory> <database-name>\n", name, options);
225 -printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls\n");
226 +printf(" <database-name> = retry | misc | wait-<transport-name> | callout | ratelimit | tls | seen\n");
230 @@ -150,6 +153,7 @@ if (argc == 3)
231 if (Ustrcmp(argv[2], "callout") == 0) return type_callout;
232 if (Ustrcmp(argv[2], "ratelimit") == 0) return type_ratelimit;
233 if (Ustrcmp(argv[2], "tls") == 0) return type_tls;
234 + if (Ustrcmp(argv[2], "seen") == 0) return type_seen;
236 usage(name, options);
237 return -1; /* Never obeyed */
238 @@ -581,6 +585,7 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
239 dbdata_ratelimit *ratelimit;
240 dbdata_ratelimit_unique *rate_unique;
241 dbdata_tls_session *session;
246 @@ -720,6 +725,11 @@ for (uschar * key = dbfn_scan(dbm, TRUE, &cursor);
247 session = (dbdata_tls_session *)value;
248 printf(" %s %.*s\n", keybuffer, length, session->session);
252 + seen = (dbdata_seen *)value;
253 + printf("%s\t%s\n", keybuffer, print_time(seen->time_stamp));
257 store_reset(reset_point);
258 diff --git a/test/confs/0626 b/test/confs/0626
260 index 000000000..872c4b20a
262 +++ b/test/confs/0626
264 +# Exim test configuration 0626
265 +# ACL seen condition
267 +.include DIR/aux-var/std_conf_prefix
270 +# ----- Main settings -----
272 +primary_hostname = test.ex
275 +acl_smtp_rcpt = chk_rcpt
284 +# seen = never / $sender_host_addreee / per_call
286 +# seen = before=10s / write
287 +# seen = since / readonly
290 +# seen = -10s / readonly
292 +# seen = 0s / update=20d
295 diff --git a/test/scripts/0000-Basic/0626 b/test/scripts/0000-Basic/0626
297 index 000000000..6da58ee48
299 +++ b/test/scripts/0000-Basic/0626
301 +# ACL 'seen' condition
303 +exim -DOPT='-1s' -bh 127.0.0.1
305 +MAIL FROM:<tester@test.ex>
306 +RCPT TO:<a1@test.ex>
309 +# Check that a hints DB was created.
310 +# Only the key is useful thanks to munging; should match the IP used above.
314 +# should now see old-enough record
315 +exim -DOPT='-1s' -bh 127.0.0.1
317 +MAIL FROM:<tester@test.ex>
318 +RCPT TO:<a1@test.ex>
321 +# force an update (visible via debug output in stdout for -bh)
322 +exim -DOPT='-1s / write' -bh 127.0.0.1
324 +MAIL FROM:<tester@test.ex>
325 +RCPT TO:<a1@test.ex>
328 +# default key should change with ip
329 +exim -DOPT='-1s' -bh HOSTIPV4
331 +MAIL FROM:<tester@test.ex>
332 +RCPT TO:<a1@test.ex>
336 +# explicit key (also checking expansion)
337 +exim -DOPT='-1s / key=${sender_host_address}_foo' -bh 127.0.0.1
339 +MAIL FROM:<tester@test.ex>
340 +RCPT TO:<a1@test.ex>
346 +exim -DOPT='-1s / refresh=1s' -bh 127.0.0.1
348 +MAIL FROM:<tester@test.ex>
349 +RCPT TO:<a1@test.ex>
357 +# test for seen-more-recently-than
358 +# that previous one should be no older than 5s, so this should pass
360 +# check list-parsing spaceless while we're here
361 +exim -DOPT='5s/key=${sender_host_address}_foo/readonly' -bh 127.0.0.1
363 +MAIL FROM:<tester@test.ex>
364 +RCPT TO:<a1@test.ex>
367 +# check the above no-update by waiting longer than the later-than interval; should fail
370 +exim -DOPT='1s / key=${sender_host_address}_foo' -bh 127.0.0.1
372 +MAIL FROM:<tester@test.ex>
373 +RCPT TO:<a1@test.ex>
376 +# having updated, should pass
377 +exim -DOPT='1s / key=${sender_host_address}_foo' -bh 127.0.0.1
379 +MAIL FROM:<tester@test.ex>
380 +RCPT TO:<a1@test.ex>
383 diff --git a/test/stderr/0626 b/test/stderr/0626
385 index 000000000..25e96bc4e
387 +++ b/test/stderr/0626
389 +>>> host in hosts_connection_nolog? no (option unset)
390 +>>> host in host_lookup? no (option unset)
391 +>>> host in host_reject_connection? no (option unset)
392 +>>> host in sender_unqualified_hosts? no (option unset)
393 +>>> host in recipient_unqualified_hosts? no (option unset)
394 +>>> host in helo_verify_hosts? no (option unset)
395 +>>> host in helo_try_verify_hosts? no (option unset)
396 +>>> host in helo_accept_junk_hosts? no (option unset)
397 +>>> test in helo_lookup_domains? no (end of list)
398 +>>> using ACL "chk_rcpt"
399 +>>> processing "accept" (TESTSUITE/test-config 19)
400 +>>> check seen = -1s
401 +>>> seen db written (create)
402 +>>> accept: condition test failed in ACL "chk_rcpt"
403 +>>> end of ACL "chk_rcpt": implicit DENY
404 +LOG: H=(test) [127.0.0.1] F=<tester@test.ex> rejected RCPT <a1@test.ex>
405 +>>> host in hosts_connection_nolog? no (option unset)
406 +>>> host in host_lookup? no (option unset)
407 +>>> host in host_reject_connection? no (option unset)
408 +>>> host in sender_unqualified_hosts? no (option unset)
409 +>>> host in recipient_unqualified_hosts? no (option unset)
410 +>>> host in helo_verify_hosts? no (option unset)
411 +>>> host in helo_try_verify_hosts? no (option unset)
412 +>>> host in helo_accept_junk_hosts? no (option unset)
413 +>>> test in helo_lookup_domains? no (end of list)
414 +>>> using ACL "chk_rcpt"
415 +>>> processing "accept" (TESTSUITE/test-config 19)
416 +>>> check seen = -1s
417 +>>> accept: condition test succeeded in ACL "chk_rcpt"
418 +>>> end of ACL "chk_rcpt": ACCEPT
419 +>>> host in hosts_connection_nolog? no (option unset)
420 +>>> host in host_lookup? no (option unset)
421 +>>> host in host_reject_connection? no (option unset)
422 +>>> host in sender_unqualified_hosts? no (option unset)
423 +>>> host in recipient_unqualified_hosts? no (option unset)
424 +>>> host in helo_verify_hosts? no (option unset)
425 +>>> host in helo_try_verify_hosts? no (option unset)
426 +>>> host in helo_accept_junk_hosts? no (option unset)
427 +>>> test in helo_lookup_domains? no (end of list)
428 +>>> using ACL "chk_rcpt"
429 +>>> processing "accept" (TESTSUITE/test-config 19)
430 +>>> check seen = -1s / write
431 +>>> seen db written (update)
432 +>>> accept: condition test succeeded in ACL "chk_rcpt"
433 +>>> end of ACL "chk_rcpt": ACCEPT
434 +>>> host in hosts_connection_nolog? no (option unset)
435 +>>> host in host_lookup? no (option unset)
436 +>>> host in host_reject_connection? no (option unset)
437 +>>> host in sender_unqualified_hosts? no (option unset)
438 +>>> host in recipient_unqualified_hosts? no (option unset)
439 +>>> host in helo_verify_hosts? no (option unset)
440 +>>> host in helo_try_verify_hosts? no (option unset)
441 +>>> host in helo_accept_junk_hosts? no (option unset)
442 +>>> test in helo_lookup_domains? no (end of list)
443 +>>> using ACL "chk_rcpt"
444 +>>> processing "accept" (TESTSUITE/test-config 19)
445 +>>> check seen = -1s
446 +>>> seen db written (create)
447 +>>> accept: condition test failed in ACL "chk_rcpt"
448 +>>> end of ACL "chk_rcpt": implicit DENY
449 +LOG: H=(test) [ip4.ip4.ip4.ip4] F=<tester@test.ex> rejected RCPT <a1@test.ex>
450 +>>> host in hosts_connection_nolog? no (option unset)
451 +>>> host in host_lookup? no (option unset)
452 +>>> host in host_reject_connection? no (option unset)
453 +>>> host in sender_unqualified_hosts? no (option unset)
454 +>>> host in recipient_unqualified_hosts? no (option unset)
455 +>>> host in helo_verify_hosts? no (option unset)
456 +>>> host in helo_try_verify_hosts? no (option unset)
457 +>>> host in helo_accept_junk_hosts? no (option unset)
458 +>>> test in helo_lookup_domains? no (end of list)
459 +>>> using ACL "chk_rcpt"
460 +>>> processing "accept" (TESTSUITE/test-config 19)
461 +>>> check seen = -1s / key=${sender_host_address}_foo
462 +>>> = -1s / key=127.0.0.1_foo
463 +>>> seen db written (create)
464 +>>> accept: condition test failed in ACL "chk_rcpt"
465 +>>> end of ACL "chk_rcpt": implicit DENY
466 +LOG: H=(test) [127.0.0.1] F=<tester@test.ex> rejected RCPT <a1@test.ex>
467 +>>> host in hosts_connection_nolog? no (option unset)
468 +>>> host in host_lookup? no (option unset)
469 +>>> host in host_reject_connection? no (option unset)
470 +>>> host in sender_unqualified_hosts? no (option unset)
471 +>>> host in recipient_unqualified_hosts? no (option unset)
472 +>>> host in helo_verify_hosts? no (option unset)
473 +>>> host in helo_try_verify_hosts? no (option unset)
474 +>>> host in helo_accept_junk_hosts? no (option unset)
475 +>>> test in helo_lookup_domains? no (end of list)
476 +>>> using ACL "chk_rcpt"
477 +>>> processing "accept" (TESTSUITE/test-config 19)
478 +>>> check seen = -1s / refresh=1s
479 +>>> seen db written (refresh)
480 +>>> accept: condition test succeeded in ACL "chk_rcpt"
481 +>>> end of ACL "chk_rcpt": ACCEPT
482 +>>> host in hosts_connection_nolog? no (option unset)
483 +>>> host in host_lookup? no (option unset)
484 +>>> host in host_reject_connection? no (option unset)
485 +>>> host in sender_unqualified_hosts? no (option unset)
486 +>>> host in recipient_unqualified_hosts? no (option unset)
487 +>>> host in helo_verify_hosts? no (option unset)
488 +>>> host in helo_try_verify_hosts? no (option unset)
489 +>>> host in helo_accept_junk_hosts? no (option unset)
490 +>>> test in helo_lookup_domains? no (end of list)
491 +>>> using ACL "chk_rcpt"
492 +>>> processing "accept" (TESTSUITE/test-config 19)
493 +>>> check seen = 5s/key=${sender_host_address}_foo/readonly
494 +>>> = 5s/key=127.0.0.1_foo/readonly
495 +>>> seen db not written (readonly)
496 +>>> accept: condition test succeeded in ACL "chk_rcpt"
497 +>>> end of ACL "chk_rcpt": ACCEPT
498 +>>> host in hosts_connection_nolog? no (option unset)
499 +>>> host in host_lookup? no (option unset)
500 +>>> host in host_reject_connection? no (option unset)
501 +>>> host in sender_unqualified_hosts? no (option unset)
502 +>>> host in recipient_unqualified_hosts? no (option unset)
503 +>>> host in helo_verify_hosts? no (option unset)
504 +>>> host in helo_try_verify_hosts? no (option unset)
505 +>>> host in helo_accept_junk_hosts? no (option unset)
506 +>>> test in helo_lookup_domains? no (end of list)
507 +>>> using ACL "chk_rcpt"
508 +>>> processing "accept" (TESTSUITE/test-config 19)
509 +>>> check seen = 1s / key=${sender_host_address}_foo
510 +>>> = 1s / key=127.0.0.1_foo
511 +>>> seen db written (update)
512 +>>> accept: condition test failed in ACL "chk_rcpt"
513 +>>> end of ACL "chk_rcpt": implicit DENY
514 +LOG: H=(test) [127.0.0.1] F=<tester@test.ex> rejected RCPT <a1@test.ex>
515 +>>> host in hosts_connection_nolog? no (option unset)
516 +>>> host in host_lookup? no (option unset)
517 +>>> host in host_reject_connection? no (option unset)
518 +>>> host in sender_unqualified_hosts? no (option unset)
519 +>>> host in recipient_unqualified_hosts? no (option unset)
520 +>>> host in helo_verify_hosts? no (option unset)
521 +>>> host in helo_try_verify_hosts? no (option unset)
522 +>>> host in helo_accept_junk_hosts? no (option unset)
523 +>>> test in helo_lookup_domains? no (end of list)
524 +>>> using ACL "chk_rcpt"
525 +>>> processing "accept" (TESTSUITE/test-config 19)
526 +>>> check seen = 1s / key=${sender_host_address}_foo
527 +>>> = 1s / key=127.0.0.1_foo
528 +>>> seen db written (update)
529 +>>> accept: condition test succeeded in ACL "chk_rcpt"
530 +>>> end of ACL "chk_rcpt": ACCEPT
531 diff --git a/test/stdout/0626 b/test/stdout/0626
533 index 000000000..44b481f31
535 +++ b/test/stdout/0626
538 +**** SMTP testing session as if from host 127.0.0.1
539 +**** but without any ident (RFC 1413) callback.
540 +**** This is not for real!
542 +220 test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
\r
543 +250 test.ex Hello test [127.0.0.1]
\r
545 +550 Administrative prohibition
\r
546 +221 test.ex closing connection
\r
547 ++++++++++++++++++++++++++++
548 +127.0.0.1 07-Mar-2000 12:21:52
550 +**** SMTP testing session as if from host 127.0.0.1
551 +**** but without any ident (RFC 1413) callback.
552 +**** This is not for real!
554 +220 test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
\r
555 +250 test.ex Hello test [127.0.0.1]
\r
558 +221 test.ex closing connection
\r
560 +**** SMTP testing session as if from host 127.0.0.1
561 +**** but without any ident (RFC 1413) callback.
562 +**** This is not for real!
564 +220 test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
\r
565 +250 test.ex Hello test [127.0.0.1]
\r
568 +221 test.ex closing connection
\r
570 +**** SMTP testing session as if from host ip4.ip4.ip4.ip4
571 +**** but without any ident (RFC 1413) callback.
572 +**** This is not for real!
574 +220 test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
\r
575 +250 test.ex Hello test [ip4.ip4.ip4.ip4]
\r
577 +550 Administrative prohibition
\r
578 +221 test.ex closing connection
\r
579 ++++++++++++++++++++++++++++
580 +ip4.ip4.ip4.ip4 07-Mar-2000 12:21:52
581 +127.0.0.1 07-Mar-2000 12:21:52
583 +**** SMTP testing session as if from host 127.0.0.1
584 +**** but without any ident (RFC 1413) callback.
585 +**** This is not for real!
587 +220 test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
\r
588 +250 test.ex Hello test [127.0.0.1]
\r
590 +550 Administrative prohibition
\r
591 +221 test.ex closing connection
\r
592 ++++++++++++++++++++++++++++
593 +127.0.0.1_foo 07-Mar-2000 12:21:52
594 +ip4.ip4.ip4.ip4 07-Mar-2000 12:21:52
595 +127.0.0.1 07-Mar-2000 12:21:52
597 +**** SMTP testing session as if from host 127.0.0.1
598 +**** but without any ident (RFC 1413) callback.
599 +**** This is not for real!
601 +220 test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
\r
602 +250 test.ex Hello test [127.0.0.1]
\r
605 +221 test.ex closing connection
\r
607 +**** SMTP testing session as if from host 127.0.0.1
608 +**** but without any ident (RFC 1413) callback.
609 +**** This is not for real!
611 +220 test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
\r
612 +250 test.ex Hello test [127.0.0.1]
\r
615 +221 test.ex closing connection
\r
617 +**** SMTP testing session as if from host 127.0.0.1
618 +**** but without any ident (RFC 1413) callback.
619 +**** This is not for real!
621 +220 test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
\r
622 +250 test.ex Hello test [127.0.0.1]
\r
624 +550 Administrative prohibition
\r
625 +221 test.ex closing connection
\r
627 +**** SMTP testing session as if from host 127.0.0.1
628 +**** but without any ident (RFC 1413) callback.
629 +**** This is not for real!
631 +220 test.ex ESMTP Exim x.yz Tue, 2 Mar 1999 09:44:33 +0000
\r
632 +250 test.ex Hello test [127.0.0.1]
\r
635 +221 test.ex closing connection
\r