X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/b94ea1bd61485a97c2d0dc2cab4c4d86ffe82e89..HEAD:/src/src/acl.c diff --git a/src/src/acl.c b/src/src/acl.c index 302dedaeb..0aa789dbf 100644 --- a/src/src/acl.c +++ b/src/src/acl.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) The Exim Maintainers 2020 - 2022 */ +/* Copyright (c) The Exim Maintainers 2020 - 2024 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* SPDX-License-Identifier: GPL-2.0-or-later */ @@ -57,9 +57,7 @@ static int msgcond[] = { #endif -/* ACL condition and modifier codes - keep in step with the table that -follows. -down. */ +/* ACL condition and modifier codes */ enum { ACLC_ACL, ACLC_ADD_HEADER, @@ -119,7 +117,8 @@ enum { ACLC_ACL, ACLC_SPF_GUESS, #endif ACLC_UDPSEND, - ACLC_VERIFY }; + ACLC_VERIFY, +}; /* ACL conditions/modifiers: "delay", "control", "continue", "endpass", "message", "log_message", "log_reject_target", "logwrite", "queue" and "set" are @@ -149,7 +148,7 @@ static condition_def conditions[] = { [ACLC_ACL] = { US"acl", FALSE, FALSE, 0 }, [ACLC_ADD_HEADER] = { US"add_header", TRUE, TRUE, - (unsigned int) + (unsigned) ~(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA | ACL_BIT_DATA | #ifndef DISABLE_PRDR @@ -188,7 +187,7 @@ static condition_def conditions[] = { #ifdef EXPERIMENTAL_DCC [ACLC_DCC] = { US"dcc", TRUE, FALSE, - (unsigned int) + (unsigned) ~(ACL_BIT_DATA | # ifndef DISABLE_PRDR ACL_BIT_PRDR | @@ -203,7 +202,14 @@ static condition_def conditions[] = { [ACLC_DELAY] = { US"delay", TRUE, TRUE, ACL_BIT_NOTQUIT }, #ifndef DISABLE_DKIM [ACLC_DKIM_SIGNER] = { US"dkim_signers", TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM }, - [ACLC_DKIM_STATUS] = { US"dkim_status", TRUE, FALSE, (unsigned int) ~ACL_BIT_DKIM }, + [ACLC_DKIM_STATUS] = { US"dkim_status", TRUE, FALSE, + (unsigned) + ~(ACL_BIT_DKIM | ACL_BIT_DATA | ACL_BIT_MIME +# ifndef DISABLE_PRDR + | ACL_BIT_PRDR +# endif + ), + }, #endif #ifdef SUPPORT_DMARC [ACLC_DMARC_STATUS] = { US"dmarc_status", TRUE, FALSE, (unsigned int) ~ACL_BIT_DATA }, @@ -214,7 +220,7 @@ static condition_def conditions[] = { [ACLC_DNSLISTS] = { US"dnslists", TRUE, FALSE, 0 }, [ACLC_DOMAINS] = { US"domains", FALSE, FALSE, - (unsigned int) + (unsigned) ~(ACL_BIT_RCPT | ACL_BIT_VRFY #ifndef DISABLE_PRDR |ACL_BIT_PRDR @@ -232,7 +238,7 @@ static condition_def conditions[] = { ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START, }, [ACLC_LOCAL_PARTS] = { US"local_parts", FALSE, FALSE, - (unsigned int) + (unsigned) ~(ACL_BIT_RCPT | ACL_BIT_VRFY #ifndef DISABLE_PRDR | ACL_BIT_PRDR @@ -246,7 +252,7 @@ static condition_def conditions[] = { #ifdef WITH_CONTENT_SCAN [ACLC_MALWARE] = { US"malware", TRUE, FALSE, - (unsigned int) + (unsigned) ~(ACL_BIT_DATA | # ifndef DISABLE_PRDR ACL_BIT_PRDR | @@ -273,7 +279,7 @@ static condition_def conditions[] = { #ifdef WITH_CONTENT_SCAN [ACLC_REGEX] = { US"regex", TRUE, FALSE, - (unsigned int) + (unsigned) ~(ACL_BIT_DATA | # ifndef DISABLE_PRDR ACL_BIT_PRDR | @@ -284,7 +290,7 @@ static condition_def conditions[] = { #endif [ACLC_REMOVE_HEADER] = { US"remove_header", TRUE, TRUE, - (unsigned int) + (unsigned) ~(ACL_BIT_MAIL|ACL_BIT_RCPT | ACL_BIT_PREDATA | ACL_BIT_DATA | #ifndef DISABLE_PRDR @@ -313,7 +319,7 @@ static condition_def conditions[] = { #ifdef WITH_CONTENT_SCAN [ACLC_SPAM] = { US"spam", TRUE, FALSE, - (unsigned int) ~(ACL_BIT_DATA | + (unsigned) ~(ACL_BIT_DATA | # ifndef DISABLE_PRDR ACL_BIT_PRDR | # endif @@ -363,8 +369,7 @@ for (condition_def * c = conditions; c < conditions + nelem(conditions); c++) #ifndef MACRO_PREDEF -/* Return values from decode_control(); used as index so keep in step -with the controls_list table that follows! */ +/* Return values from decode_control() */ enum { CONTROL_AUTH_UNADVERTISED, @@ -404,6 +409,9 @@ enum { #ifdef SUPPORT_I18N CONTROL_UTF8_DOWNCONVERT, #endif +#ifndef DISABLE_WELLKNOWN + CONTROL_WELLKNOWN, +#endif }; @@ -557,7 +565,12 @@ static control_def controls_list[] = { #ifdef SUPPORT_I18N [CONTROL_UTF8_DOWNCONVERT] = { US"utf8_downconvert", TRUE, (unsigned) ~(ACL_BIT_RCPT | ACL_BIT_VRFY) - } + }, +#endif +#ifndef DISABLE_WELLKNOWN +[CONTROL_WELLKNOWN] = + { US"wellknown", TRUE, (unsigned) ~ACL_BIT_WELLKNOWN + }, #endif }; @@ -793,16 +806,16 @@ return TRUE; static BOOL acl_data_to_cond(const uschar * s, acl_condition_block * cond, - const uschar * name, uschar ** error) + const uschar * name, BOOL taint, uschar ** error) { if (*s++ != '=') { *error = string_sprintf("\"=\" missing after ACL \"%s\" %s", name, conditions[cond->type].is_modifier ? US"modifier" : US"condition"); - return FALSE;; + return FALSE; } Uskip_whitespace(&s); -cond->arg = string_copy(s); +cond->arg = taint ? string_copy_taint(s, GET_TAINTED) : string_copy(s); return TRUE; } @@ -952,7 +965,7 @@ while ((s = (*func)())) "endpass" has no data */ if (c != ACLC_ENDPASS) - if (!acl_data_to_cond(s, cond, name, error)) return NULL; + if (!acl_data_to_cond(s, cond, name, FALSE, error)) return NULL; } return yield; @@ -1139,9 +1152,9 @@ Returns: nothing */ static void -acl_warn(int where, uschar *user_message, uschar *log_message) +acl_warn(int where, uschar * user_message, uschar * log_message) { -if (log_message != NULL && log_message != user_message) +if (log_message && log_message != user_message) { uschar *text; string_item *logged; @@ -1152,9 +1165,9 @@ if (log_message != NULL && log_message != user_message) /* If a sender verification has failed, and the log message is "sender verify failed", add the failure message. */ - if (sender_verified_failed != NULL && - sender_verified_failed->message != NULL && - strcmpic(log_message, US"sender verify failed") == 0) + if ( sender_verified_failed + && sender_verified_failed->message + && strcmpic(log_message, US"sender verify failed") == 0) text = string_sprintf("%s: %s", text, sender_verified_failed->message); /* Search previously logged warnings. They are kept in malloc @@ -1359,7 +1372,7 @@ uschar * target = store_get(TARGET_SIZE, GET_TAINTED); client's HELO domain. If the client has not said HELO, use its IP address instead. If it's a local client (exim -bs), CSA isn't applicable. */ -while (isspace(*domain) && *domain != '\0') ++domain; +while (isspace(*domain) && *domain) ++domain; if (*domain == '\0') domain = sender_helo_name; if (!domain) domain = sender_host_address; if (!sender_host_address) return CSA_UNKNOWN; @@ -1704,10 +1717,10 @@ BOOL no_details = FALSE; BOOL success_on_redirect = FALSE; BOOL quota = FALSE; int quota_pos_cache = QUOTA_POS_DEFAULT, quota_neg_cache = QUOTA_NEG_DEFAULT; -address_item *sender_vaddr = NULL; -uschar *verify_sender_address = NULL; -uschar *pm_mailfrom = NULL; -uschar *se_mailfrom = NULL; +address_item * sender_vaddr = NULL; +const uschar * verify_sender_address = NULL; +uschar * pm_mailfrom = NULL; +uschar * se_mailfrom = NULL; /* Some of the verify items have slash-separated options; some do not. Diagnose an error if options are given for items that don't expect them. @@ -1860,9 +1873,10 @@ switch(vp->value) verify_sender_address = sender_address; else { - while (isspace(*s)) s++; - if (*s++ != '=') goto BAD_VERIFY; - while (isspace(*s)) s++; + if (Uskip_whitespace(&s) != '=') + goto BAD_VERIFY; + s++; + Uskip_whitespace(&s); verify_sender_address = string_copy(s); } } @@ -1904,13 +1918,13 @@ while ((ss = string_nextinlist(&list, &sep, NULL, 0))) callout = CALLOUT_TIMEOUT_DEFAULT; if (*(ss += 7)) { - while (isspace(*ss)) ss++; + Uskip_whitespace(&ss); if (*ss++ == '=') { const uschar * sublist = ss; int optsep = ','; - while (isspace(*sublist)) sublist++; + Uskip_whitespace(&sublist); for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); ) { callout_opt_t * op; @@ -1924,14 +1938,14 @@ while ((ss = string_nextinlist(&list, &sep, NULL, 0))) if (op->has_option) { opt += Ustrlen(op->name); - while (isspace(*opt)) opt++; + Uskip_whitespace(&opt); if (*opt++ != '=') { *log_msgptr = string_sprintf("'=' expected after " "\"%s\" in ACL verify condition \"%s\"", op->name, arg); return ERROR; } - while (isspace(*opt)) opt++; + Uskip_whitespace(&opt); } if (op->timeval && (period = v_period(opt, arg, log_msgptr)) < 0) return ERROR; @@ -1974,14 +1988,14 @@ while ((ss = string_nextinlist(&list, &sep, NULL, 0))) quota = TRUE; if (*(ss += 5)) { - while (isspace(*ss)) ss++; + Uskip_whitespace(&ss); if (*ss++ == '=') { const uschar * sublist = ss; int optsep = ','; int period; - while (isspace(*sublist)) sublist++; + Uskip_whitespace(&sublist); for (uschar * opt; opt = string_nextinlist(&sublist, &optsep, NULL, 0); ) if (Ustrncmp(opt, "cachepos=", 9) == 0) if ((period = v_period(opt += 9, arg, log_msgptr)) < 0) @@ -3114,6 +3128,80 @@ return DEFER; +#ifndef DISABLE_WELLKNOWN +/************************************************* +* The "wellknown" ACL modifier * +*************************************************/ + +/* Called by acl_check_condition() below. + +Retrieve the given file and encode content as xtext. +Prefix with a summary line giving the length of plaintext. +Leave a global pointer to the whole, for output by +the smtp verb handler code (smtp_in.c). + +Arguments: + arg the option string for wellknown= + log_msgptr for error messages + +Returns: OK/FAIL +*/ + +static int +wellknown_process(const uschar * arg, uschar ** log_msgptr) +{ +struct stat statbuf; +FILE * rf; +gstring * g; + +wellknown_response = NULL; +if (f.no_multiline_responses) return FAIL; + +/* Check for file existence */ + +if (!*arg) return FAIL; +if (Ustat(arg, &statbuf) != 0) + { *log_msgptr = US"stat"; goto fail; } + +/*XXX perhaps refuse to serve a group- or world-writeable file? */ + +if (!(rf = Ufopen(arg, "r"))) + { *log_msgptr = US"open"; goto fail; } + +/* Set up summary line for output */ + +g = string_fmt_append(NULL, "SIZE=%lu\n", (long) statbuf.st_size); + +#define LINE_LIM 75 +for (int n = 0, ch; (ch = fgetc(rf)) != EOF; ) + { + /* Xtext-encode, adding output linebreaks for input linebreaks + or when the line gets long enough */ + + if (ch == '\n') + { g = string_fmt_append(g, "+%02X", ch); n = LINE_LIM; } + else if (ch < 33 || ch > 126 || ch == '+' || ch == '=') + { g = string_fmt_append(g, "+%02X", ch); n += 3; } + else + { g = string_fmt_append(g, "%c", ch); n++; } + + if (n >= LINE_LIM) + { g = string_catn(g, US"\n", 1); n = 0; } + } +#undef LINE_LIM + +gstring_release_unused(g); +wellknown_response = string_from_gstring(g); +return OK; + +fail: + *log_msgptr = string_sprintf("wellknown: failed to %s file \"%s\": %s", + *log_msgptr, arg, strerror(errno)); + return FAIL; +} +#endif + + /************************************************* * Handle conditions/modifiers on an ACL item * *************************************************/ @@ -3303,7 +3391,7 @@ for (; cb; cb = cb->next) case ACLC_CONTROL: { - const uschar *p = NULL; + const uschar * p = NULL; control_type = decode_control(arg, &p, where, log_msgptr); /* Check if this control makes sense at this time */ @@ -3315,6 +3403,7 @@ for (; cb; cb = cb->next) return ERROR; } + /*XXX ought to sort these, just for sanity */ switch(control_type) { case CONTROL_AUTH_UNADVERTISED: @@ -3660,8 +3749,13 @@ for (; cb; cb = cb->next) break; } return ERROR; -#endif +#endif /*I18N*/ +#ifndef DISABLE_WELLKNOWN + case CONTROL_WELLKNOWN: + rc = *p == '/' ? wellknown_process(p+1, log_msgptr) : FAIL; + break; +#endif } break; } @@ -3764,8 +3858,14 @@ for (; cb; cb = cb->next) break; case ACLC_DKIM_STATUS: - rc = match_isinlist(dkim_verify_status, - &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); + { /* return good for any match */ + const uschar * s = dkim_verify_status ? dkim_verify_status : US"none"; + int sep = 0; + for (uschar * ss; ss = string_nextinlist(&s, &sep, NULL, 0); ) + if ( (rc = match_isinlist(ss, &arg, + 0, NULL, NULL, MCL_STRING, TRUE, NULL)) + == OK) break; + } break; #endif @@ -3879,7 +3979,7 @@ for (; cb; cb = cb->next) } s++; } - while (isspace(*s)) s++; + Uskip_whitespace(&s); if (logbits == 0) logbits = LOG_MAIN; log_write(0, logbits, "%s", string_printing(s)); @@ -3942,11 +4042,11 @@ for (; cb; cb = cb->next) CUSS &recipient_data); break; - #ifdef WITH_CONTENT_SCAN +#ifdef WITH_CONTENT_SCAN case ACLC_REGEX: rc = regex(&arg, textonly); break; - #endif +#endif case ACLC_REMOVE_HEADER: setup_remove_header(arg); @@ -4303,19 +4403,17 @@ if (!s) /* At top level, we expand the incoming string. At lower levels, it has already been expanded as part of condition processing. */ -if (acl_level == 0) +if (acl_level != 0) + ss = s; +else if (!(ss = expand_string(s))) { - if (!(ss = expand_string(s))) - { - if (f.expand_string_forcedfail) return OK; - *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s, - expand_string_message); - return ERROR; - } + if (f.expand_string_forcedfail) return OK; + *log_msgptr = string_sprintf("failed to expand ACL string \"%s\": %s", s, + expand_string_message); + return ERROR; } -else ss = s; -while (isspace(*ss)) ss++; +Uskip_whitespace(&ss); /* If we can't find a named ACL, the default is to parse it as an inline one. (Unless it begins with a slash; non-existent files give rise to an error.) */ @@ -4438,7 +4536,7 @@ while ((acl_current = acl)) verbs[acl->verb], acl_name); if (basic_errno != ERRNO_CALLOUTDEFER) { - if (search_error_message != NULL && *search_error_message != 0) + if (search_error_message && *search_error_message) *log_msgptr = search_error_message; if (smtp_return_error_details) f.acl_temp_details = TRUE; } @@ -4604,8 +4702,8 @@ if (!(tmp = string_dequote(&s)) || !(name = expand_string(tmp))) for (i = 0; i < 9; i++) { - while (*s && isspace(*s)) s++; - if (!*s) break; + if (!Uskip_whitespace(&s)) + break; if (!(tmp = string_dequote(&s)) || !(tmp_arg[i] = expand_string(tmp))) { tmp = name; @@ -4700,8 +4798,8 @@ Returns: OK access is granted by an ACCEPT verb int acl_where = ACL_WHERE_UNKNOWN; int -acl_check(int where, uschar *recipient, uschar *s, uschar **user_msgptr, - uschar **log_msgptr) +acl_check(int where, const uschar * recipient, uschar * s, + uschar ** user_msgptr, uschar ** log_msgptr) { int rc; address_item adb; @@ -4928,7 +5026,7 @@ fprintf(f, "acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value); uschar * -acl_standalone_setvar(const uschar * s) +acl_standalone_setvar(const uschar * s, BOOL taint) { acl_condition_block * cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED); uschar * errstr = NULL, * log_msg = NULL; @@ -4938,7 +5036,7 @@ int e; cond->next = NULL; cond->type = ACLC_SET; if (!acl_varname_to_cond(&s, cond, &errstr)) return errstr; -if (!acl_data_to_cond(s, cond, US"'-be'", &errstr)) return errstr; +if (!acl_data_to_cond(s, cond, US"'-be'", taint, &errstr)) return errstr; if (acl_check_condition(ACL_WARN, cond, ACL_WHERE_UNKNOWN, NULL, 0, &endpass_seen, &errstr, &log_msg, &e) != OK)