X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/3b6774c818ba09749c2168cd0705c18d01b572ec..HEAD:/src/src/acl.c diff --git a/src/src/acl.c b/src/src/acl.c index ab991ef41..ab05b13a9 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 @@ -130,27 +129,28 @@ being the prefix of another; the binary-search in the list will go wrong. */ typedef struct condition_def { uschar *name; -/* Flag to indicate the condition/modifier has a string expansion done -at the outer level. In the other cases, expansion already occurs in the -checking functions. */ - BOOL expand_at_top:1; - - BOOL is_modifier:1; + /* Flags for actions or checks to do during readconf for this condition */ + unsigned flags; +#define ACD_EXP BIT(0) /* do expansion at outer level*/ +#define ACD_MOD BIT(1) /* is a modifier */ +#define ACD_LOAD BIT(2) /* supported by a dynamic-load module */ -/* Bit map vector of which conditions and modifiers are not allowed at certain -times. For each condition and modifier, there's a bitmap of dis-allowed times. -For some, it is easier to specify the negation of a small number of allowed -times. */ + /* Bit map vector of which conditions and modifiers are not allowed at certain + times. For each condition and modifier, there's a bitmap of dis-allowed times. + For some, it is easier to specify the negation of a small number of allowed + times. */ unsigned forbids; +#define FORBIDDEN(times) (times) +#define PERMITTED(times) ((unsigned) ~(times)) } condition_def; static condition_def conditions[] = { - [ACLC_ACL] = { US"acl", FALSE, FALSE, 0 }, + [ACLC_ACL] = { US"acl", 0, + FORBIDDEN(0) }, - [ACLC_ADD_HEADER] = { US"add_header", TRUE, TRUE, - (unsigned int) - ~(ACL_BIT_MAIL | ACL_BIT_RCPT | + [ACLC_ADD_HEADER] = { US"add_header", ACD_EXP | ACD_MOD, + PERMITTED(ACL_BIT_MAIL | ACL_BIT_RCPT | ACL_BIT_PREDATA | ACL_BIT_DATA | #ifndef DISABLE_PRDR ACL_BIT_PRDR | @@ -160,13 +160,14 @@ static condition_def conditions[] = { ACL_BIT_NOTSMTP_START), }, - [ACLC_AUTHENTICATED] = { US"authenticated", FALSE, FALSE, - ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START | - ACL_BIT_CONNECT | ACL_BIT_HELO, + [ACLC_AUTHENTICATED] = { US"authenticated", 0, + FORBIDDEN(ACL_BIT_NOTSMTP | + ACL_BIT_NOTSMTP_START | + ACL_BIT_CONNECT | ACL_BIT_HELO), }, #ifdef EXPERIMENTAL_BRIGHTMAIL - [ACLC_BMI_OPTIN] = { US"bmi_optin", TRUE, TRUE, - ACL_BIT_AUTH | + [ACLC_BMI_OPTIN] = { US"bmi_optin", ACD_EXP | ACD_MOD, + FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT | ACL_BIT_HELO | ACL_BIT_DATA | ACL_BIT_MIME | # ifndef DISABLE_PRDR @@ -176,20 +177,22 @@ static condition_def conditions[] = { ACL_BIT_MAILAUTH | ACL_BIT_MAIL | ACL_BIT_STARTTLS | ACL_BIT_VRFY | ACL_BIT_PREDATA | - ACL_BIT_NOTSMTP_START, + ACL_BIT_NOTSMTP_START), }, #endif - [ACLC_CONDITION] = { US"condition", TRUE, FALSE, 0 }, - [ACLC_CONTINUE] = { US"continue", TRUE, TRUE, 0 }, + [ACLC_CONDITION] = { US"condition", ACD_EXP, + FORBIDDEN(0) }, + [ACLC_CONTINUE] = { US"continue", ACD_EXP | ACD_MOD, + FORBIDDEN(0) }, /* Certain types of control are always allowed, so we let it through always and check in the control processing itself. */ - [ACLC_CONTROL] = { US"control", TRUE, TRUE, 0 }, + [ACLC_CONTROL] = { US"control", ACD_EXP | ACD_MOD, + FORBIDDEN(0) }, #ifdef EXPERIMENTAL_DCC - [ACLC_DCC] = { US"dcc", TRUE, FALSE, - (unsigned int) - ~(ACL_BIT_DATA | + [ACLC_DCC] = { US"dcc", ACD_EXP, + PERMITTED(ACL_BIT_DATA | # ifndef DISABLE_PRDR ACL_BIT_PRDR | # endif @@ -197,57 +200,82 @@ static condition_def conditions[] = { }, #endif #ifdef WITH_CONTENT_SCAN - [ACLC_DECODE] = { US"decode", TRUE, FALSE, (unsigned int) ~ACL_BIT_MIME }, + [ACLC_DECODE] = { US"decode", ACD_EXP, + PERMITTED(ACL_BIT_MIME) }, #endif - [ACLC_DELAY] = { US"delay", TRUE, TRUE, ACL_BIT_NOTQUIT }, + [ACLC_DELAY] = { US"delay", ACD_EXP | ACD_MOD, + FORBIDDEN(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_SIGNER] = { US"dkim_signers", +# if SUPPORT_DKIM==2 + ACD_LOAD | +# endif + ACD_EXP, + PERMITTED(ACL_BIT_DKIM) }, + [ACLC_DKIM_STATUS] = { US"dkim_status", +# if SUPPORT_DKIM==2 + ACD_LOAD | +# endif + ACD_EXP, + PERMITTED(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 }, + [ACLC_DMARC_STATUS] = { US"dmarc_status", +# if SUPPORT_DMARC==2 + ACD_LOAD | +# endif + ACD_EXP, + PERMITTED(ACL_BIT_DATA) }, #endif /* Explicit key lookups can be made in non-smtp ACLs so pass always and check in the verify processing itself. */ - [ACLC_DNSLISTS] = { US"dnslists", TRUE, FALSE, 0 }, + [ACLC_DNSLISTS] = { US"dnslists", ACD_EXP, + FORBIDDEN(0) }, - [ACLC_DOMAINS] = { US"domains", FALSE, FALSE, - (unsigned int) - ~(ACL_BIT_RCPT | ACL_BIT_VRFY + [ACLC_DOMAINS] = { US"domains", 0, + PERMITTED(ACL_BIT_RCPT | ACL_BIT_VRFY #ifndef DISABLE_PRDR - |ACL_BIT_PRDR + | ACL_BIT_PRDR #endif ), }, - [ACLC_ENCRYPTED] = { US"encrypted", FALSE, FALSE, - ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START | - ACL_BIT_CONNECT + [ACLC_ENCRYPTED] = { US"encrypted", 0, + FORBIDDEN(ACL_BIT_NOTSMTP | + ACL_BIT_NOTSMTP_START | ACL_BIT_CONNECT) }, - [ACLC_ENDPASS] = { US"endpass", TRUE, TRUE, 0 }, + [ACLC_ENDPASS] = { US"endpass", ACD_EXP | ACD_MOD, + FORBIDDEN(0) }, - [ACLC_HOSTS] = { US"hosts", FALSE, FALSE, - ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START, + [ACLC_HOSTS] = { US"hosts", 0, + FORBIDDEN(ACL_BIT_NOTSMTP | + ACL_BIT_NOTSMTP_START), }, - [ACLC_LOCAL_PARTS] = { US"local_parts", FALSE, FALSE, - (unsigned int) - ~(ACL_BIT_RCPT | ACL_BIT_VRFY + [ACLC_LOCAL_PARTS] = { US"local_parts", 0, + PERMITTED(ACL_BIT_RCPT | ACL_BIT_VRFY #ifndef DISABLE_PRDR | ACL_BIT_PRDR #endif ), }, - [ACLC_LOG_MESSAGE] = { US"log_message", TRUE, TRUE, 0 }, - [ACLC_LOG_REJECT_TARGET] = { US"log_reject_target", TRUE, TRUE, 0 }, - [ACLC_LOGWRITE] = { US"logwrite", TRUE, TRUE, 0 }, + [ACLC_LOG_MESSAGE] = { US"log_message", ACD_EXP | ACD_MOD, + FORBIDDEN(0) }, + [ACLC_LOG_REJECT_TARGET] = { US"log_reject_target", ACD_EXP | ACD_MOD, + FORBIDDEN(0) }, + [ACLC_LOGWRITE] = { US"logwrite", ACD_EXP | ACD_MOD, + FORBIDDEN(0) }, #ifdef WITH_CONTENT_SCAN - [ACLC_MALWARE] = { US"malware", TRUE, FALSE, - (unsigned int) - ~(ACL_BIT_DATA | + [ACLC_MALWARE] = { US"malware", ACD_EXP, + PERMITTED(ACL_BIT_DATA | # ifndef DISABLE_PRDR ACL_BIT_PRDR | # endif @@ -255,26 +283,29 @@ static condition_def conditions[] = { }, #endif - [ACLC_MESSAGE] = { US"message", TRUE, TRUE, 0 }, + [ACLC_MESSAGE] = { US"message", ACD_EXP | ACD_MOD, + FORBIDDEN(0) }, #ifdef WITH_CONTENT_SCAN - [ACLC_MIME_REGEX] = { US"mime_regex", TRUE, FALSE, (unsigned int) ~ACL_BIT_MIME }, + [ACLC_MIME_REGEX] = { US"mime_regex", ACD_EXP, + PERMITTED(ACL_BIT_MIME) }, #endif - [ACLC_QUEUE] = { US"queue", TRUE, TRUE, - ACL_BIT_NOTSMTP | + [ACLC_QUEUE] = { US"queue", ACD_EXP | ACD_MOD, + FORBIDDEN(ACL_BIT_NOTSMTP | #ifndef DISABLE_PRDR ACL_BIT_PRDR | #endif - ACL_BIT_DATA, + ACL_BIT_DATA), }, - [ACLC_RATELIMIT] = { US"ratelimit", TRUE, FALSE, 0 }, - [ACLC_RECIPIENTS] = { US"recipients", FALSE, FALSE, (unsigned int) ~ACL_BIT_RCPT }, + [ACLC_RATELIMIT] = { US"ratelimit", ACD_EXP, + FORBIDDEN(0) }, + [ACLC_RECIPIENTS] = { US"recipients", 0, + PERMITTED(ACL_BIT_RCPT) }, #ifdef WITH_CONTENT_SCAN - [ACLC_REGEX] = { US"regex", TRUE, FALSE, - (unsigned int) - ~(ACL_BIT_DATA | + [ACLC_REGEX] = { US"regex", ACD_EXP, + PERMITTED(ACL_BIT_DATA | # ifndef DISABLE_PRDR ACL_BIT_PRDR | # endif @@ -283,9 +314,8 @@ static condition_def conditions[] = { }, #endif - [ACLC_REMOVE_HEADER] = { US"remove_header", TRUE, TRUE, - (unsigned int) - ~(ACL_BIT_MAIL|ACL_BIT_RCPT | + [ACLC_REMOVE_HEADER] = { US"remove_header", ACD_EXP | ACD_MOD, + PERMITTED(ACL_BIT_MAIL|ACL_BIT_RCPT | ACL_BIT_PREDATA | ACL_BIT_DATA | #ifndef DISABLE_PRDR ACL_BIT_PRDR | @@ -293,27 +323,29 @@ 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 | + [ACLC_SEEN] = { US"seen", ACD_EXP, + FORBIDDEN(0) }, + [ACLC_SENDER_DOMAINS] = { US"sender_domains", 0, + FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT | ACL_BIT_HELO | ACL_BIT_MAILAUTH | ACL_BIT_QUIT | ACL_BIT_ETRN | ACL_BIT_EXPN | - ACL_BIT_STARTTLS | ACL_BIT_VRFY, + ACL_BIT_STARTTLS | ACL_BIT_VRFY), }, - [ACLC_SENDERS] = { US"senders", FALSE, FALSE, - ACL_BIT_AUTH | ACL_BIT_CONNECT | + [ACLC_SENDERS] = { US"senders", 0, + FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT | ACL_BIT_HELO | ACL_BIT_MAILAUTH | ACL_BIT_QUIT | ACL_BIT_ETRN | ACL_BIT_EXPN | - ACL_BIT_STARTTLS | ACL_BIT_VRFY, + ACL_BIT_STARTTLS | ACL_BIT_VRFY), }, - [ACLC_SET] = { US"set", TRUE, TRUE, 0 }, + [ACLC_SET] = { US"set", ACD_EXP | ACD_MOD, + FORBIDDEN(0) }, #ifdef WITH_CONTENT_SCAN - [ACLC_SPAM] = { US"spam", TRUE, FALSE, - (unsigned int) ~(ACL_BIT_DATA | + [ACLC_SPAM] = { US"spam", ACD_EXP, + PERMITTED(ACL_BIT_DATA | # ifndef DISABLE_PRDR ACL_BIT_PRDR | # endif @@ -321,26 +353,36 @@ static condition_def conditions[] = { }, #endif #ifdef SUPPORT_SPF - [ACLC_SPF] = { US"spf", TRUE, FALSE, - ACL_BIT_AUTH | ACL_BIT_CONNECT | + [ACLC_SPF] = { US"spf", +# if SUPPORT_SPF==2 + ACD_LOAD | +# endif + ACD_EXP, + FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT | ACL_BIT_HELO | ACL_BIT_MAILAUTH | ACL_BIT_ETRN | ACL_BIT_EXPN | ACL_BIT_STARTTLS | ACL_BIT_VRFY | - ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START, + ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START), }, - [ACLC_SPF_GUESS] = { US"spf_guess", TRUE, FALSE, - ACL_BIT_AUTH | ACL_BIT_CONNECT | + [ACLC_SPF_GUESS] = { US"spf_guess", +# if SUPPORT_SPF==2 + ACD_LOAD | +# endif + ACD_EXP, + FORBIDDEN(ACL_BIT_AUTH | ACL_BIT_CONNECT | ACL_BIT_HELO | ACL_BIT_MAILAUTH | ACL_BIT_ETRN | ACL_BIT_EXPN | ACL_BIT_STARTTLS | ACL_BIT_VRFY | - ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START, + ACL_BIT_NOTSMTP | ACL_BIT_NOTSMTP_START), }, #endif - [ACLC_UDPSEND] = { US"udpsend", TRUE, TRUE, 0 }, + [ACLC_UDPSEND] = { US"udpsend", ACD_EXP | ACD_MOD, + FORBIDDEN(0) }, /* Certain types of verify are always allowed, so we let it through always and check in the verify function itself */ - [ACLC_VERIFY] = { US"verify", TRUE, FALSE, 0 }, + [ACLC_VERIFY] = { US"verify", ACD_EXP, + FORBIDDEN(0) }, }; @@ -352,7 +394,7 @@ 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"); + int n = sprintf(CS buf, "_ACL_%s_", c->flags & ACD_MOD ? "MOD" : "COND"); for (p = buf + n, s = c->name; *s; s++) *p++ = toupper(*s); *p = '\0'; builtin_macro_create(buf); @@ -360,11 +402,50 @@ for (condition_def * c = conditions; c < conditions + nelem(conditions); c++) } #endif +/******************************************************************************/ #ifndef MACRO_PREDEF -/* Return values from decode_control(); used as index so keep in step -with the controls_list table that follows! */ +/* These tables support loading of dynamic modules triggered by an ACL +condition use, spotted during readconf. See acl_read(). */ + +# ifdef LOOKUP_MODULE_DIR +typedef struct condition_module { + const uschar * mod_name; /* module for the givien conditions */ + misc_module_info * info; /* NULL when not loaded */ + const int * conditions; /* array of ACLC_*, -1 terminated */ +} condition_module; + +# if SUPPORT_SPF==2 +static int spf_condx[] = { ACLC_SPF, ACLC_SPF_GUESS, -1 }; +# endif +# if SUPPORT_DKIM==2 +static int dkim_condx[] = { ACLC_DKIM_SIGNER, ACLC_DKIM_STATUS, -1 }; +# endif +# if SUPPORT_DMARC==2 +static int dmarc_condx[] = { ACLC_DMARC_STATUS, -1 }; +# endif + +/* These are modules which can be loaded on seeing an ACL condition +during readconf, The "arc" module is handled by custom coding. */ + +static condition_module condition_modules[] = { +# if SUPPORT_SPF==2 + {.mod_name = US"spf", .conditions = spf_condx}, +# endif +# if SUPPORT_DKIM==2 + {.mod_name = US"dkim", .conditions = dkim_condx}, +# endif +# if SUPPORT_DMARC==2 + {.mod_name = US"dmarc", .conditions = dmarc_condx}, +# endif +}; + +# endif /*LOOKUP_MODULE_DIR*/ + +/****************************/ + +/* Return values from decode_control() */ enum { CONTROL_AUTH_UNADVERTISED, @@ -404,6 +485,9 @@ enum { #ifdef SUPPORT_I18N CONTROL_UTF8_DOWNCONVERT, #endif +#ifndef DISABLE_WELLKNOWN + CONTROL_WELLKNOWN, +#endif }; @@ -557,7 +641,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 +882,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;; + conditions[cond->type].flags & ACD_MOD ? US"modifier" : US"condition"); + return FALSE; } Uskip_whitespace(&s); -cond->arg = string_copy(s); +cond->arg = taint ? string_copy_taint(s, GET_TAINTED) : string_copy(s); return TRUE; } @@ -864,7 +953,7 @@ while ((s = (*func)())) if ((v = acl_checkname(name, verbs, nelem(verbs))) < 0) { - if (!this) + if (!this) /* not handling a verb right now */ { *error = string_sprintf("unknown ACL verb \"%s\" in \"%s\"", name, saveline); @@ -909,7 +998,7 @@ while ((s = (*func)())) /* The modifiers may not be negated */ - if (negated && conditions[c].is_modifier) + if (negated && conditions[c].flags & ACD_MOD) { *error = string_sprintf("ACL error: negation is not allowed with " "\"%s\"", conditions[c].name); @@ -927,6 +1016,51 @@ while ((s = (*func)())) return NULL; } +#ifdef LOOKUP_MODULE_DIR + if (conditions[c].flags & ACD_LOAD) + { /* a loadable module supports this condition */ + condition_module * cm; + uschar * s = NULL; + + /* Over the list of modules we support, check the list of ACL conditions + each supports. This assumes no duplicates. */ + + for (cm = condition_modules; + cm < condition_modules + nelem(condition_modules); cm++) + for (const int * cond = cm->conditions; *cond != -1; cond++) + if (*cond == c) goto found; + found: + + if (cm >= condition_modules + nelem(condition_modules)) + { /* shouldn't happen */ + *error = string_sprintf("ACL error: failed to locate support for '%s'", + conditions[c].name); + return NULL; + } + if ( !cm->info /* module not loaded */ + && !(cm->info = misc_mod_find(cm->mod_name, &s))) + { + *error = string_sprintf("ACL error: failed to find module for '%s': %s", + conditions[c].name, s); + return NULL; + } + } +# ifdef EXPERIMENTAL_ARC + else if (c == ACLC_VERIFY) /* Special handling for verify=arc; */ + { /* not invented a more general method yet- flag in verify_type_list? */ + const uschar * t = s; + uschar * e; + if ( *t++ == '=' && Uskip_whitespace(&t) && Ustrncmp(t, "arc", 3) == 0 + && !misc_mod_find(US"arc", &e)) + { + *error = string_sprintf("ACL error: failed to find module for '%s': %s", + conditions[c].name, e); + return NULL; + } + } +# endif +#endif /*LOOKUP_MODULE_DIR*/ + cond = store_get(sizeof(acl_condition_block), GET_UNTAINTED); cond->next = NULL; cond->type = c; @@ -952,7 +1086,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 +1273,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 +1286,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 +1493,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; @@ -1434,6 +1568,7 @@ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); /* Extract the numerical SRV fields (p is incremented) */ + if (rr_bad_size(rr, 3 * sizeof(uint16_t))) continue; GETSHORT(priority, p); GETSHORT(weight, p); GETSHORT(port, p); @@ -1703,10 +1838,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. @@ -1778,19 +1913,11 @@ switch(vp->value) #ifdef EXPERIMENTAL_ARC case VERIFY_ARC: - { /* Do Authenticated Received Chain checks in a separate function. */ - const uschar * condlist = CUS string_nextinlist(&list, &sep, NULL, 0); - int csep = 0; - uschar * cond; - - if (!(arc_state = acl_verify_arc())) return DEFER; - DEBUG(D_acl) debug_printf_indent("ARC verify result %s %s%s%s\n", arc_state, - arc_state_reason ? "(":"", arc_state_reason, arc_state_reason ? ")":""); - - if (!condlist) condlist = US"none:pass"; - while ((cond = string_nextinlist(&condlist, &csep, NULL, 0))) - if (Ustrcmp(arc_state, cond) == 0) return OK; - return FAIL; + { + const misc_module_info * mi = misc_mod_findonly(US"arc"); + typedef int (*fn_t)(const uschar *); + if (mi) return (((fn_t *) mi->functions)[ARC_VERIFY]) + (CUS string_nextinlist(&list, &sep, NULL, 0)); } #endif @@ -1859,9 +1986,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); } } @@ -1903,13 +2031,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; @@ -1923,14 +2051,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; @@ -1973,14 +2101,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) @@ -2569,7 +2697,7 @@ if ((t = tree_search(*anchor, key))) /* We aren't using a pre-computed rate, so get a previously recorded rate from the database, which will be updated and written back if required. */ -if (!(dbm = dbfn_open(US"ratelimit", O_RDWR, &dbblock, TRUE, TRUE))) +if (!(dbm = dbfn_open(US"ratelimit", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE))) { store_pool = old_pool; sender_rate = NULL; @@ -2951,7 +3079,7 @@ while ((ele = string_nextinlist(&list, &slash, NULL, 0))) else goto badopt; -if (!(dbm = dbfn_open(US"seen", O_RDWR, &dbblock, TRUE, TRUE))) +if (!(dbm = dbfn_open(US"seen", O_RDWR|O_CREAT, &dbblock, TRUE, TRUE))) { HDEBUG(D_acl) debug_printf_indent("database for 'seen' not available\n"); *log_msgptr = US"database for 'seen' not available"; @@ -3113,6 +3241,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 * *************************************************/ @@ -3187,7 +3389,7 @@ for (; cb; cb = cb->next) of them, but not for all, because expansion happens down in some lower level checking functions in some cases. */ - if (!conditions[cb->type].expand_at_top) + if (!(conditions[cb->type].flags & ACD_EXP)) arg = cb->arg; else if (!(arg = expand_string_2(cb->arg, &textonly))) @@ -3204,7 +3406,7 @@ for (; cb; cb = cb->next) { int lhswidth = 0; debug_printf_indent("check %s%s %n", - (!conditions[cb->type].is_modifier && cb->u.negated)? "!":"", + (!(conditions[cb->type].flags & ACD_MOD) && cb->u.negated) ? "!":"", conditions[cb->type].name, &lhswidth); if (cb->type == ACLC_SET) @@ -3236,7 +3438,7 @@ for (; cb; cb = cb->next) if ((conditions[cb->type].forbids & (1 << where)) != 0) { *log_msgptr = string_sprintf("cannot %s %s condition in %s ACL", - conditions[cb->type].is_modifier ? "use" : "test", + conditions[cb->type].flags & ACD_MOD ? "use" : "test", conditions[cb->type].name, acl_wherenames[where]); return ERROR; } @@ -3302,7 +3504,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 */ @@ -3314,6 +3516,7 @@ for (; cb; cb = cb->next) return ERROR; } + /*XXX ought to sort these, just for sanity */ switch(control_type) { case CONTROL_AUTH_UNADVERTISED: @@ -3428,7 +3631,7 @@ for (; cb; cb = cb->next) case CONTROL_FAKEREJECT: cancel_cutthrough_connection(TRUE, US"fakereject"); case CONTROL_FAKEDEFER: - fake_response = (control_type == CONTROL_FAKEDEFER) ? DEFER : FAIL; + fake_response = control_type == CONTROL_FAKEDEFER ? DEFER : FAIL; if (*p == '/') { const uschar *pp = p + 1; @@ -3659,8 +3862,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; } @@ -3755,29 +3963,48 @@ for (; cb; cb = cb->next) #ifndef DISABLE_DKIM case ACLC_DKIM_SIGNER: - if (dkim_cur_signer) - rc = match_isinlist(dkim_cur_signer, - &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); - else - rc = FAIL; - break; - case ACLC_DKIM_STATUS: - rc = match_isinlist(dkim_verify_status, - &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); + /* See comment on ACLC_SPF wrt. coding issues */ + { + misc_module_info * mi = misc_mod_find(US"dkim", &log_message); + typedef int (*fn_t)(const uschar *); + rc = mi + ? (((fn_t *) mi->functions) + [cb->type == ACLC_DKIM_SIGNER + ? DKIM_SIGNER_ISINLIST + : DKIM_STATUS_LISTMATCH]) (arg) + : DEFER; break; + } #endif #ifdef SUPPORT_DMARC case ACLC_DMARC_STATUS: + /* See comment on ACLC_SPF wrt. coding issues */ + { + misc_module_info * mi = misc_mod_find(US"dmarc", &log_message); + typedef uschar * (*efn_t)(int); + uschar * expanded_query; + + if (!mi) + { rc = DEFER; break; } /* shouldn't happen */ + if (!f.dmarc_has_been_checked) - dmarc_process(); - f.dmarc_has_been_checked = TRUE; + { + typedef int (*pfn_t)(void); + (void) (((pfn_t *) mi->functions)[DMARC_PROCESS]) (); + f.dmarc_has_been_checked = TRUE; + } /* used long way of dmarc_exim_expand_query() in case we need more view into the process in the future. */ - rc = match_isinlist(dmarc_exim_expand_query(DMARC_VERIFY_STATUS), + + /*XXX is this call used with any other arg? */ + expanded_query = (((efn_t *) mi->functions)[DMARC_EXPAND_QUERY]) + (DMARC_VERIFY_STATUS); + rc = match_isinlist(expanded_query, &arg, 0, NULL, NULL, MCL_STRING, TRUE, NULL); + } break; #endif @@ -3878,7 +4105,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)); @@ -3941,11 +4168,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); @@ -3981,11 +4208,19 @@ for (; cb; cb = cb->next) #endif ) store_pool = POOL_PERM; + #ifndef DISABLE_DKIM /* Overwriteable dkim result variables */ - if (Ustrcmp(cb->u.varname, "dkim_verify_status") == 0) - dkim_verify_status = string_copy(arg); - else if (Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0) - dkim_verify_reason = string_copy(arg); + if ( Ustrcmp(cb->u.varname, "dkim_verify_status") == 0 + || Ustrcmp(cb->u.varname, "dkim_verify_reason") == 0 + ) + { + misc_module_info * mi = misc_mod_findonly(US"dkim"); + typedef void (*fn_t)(const uschar *, void *); + + if (mi) + (((fn_t *) mi->functions)[DKIM_SETVAR]) + (cb->u.varname, string_copy(arg)); + } else #endif acl_var_create(cb->u.varname)->data.ptr = string_copy(arg); @@ -4012,12 +4247,28 @@ for (; cb; cb = cb->next) #ifdef SUPPORT_SPF case ACLC_SPF: - rc = spf_process(&arg, sender_address, SPF_PROCESS_NORMAL); - break; - case ACLC_SPF_GUESS: - rc = spf_process(&arg, sender_address, SPF_PROCESS_GUESS); + /* We have hardwired function-call numbers, and also prototypes for the + functions. We could do a function name table search or (simpler) + a module include file with defines for the numbers + but I can't see how to deal with prototypes. Is a K&R non-prototyped + function still usable with today's compilers (but we would lose on + type-checking)? We could macroize the typedef, and even the function + table access - but it obscures how it works rather. */ + { + misc_module_info * mi = misc_mod_find(US"spf", &log_message); + typedef int (*fn_t)(const uschar **, const uschar *, int); + fn_t fn; + + if (!mi) + { rc = DEFER; break; } /* shouldn't happen */ + + fn = ((fn_t *) mi->functions)[SPF_PROCESS]; + + rc = fn(&arg, sender_address, + cb->type == ACLC_SPF ? SPF_PROCESS_NORMAL : SPF_PROCESS_GUESS); break; + } #endif case ACLC_UDPSEND: @@ -4045,7 +4296,7 @@ for (; cb; cb = cb->next) /* If a condition was negated, invert OK/FAIL. */ - if (!conditions[cb->type].is_modifier && cb->u.negated) + if (!(conditions[cb->type].flags & ACD_MOD) && cb->u.negated) if (rc == OK) rc = FAIL; else if (rc == FAIL || rc == FAIL_DROP) rc = OK; @@ -4302,19 +4553,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.) */ @@ -4346,7 +4595,7 @@ if (Ustrchr(ss, ' ') == NULL) HDEBUG(D_acl) debug_printf_indent("ACL \"%s\" is empty: implicit DENY\n", ss); return FAIL; } - acl_name = string_sprintf("ACL \"%s\"", ss); + acl_name = string_sprintf("ACL %s", ss); HDEBUG(D_acl) debug_printf_indent("using ACL \"%s\"\n", ss); } @@ -4379,7 +4628,7 @@ if (Ustrchr(ss, ' ') == NULL) acl_text[statbuf.st_size] = 0; (void)close(fd); - acl_name = string_sprintf("ACL \"%s\"", ss); + acl_name = string_sprintf("ACL %s", ss); HDEBUG(D_acl) debug_printf_indent("read ACL from file %s\n", ss); } } @@ -4417,8 +4666,15 @@ while ((acl_current = acl)) *log_msgptr = *user_msgptr = NULL; f.acl_temp_details = FALSE; - HDEBUG(D_acl) debug_printf_indent("processing \"%s\" (%s %d)\n", - verbs[acl->verb], acl->srcfile, acl->srcline); + config_filename = acl->srcfile; + config_lineno = acl->srcline; + + HDEBUG(D_acl) + { + debug_printf_indent("processing %s \"%s\"", acl_name, verbs[acl->verb]); + if (config_lineno) debug_printf(" (%s %d)", config_filename, config_lineno); + debug_printf("\n"); + } /* Clear out any search error message from a previous check before testing this condition. */ @@ -4437,7 +4693,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; } @@ -4551,10 +4807,18 @@ while ((acl_current = acl)) if (cond == OK) acl_warn(where, *user_msgptr, *log_msgptr); else if (cond == DEFER && LOGGING(acl_warn_skipped)) - log_write(0, LOG_MAIN, "%s Warning: ACL \"warn\" statement skipped: " - "condition test deferred%s%s", host_and_ident(TRUE), - *log_msgptr ? US": " : US"", - *log_msgptr ? *log_msgptr : US""); + if (config_lineno > 0) + log_write(0, LOG_MAIN, + "%s Warning: ACL 'warn' statement skipped (in %s at line %d of %s):" + " condition test deferred%s%s", + host_and_ident(TRUE), acl_name, config_lineno, config_filename, + *log_msgptr ? US": " : US"", *log_msgptr ? *log_msgptr : US""); + else + log_write(0, LOG_MAIN, + "%s Warning: ACL 'warn' statement skipped (in %s):" + " condition test deferred%s%s", + host_and_ident(TRUE), acl_name, + *log_msgptr ? US": " : US"", *log_msgptr ? *log_msgptr : US""); *log_msgptr = *user_msgptr = NULL; /* In case implicit DENY follows */ break; @@ -4603,8 +4867,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; @@ -4628,6 +4892,7 @@ while (i < 9) acl_level++; ret = acl_check_internal(where, addr, name, user_msgptr, log_msgptr); acl_level--; +config_lineno = 0; acl_narg = sav_narg; for (i = 0; i < 9; i++) acl_arg[i] = sav_arg[i]; @@ -4673,6 +4938,7 @@ if (where == ACL_WHERE_RCPT) acl_level++; rc = acl_check_internal(where, addr, s, user_msgptr, log_msgptr); acl_level--; +config_lineno = 0; return rc; } @@ -4696,11 +4962,10 @@ Returns: OK access is granted by an ACCEPT verb DEFER can't tell at the moment ERROR disaster */ -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; @@ -4741,6 +5006,7 @@ acl_level = 0; rc = acl_check_internal(where, addr, s, user_msgptr, log_msgptr); acl_level = 0; acl_where = ACL_WHERE_UNKNOWN; +config_lineno = 0; /* Cutthrough - if requested, and WHERE_RCPT and not yet opened conn as result of recipient-verify, @@ -4916,9 +5182,11 @@ FILE * f = (FILE *)ctx; putc('-', f); if (is_tainted(value)) { - int q = quoter_for_address(value); + const uschar * quoter_name; putc('-', f); - if (is_real_quoter(q)) fprintf(f, "(%s)", lookup_list[q]->name); + (void) quoter_for_address(value, "er_name); + if (quoter_name) + fprintf(f, "(%s)", quoter_name); } fprintf(f, "acl%c %s %d\n%s\n", name[0], name+1, Ustrlen(value), value); } @@ -4927,7 +5195,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; @@ -4937,7 +5205,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)