X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/b3ce3e5440ea0ce5e3580aa9a0fa18c88214a286..b07d141af23f2ab160eba2b58a834baee513b3f8:/src/src/acl.c diff --git a/src/src/acl.c b/src/src/acl.c index a6c3d4cbe..19c1bbbd9 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 == '/') { @@ -3339,13 +3501,20 @@ for (; cb; cb = cb->next) for (pp += 4; *pp && *pp != '/';) pp++; kill = TRUE; } + else if (Ustrncmp(pp, "stop", 4) == 0) + { + for (pp += 4; *pp && *pp != '/';) pp++; + stop = TRUE; + } else while (*pp && *pp != '/') pp++; p = pp; } if (kill) - debug_logging_stop(); + debug_logging_stop(TRUE); + else if (stop) + debug_logging_stop(FALSE); else debug_logging_activate(debug_tag, debug_opts); break; @@ -3538,7 +3707,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 +3720,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: @@ -3702,20 +3871,22 @@ for (; cb; cb = cb->next) #endif case ACLC_QUEUE: - if (is_tainted(arg)) { - *log_msgptr = string_sprintf("Tainted name '%s' for queue not permitted", - arg); - return ERROR; - } - if (Ustrchr(arg, '/')) - { - *log_msgptr = string_sprintf( - "Directory separator not permitted in queue name: '%s'", arg); - return ERROR; + uschar *m; + if ((m = is_tainted2(arg, 0, "Tainted name '%s' for queue not permitted", arg))) + { + *log_msgptr = m; + return ERROR; + } + if (Ustrchr(arg, '/')) + { + *log_msgptr = string_sprintf( + "Directory separator not permitted in queue name: '%s'", arg); + return ERROR; + } + queue_name = string_copy_perm(arg, FALSE); + break; } - queue_name = string_copy_perm(arg, FALSE); - break; case ACLC_RATELIMIT: rc = acl_ratelimit(arg, where, log_msgptr); @@ -3736,6 +3907,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,25 +4263,14 @@ 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)) + "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, - "attempt to use tainted ACL text \"%s\"", acl_text); - /* Avoid leaking info to an attacker */ - *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. @@ -4131,6 +4295,12 @@ if (Ustrchr(ss, ' ') == NULL) else if (*ss == '/') { struct stat statbuf; + if (is_tainted2(ss, LOG_MAIN|LOG_PANIC, "Tainted ACL file name '%s'", ss)) + { + /* Avoid leaking info to an attacker */ + *log_msgptr = US"internal configuration error"; + return ERROR; + } if ((fd = Uopen(ss, O_RDONLY, 0)) < 0) { *log_msgptr = string_sprintf("failed to open ACL file \"%s\": %s", ss, @@ -4695,6 +4865,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 */