X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/b3ce3e5440ea0ce5e3580aa9a0fa18c88214a286..8f2cf8f5adaa08ef84b47bf9bc2f71e39236c22d:/src/src/acl.c diff --git a/src/src/acl.c b/src/src/acl.c index a6c3d4cbe..46615ce24 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -3,13 +3,14 @@ *************************************************/ /* Copyright (c) University of Cambridge 1995 - 2018 */ -/* Copyright (c) The Exim Maintainers 2020 */ +/* Copyright (c) The Exim Maintainers 2020 - 2021 */ /* See the file NOTICE for conditions of use and distribution. */ /* Code for handling Access Control Lists (ACLs) */ #include "exim.h" +#ifndef MACRO_PREDEF /* Default callout timeout */ @@ -53,6 +54,8 @@ static int msgcond[] = { [ACL_WARN] = BIT(OK) }; +#endif + /* ACL condition and modifier codes - keep in step with the table that follows. down. */ @@ -103,6 +106,7 @@ enum { ACLC_ACL, ACLC_REGEX, #endif ACLC_REMOVE_HEADER, + ACLC_SEEN, ACLC_SENDER_DOMAINS, ACLC_SENDERS, ACLC_SET, @@ -288,6 +292,7 @@ static condition_def conditions[] = { ACL_BIT_MIME | ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START), }, + [ACLC_SEEN] = { US"seen", TRUE, FALSE, 0 }, [ACLC_SENDER_DOMAINS] = { US"sender_domains", FALSE, FALSE, ACL_BIT_AUTH | ACL_BIT_CONNECT | ACL_BIT_HELO | @@ -338,6 +343,24 @@ static condition_def conditions[] = { }; +#ifdef MACRO_PREDEF +# include "macro_predef.h" +void +features_acl(void) +{ +for (condition_def * c = conditions; c < conditions + nelem(conditions); c++) + { + uschar buf[64], * p, * s; + int n = sprintf(CS buf, "_ACL_%s_", c->is_modifier ? "MOD" : "COND"); + for (p = buf + n, s = c->name; *s; s++) *p++ = toupper(*s); + *p = '\0'; + builtin_macro_create(buf); + } +} +#endif + + +#ifndef MACRO_PREDEF /* Return values from decode_control(); used as index so keep in step with the controls_list table that follows! */ @@ -486,7 +509,7 @@ static control_def controls_list[] = { { US"no_delay_flush", FALSE, ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START }, - + [CONTROL_NO_ENFORCE_SYNC] = { US"no_enforce_sync", FALSE, ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START @@ -744,7 +767,7 @@ while ((s = (*func)())) int v, c; BOOL negated = FALSE; uschar *saveline = s; - uschar name[64]; + uschar name[EXIM_DRIVERNAME_MAX]; /* Conditions (but not verbs) are allowed to be negated by an initial exclamation mark. */ @@ -1188,7 +1211,7 @@ int rc; /* Previous success */ -if (sender_host_name != NULL) return OK; +if (sender_host_name) return OK; /* Previous failure */ @@ -2815,6 +2838,143 @@ return rc; +/************************************************* +* Handle a check for previously-seen * +*************************************************/ + +/* +ACL clauses like: seen = -5m / key=$foo / readonly + +Return is true for condition-true - but the semantics +depend heavily on the actual use-case. + +Negative times test for seen-before, positive for seen-more-recently-than +(the given interval before current time). + +All are subject to history not having been cleaned from the DB. + +Default for seen-before is to create if not present, and to +update if older than 10d (with the seen-test time). +Default for seen-since is to always create or update. + +Options: + key=value. Default key is $sender_host_address + readonly + write + refresh=: update an existing DB entry older than given + amount. Default refresh lacking this option is 10d. + The update sets the record timestamp to the seen-test time. + +XXX do we need separate nocreate, noupdate controls? + +Arguments: + arg the option string for seen= + where ACL_WHERE_xxxx indicating which ACL this is + log_msgptr for error messages + +Returns: OK - Condition is true + FAIL - Condition is false + DEFER - Problem opening history database + ERROR - Syntax error in options +*/ + +static int +acl_seen(const uschar * arg, int where, uschar ** log_msgptr) +{ +enum { SEEN_DEFAULT, SEEN_READONLY, SEEN_WRITE }; + +const uschar * list = arg; +int slash = '/', interval, mode = SEEN_DEFAULT, yield = FAIL; +BOOL before; +int refresh = 10 * 24 * 60 * 60; /* 10 days */ +const uschar * ele, * key = sender_host_address; +open_db dbblock, * dbm; +dbdata_seen * dbd; +time_t now; + +/* Parse the first element, the time-relation. */ + +if (!(ele = string_nextinlist(&list, &slash, NULL, 0))) + goto badparse; +if ((before = *ele == '-')) + ele++; +if ((interval = readconf_readtime(ele, 0, FALSE)) < 0) + goto badparse; + +/* Remaining elements are options */ + +while ((ele = string_nextinlist(&list, &slash, NULL, 0))) + if (Ustrncmp(ele, "key=", 4) == 0) + key = ele + 4; + else if (Ustrcmp(ele, "readonly") == 0) + mode = SEEN_READONLY; + else if (Ustrcmp(ele, "write") == 0) + mode = SEEN_WRITE; + else if (Ustrncmp(ele, "refresh=", 8) == 0) + { + if ((refresh = readconf_readtime(ele + 8, 0, FALSE)) < 0) + goto badparse; + } + else + goto badopt; + +if (!(dbm = dbfn_open(US"seen", O_RDWR, &dbblock, TRUE, TRUE))) + { + HDEBUG(D_acl) debug_printf_indent("database for 'seen' not available\n"); + *log_msgptr = US"database for 'seen' not available"; + return DEFER; + } + +dbd = dbfn_read_with_length(dbm, key, NULL); +now = time(NULL); +if (dbd) /* an existing record */ + { + time_t diff = now - dbd->time_stamp; /* time since the record was written */ + + if (before ? diff >= interval : diff < interval) + yield = OK; + + if (mode == SEEN_READONLY) + { HDEBUG(D_acl) debug_printf_indent("seen db not written (readonly)\n"); } + else if (mode == SEEN_WRITE || !before) + { + dbd->time_stamp = now; + dbfn_write(dbm, key, dbd, sizeof(*dbd)); + HDEBUG(D_acl) debug_printf_indent("seen db written (update)\n"); + } + else if (diff >= refresh) + { + dbd->time_stamp = now - interval; + dbfn_write(dbm, key, dbd, sizeof(*dbd)); + HDEBUG(D_acl) debug_printf_indent("seen db written (refresh)\n"); + } + } +else + { /* No record found, yield always FAIL */ + if (mode != SEEN_READONLY) + { + dbdata_seen d = {.time_stamp = now}; + dbfn_write(dbm, key, &d, sizeof(*dbd)); + HDEBUG(D_acl) debug_printf_indent("seen db written (create)\n"); + } + else + HDEBUG(D_acl) debug_printf_indent("seen db not written (readonly)\n"); + } + +dbfn_close(dbm); +return yield; + + +badparse: + *log_msgptr = string_sprintf("failed to parse '%s'", arg); + return ERROR; +badopt: + *log_msgptr = string_sprintf("unrecognised option '%s' in '%s'", ele, arg); + return ERROR; +} + + + /************************************************* * The udpsend ACL modifier * *************************************************/ @@ -3235,13 +3395,15 @@ for (; cb; cb = cb->next) case CONTROL_FAKEREJECT: cancel_cutthrough_connection(TRUE, US"fakereject"); - case CONTROL_FAKEDEFER: + case CONTROL_FAKEDEFER: fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL; if (*p == '/') { const uschar *pp = p + 1; while (*pp) pp++; - fake_response_text = expand_string(string_copyn(p+1, pp-p-1)); + /* The entire control= line was expanded at top so no need to expand + the part after the / */ + fake_response_text = string_copyn(p+1, pp-p-1); p = pp; } else /* Explicitly reset to default string */ @@ -3319,7 +3481,7 @@ for (; cb; cb = cb->next) { uschar * debug_tag = NULL; uschar * debug_opts = NULL; - BOOL kill = FALSE; + BOOL kill = FALSE, stop = FALSE; while (*p == '/') { @@ -3336,18 +3498,39 @@ for (; cb; cb = cb->next) } else if (Ustrncmp(pp, "kill", 4) == 0) { - for (pp += 4; *pp && *pp != '/';) pp++; + pp += 4; kill = TRUE; } - else - while (*pp && *pp != '/') pp++; + else if (Ustrncmp(pp, "stop", 4) == 0) + { + pp += 4; + stop = TRUE; + } + else if (Ustrncmp(pp, "pretrigger=", 11) == 0) + debug_pretrigger_setup(pp+11); + else if (Ustrncmp(pp, "trigger=", 8) == 0) + { + if (Ustrncmp(pp += 8, "now", 3) == 0) + { + pp += 3; + debug_trigger_fire(); + } + else if (Ustrncmp(pp, "paniclog", 8) == 0) + { + pp += 8; + dtrigger_selector |= BIT(DTi_panictrigger); + } + } + while (*pp && *pp != '/') pp++; p = pp; } - if (kill) - debug_logging_stop(); - else - debug_logging_activate(debug_tag, debug_opts); + if (kill) + debug_logging_stop(TRUE); + else if (stop) + debug_logging_stop(FALSE); + else if (debug_tag || debug_opts) + debug_logging_activate(debug_tag, debug_opts); break; } @@ -3538,7 +3721,7 @@ for (; cb; cb = cb->next) } break; - #ifndef DISABLE_DKIM +#ifndef DISABLE_DKIM case ACLC_DKIM_SIGNER: if (dkim_cur_signer) rc = match_isinlist(dkim_cur_signer, @@ -3551,7 +3734,7 @@ for (; cb; cb = cb->next) rc = match_isinlist(dkim_verify_status, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); break; - #endif +#endif #ifdef SUPPORT_DMARC case ACLC_DMARC_STATUS: @@ -3736,6 +3919,10 @@ for (; cb; cb = cb->next) setup_remove_header(arg); break; + case ACLC_SEEN: + rc = acl_seen(arg, where, log_msgptr); + break; + case ACLC_SENDER_DOMAINS: { uschar *sdomain; @@ -4088,16 +4275,6 @@ while (isspace(*ss)) ss++; acl_text = ss; -#ifdef notyet_taintwarn -if ( !f.running_in_test_harness - && is_tainted2(acl_text, LOG_MAIN|LOG_PANIC, - "attempt to use tainted ACL text \"%s\"", acl_text)) - { - /* Avoid leaking info to an attacker */ - *log_msgptr = US"internal configuration error"; - return ERROR; - } -#else if (is_tainted(acl_text) && !f.running_in_test_harness) { log_write(0, LOG_MAIN|LOG_PANIC, @@ -4106,7 +4283,6 @@ if (is_tainted(acl_text) && !f.running_in_test_harness) *log_msgptr = US"internal configuration error"; return ERROR; } -#endif /* Handle the case of a string that does not contain any spaces. Look for a named ACL among those read from the configuration, or a previously read file. @@ -4695,6 +4871,7 @@ if (is_tainted(value)) putc('-', f); fprintf(f, "-acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value); } +#endif /* !MACRO_PREDEF */ /* vi: aw ai sw=2 */ /* End of acl.c */