X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/b752461e73d65163325bbf35dd55268e36e7f572..cf3fecb9e873df38a9245775a3887e73a8716083:/src/src/expand.c diff --git a/src/src/expand.c b/src/src/expand.c index f7c8f0537..e0c571ade 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -2,9 +2,10 @@ * Exim - an Internet mail transport agent * *************************************************/ +/* Copyright (c) The Exim Maintainers 2020 - 2023 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ -/* Copyright (c) The Exim Maintainers 2020 - 2021 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* Functions for handling string expansion. */ @@ -12,16 +13,21 @@ #include "exim.h" -/* Recursively called function */ +#ifdef MACRO_PREDEF +# include "macro_predef.h" +#endif -static uschar *expand_string_internal(const uschar *, BOOL, const uschar **, BOOL, BOOL, BOOL *); -static int_eximarith_t expanded_string_integer(const uschar *, BOOL); +typedef unsigned esi_flags; +#define ESI_NOFLAGS 0 +#define ESI_BRACE_ENDS BIT(0) /* expansion should stop at } */ +#define ESI_HONOR_DOLLAR BIT(1) /* $ is meaningfull */ +#define ESI_SKIPPING BIT(2) /* value will not be needed */ #ifdef STAND_ALONE # ifndef SUPPORT_CRYPTEQ # define SUPPORT_CRYPTEQ # endif -#endif +#endif /*!STAND_ALONE*/ #ifdef LOOKUP_LDAP # include "lookups/ldap.h" @@ -224,6 +230,7 @@ static uschar *op_table_main[] = { US"expand", US"h", US"hash", + US"headerwrap", US"hex2b64", US"hexquote", US"ipv6denorm", @@ -271,6 +278,7 @@ enum { EOP_EXPAND, EOP_H, EOP_HASH, + EOP_HEADERWRAP, EOP_HEX2B64, EOP_HEXQUOTE, EOP_IPV6DENORM, @@ -444,9 +452,9 @@ enum vtypes { vtype_pspace, /* partition space; value is T/F for spool/log */ vtype_pinodes, /* partition inodes; value is T/F for spool/log */ vtype_cert /* SSL certificate */ - #ifndef DISABLE_DKIM +#ifndef DISABLE_DKIM ,vtype_dkim /* Lookup of value in DKIM signature */ - #endif +#endif }; /* Type for main variable table */ @@ -465,8 +473,8 @@ typedef struct { int *length; } alblock; -static uschar * fn_recipients(void); typedef uschar * stringptr_fn_t(void); +static uschar * fn_recipients(void); static uschar * fn_queue_size(void); /* This table must be kept in alphabetical order. */ @@ -583,9 +591,9 @@ static var_entry var_table[] = { { "interface_address", vtype_stringptr, &interface_address }, { "interface_port", vtype_int, &interface_port }, { "item", vtype_stringptr, &iterate_item }, - #ifdef LOOKUP_LDAP +#ifdef LOOKUP_LDAP { "ldap_dn", vtype_stringptr, &eldap_dn }, - #endif +#endif { "load_average", vtype_load_avg, NULL }, { "local_part", vtype_stringptr, &deliver_localpart }, { "local_part_data", vtype_stringptr, &deliver_localpart_data }, @@ -672,7 +680,7 @@ static var_entry var_table[] = { { "qualify_domain", vtype_stringptr, &qualify_domain_sender }, { "qualify_recipient", vtype_stringptr, &qualify_domain_recipient }, { "queue_name", vtype_stringptr, &queue_name }, - { "queue_size", vtype_string_func, &fn_queue_size }, + { "queue_size", vtype_string_func, (void *) &fn_queue_size }, { "rcpt_count", vtype_int, &rcpt_count }, { "rcpt_defer_count", vtype_int, &rcpt_defer_count }, { "rcpt_fail_count", vtype_int, &rcpt_fail_count }, @@ -686,6 +694,7 @@ static var_entry var_table[] = { { "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure }, { "recipients", vtype_string_func, (void *) &fn_recipients }, { "recipients_count", vtype_int, &recipients_count }, + { "regex_cachesize", vtype_int, ®ex_cachesize },/* undocumented; devel observability */ #ifdef WITH_CONTENT_SCAN { "regex_match_string", vtype_stringptr, ®ex_match_string }, #endif @@ -703,6 +712,7 @@ static var_entry var_table[] = { { "sender_fullhost", vtype_stringptr, &sender_fullhost }, { "sender_helo_dnssec", vtype_bool, &sender_helo_dnssec }, { "sender_helo_name", vtype_stringptr, &sender_helo_name }, + { "sender_helo_verified",vtype_string_func, (void *) &sender_helo_verified_boolstr }, { "sender_host_address", vtype_stringptr, &sender_host_address }, { "sender_host_authenticated",vtype_stringptr, &sender_host_authenticated }, { "sender_host_dnssec", vtype_bool, &sender_host_dnssec }, @@ -750,17 +760,8 @@ static var_entry var_table[] = { { "spool_directory", vtype_stringptr, &spool_directory }, { "spool_inodes", vtype_pinodes, (void *)TRUE }, { "spool_space", vtype_pspace, (void *)TRUE }, -#ifdef EXPERIMENTAL_SRS_ALT - { "srs_db_address", vtype_stringptr, &srs_db_address }, - { "srs_db_key", vtype_stringptr, &srs_db_key }, - { "srs_orig_recipient", vtype_stringptr, &srs_orig_recipient }, - { "srs_orig_sender", vtype_stringptr, &srs_orig_sender }, -#endif -#if defined(EXPERIMENTAL_SRS_ALT) || defined(SUPPORT_SRS) +#ifdef SUPPORT_SRS { "srs_recipient", vtype_stringptr, &srs_recipient }, -#endif -#ifdef EXPERIMENTAL_SRS_ALT - { "srs_status", vtype_stringptr, &srs_status }, #endif { "thisaddress", vtype_stringptr, &filter_thisaddress }, @@ -831,7 +832,75 @@ static var_entry var_table[] = { { "warnmsg_recipients", vtype_stringptr, &warnmsg_recipients } }; -static int var_table_size = nelem(var_table); +#ifdef MACRO_PREDEF + +/* dummies */ +uschar * fn_arc_domains(void) {return NULL;} +uschar * fn_hdrs_added(void) {return NULL;} +uschar * fn_queue_size(void) {return NULL;} +uschar * fn_recipients(void) {return NULL;} +uschar * sender_helo_verified_boolstr(void) {return NULL;} +uschar * smtp_cmd_hist(void) {return NULL;} + + + +static void +expansion_items(void) +{ +uschar buf[64]; +for (int i = 0; i < nelem(item_table); i++) + { + spf(buf, sizeof(buf), CUS"_EXP_ITEM_%T", item_table[i]); + builtin_macro_create(buf); + } +} +static void +expansion_operators(void) +{ +uschar buf[64]; +for (int i = 0; i < nelem(op_table_underscore); i++) + { + spf(buf, sizeof(buf), CUS"_EXP_OP_%T", op_table_underscore[i]); + builtin_macro_create(buf); + } +for (int i = 0; i < nelem(op_table_main); i++) + { + spf(buf, sizeof(buf), CUS"_EXP_OP_%T", op_table_main[i]); + builtin_macro_create(buf); + } +} +static void +expansion_conditions(void) +{ +uschar buf[64]; +for (int i = 0; i < nelem(cond_table); i++) + { + spf(buf, sizeof(buf), CUS"_EXP_COND_%T", cond_table[i]); + builtin_macro_create(buf); + } +} +static void +expansion_variables(void) +{ +uschar buf[64]; +for (int i = 0; i < nelem(var_table); i++) + { + spf(buf, sizeof(buf), CUS"_EXP_VAR_%T", var_table[i].name); + builtin_macro_create(buf); + } +} + +void +expansions(void) +{ +expansion_items(); +expansion_operators(); +expansion_conditions(); +expansion_variables(); +} + +#else /*!MACRO_PREDEF*/ + static uschar var_buffer[256]; static BOOL malformed_header; @@ -866,6 +935,10 @@ static uschar *mtable_sticky[] = #define FH_WANT_RAW BIT(1) #define FH_WANT_LIST BIT(2) +/* Recursively called function */ +static uschar *expand_string_internal(const uschar *, esi_flags, const uschar **, BOOL *, BOOL *); +static int_eximarith_t expanded_string_integer(const uschar *, BOOL); + /************************************************* * Tables for UTF-8 support * @@ -1204,7 +1277,7 @@ static var_entry * find_var_ent(uschar * name) { int first = 0; -int last = var_table_size; +int last = nelem(var_table); while (last > first) { @@ -1297,7 +1370,7 @@ expand_getlistele(int field, const uschar * list) const uschar * tlist = list; int sep = 0; /* Tainted mem for the throwaway element copies */ -uschar * dummy = store_get(2, TRUE); +uschar * dummy = store_get(2, GET_TAINTED); if (field < 0) { @@ -1593,12 +1666,13 @@ Returns: NULL if the header does not exist, else a pointer to a new */ static uschar * -find_header(uschar *name, int *newsize, unsigned flags, const uschar *charset) +find_header(uschar * name, int * newsize, unsigned flags, const uschar * charset) { BOOL found = !name; int len = name ? Ustrlen(name) : 0; BOOL comma = FALSE; gstring * g = NULL; +uschar * rawhdr; for (header_line * h = header_list; h; h = h->next) if (h->type != htype_old && h->text) /* NULL => Received: placeholder */ @@ -1661,8 +1735,9 @@ if (!g) return US""; /* That's all we do for raw header expansion. */ *newsize = g->size; +rawhdr = string_from_gstring(g); if (flags & FH_WANT_RAW) - return string_from_gstring(g); + return rawhdr; /* Otherwise do RFC 2047 decoding, translating the charset if requested. The rfc2047_decode2() function can return an error with decoded data if the @@ -1670,12 +1745,12 @@ charset translation fails. If decoding fails, it returns NULL. */ else { - uschar * error, * decoded = rfc2047_decode2(string_from_gstring(g), + uschar * error, * decoded = rfc2047_decode2(rawhdr, check_rfc2047_length, charset, '?', NULL, newsize, &error); if (error) DEBUG(D_any) debug_printf("*** error in RFC 2047 decoding: %s\n" - " input was: %s\n", error, g->s); - return decoded ? decoded : string_from_gstring(g); + " input was: %s\n", error, rawhdr); + return decoded ? decoded : rawhdr; } } @@ -1740,7 +1815,7 @@ for (int i = 0; i < recipients_count; i++) s = recipients_list[i].address; g = string_append2_listele_n(g, US", ", s, Ustrlen(s)); } -return g ? g->s : NULL; +return string_from_gstring(g); } @@ -1757,9 +1832,7 @@ uschar buf[16]; int fd; ssize_t len; const uschar * where; -#ifndef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS uschar * sname; -#endif if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) { @@ -1767,17 +1840,9 @@ if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) return NULL; } -#ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS -sa_un.sun_path[0] = 0; /* Abstract local socket addr - Linux-specific? */ -len = offsetof(struct sockaddr_un, sun_path) + 1 - + snprintf(sa_un.sun_path+1, sizeof(sa_un.sun_path)-1, "exim_%d", getpid()); -#else -sname = string_sprintf("%s/p_%d", spool_directory, getpid()); -len = offsetof(struct sockaddr_un, sun_path) - + snprintf(sa_un.sun_path, sizeof(sa_un.sun_path), "%s", sname); -#endif +len = daemon_client_sockname(&sa_un, &sname); -if (bind(fd, (const struct sockaddr *)&sa_un, len) < 0) +if (bind(fd, (const struct sockaddr *)&sa_un, (socklen_t)len) < 0) { where = US"bind"; goto bad; } #ifdef notdef @@ -1786,17 +1851,7 @@ debug_printf("local addr '%s%s'\n", sa_un.sun_path + (*sa_un.sun_path ? 0 : 1)); #endif -#ifdef EXIM_HAVE_ABSTRACT_UNIX_SOCKETS -sa_un.sun_path[0] = 0; /* Abstract local socket addr - Linux-specific? */ -len = offsetof(struct sockaddr_un, sun_path) + 1 - + snprintf(sa_un.sun_path+1, sizeof(sa_un.sun_path)-1, "%s", - expand_string(notifier_socket)); -#else -len = offsetof(struct sockaddr_un, sun_path) - + snprintf(sa_un.sun_path, sizeof(sa_un.sun_path), "%s", - expand_string(notifier_socket)); -#endif - +len = daemon_notifier_sockname(&sa_un); if (connect(fd, (const struct sockaddr *)&sa_un, len) < 0) { where = US"connect"; goto bad2; } @@ -1882,7 +1937,7 @@ else if (Ustrncmp(name, "r_", 2) == 0) return node ? node->data.ptr : strict_acl_vars ? NULL : US""; } -/* Handle $auth variables. */ +/* Handle $auth, $regex variables. */ if (Ustrncmp(name, "auth", 4) == 0) { @@ -1891,6 +1946,7 @@ if (Ustrncmp(name, "auth", 4) == 0) if (!*endptr && n != 0 && n <= AUTH_VARS) return auth_vars[n-1] ? auth_vars[n-1] : US""; } +#ifdef WITH_CONTENT_SCAN else if (Ustrncmp(name, "regex", 5) == 0) { uschar *endptr; @@ -1898,6 +1954,7 @@ else if (Ustrncmp(name, "regex", 5) == 0) if (!*endptr && n != 0 && n <= REGEX_VARS) return regex_vars[n-1] ? regex_vars[n-1] : US""; } +#endif /* For all other variables, search the table */ @@ -1980,11 +2037,12 @@ switch (vp->type) if (!*ss && deliver_datafile >= 0) /* Read body when needed */ { uschar * body; - off_t start_offset = SPOOL_DATA_START_OFFSET; + off_t start_offset_o = spool_data_start_offset(message_id); + off_t start_offset = start_offset_o; int len = message_body_visible; if (len > message_size) len = message_size; - *ss = body = store_get(len+1, TRUE); + *ss = body = store_get(len+1, GET_TAINTED); body[0] = 0; if (vp->type == vtype_msgbody_end) { @@ -1992,8 +2050,8 @@ switch (vp->type) if (fstat(deliver_datafile, &statbuf) == 0) { start_offset = statbuf.st_size - len; - if (start_offset < SPOOL_DATA_START_OFFSET) - start_offset = SPOOL_DATA_START_OFFSET; + if (start_offset < start_offset_o) + start_offset = start_offset_o; } } if (lseek(deliver_datafile, start_offset, SEEK_SET) < 0) @@ -2123,27 +2181,33 @@ Arguments: n maximum number of substrings m minimum required sptr points to current string pointer - skipping the skipping flag + flags + skipping the skipping flag check_end if TRUE, check for final '}' name name of item, for error message resetok if not NULL, pointer to flag - write FALSE if unsafe to reset - the store. + the store + textonly_p if not NULL, pointer to bitmask of which subs were text-only + (did not change when expended) -Returns: 0 OK; string pointer updated +Returns: -1 OK; string pointer updated, but in "skipping" mode + 0 OK; string pointer updated 1 curly bracketing error (too few arguments) 2 too many arguments (only if check_end is set); message set 3 other error (expansion failure) */ static int -read_subs(uschar **sub, int n, int m, const uschar **sptr, BOOL skipping, - BOOL check_end, uschar *name, BOOL *resetok) +read_subs(uschar ** sub, int n, int m, const uschar ** sptr, esi_flags flags, + BOOL check_end, uschar * name, BOOL * resetok, unsigned * textonly_p) { -const uschar *s = *sptr; +const uschar * s = *sptr; +unsigned textonly_l = 0; Uskip_whitespace(&s); for (int i = 0; i < n; i++) { + BOOL textonly; if (*s != '{') { if (i < m) @@ -2155,11 +2219,14 @@ for (int i = 0; i < n; i++) sub[i] = NULL; break; } - if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok))) + if (!(sub[i] = expand_string_internal(s+1, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags & ESI_SKIPPING, &s, resetok, + textonly_p ? &textonly : NULL))) return 3; if (*s++ != '}') return 1; + if (textonly_p && textonly) textonly_l |= BIT(i); Uskip_whitespace(&s); - } + } /*{*/ if (check_end && *s++ != '}') { if (s[-1] == '{') @@ -2172,8 +2239,9 @@ if (check_end && *s++ != '}') return 1; } +if (textonly_p) *textonly_p = textonly_l; *sptr = s; -return 0; +return flags & ESI_SKIPPING ? -1 : 0; } @@ -2316,19 +2384,26 @@ static uschar * json_nextinlist(const uschar ** list) { unsigned array_depth = 0, object_depth = 0; +BOOL quoted = FALSE; const uschar * s = *list, * item; skip_whitespace(&s); for (item = s; - *s && (*s != ',' || array_depth != 0 || object_depth != 0); + *s && (*s != ',' || array_depth != 0 || object_depth != 0 || quoted); s++) - switch (*s) + if (!quoted) switch (*s) { case '[': array_depth++; break; case ']': array_depth--; break; case '{': object_depth++; break; case '}': object_depth--; break; + case '"': quoted = TRUE; + } + else switch(*s) + { + case '\\': s++; break; /* backslash protects one char */ + case '"': quoted = FALSE; break; } *list = *s ? s+1 : s; if (item == s) return NULL; @@ -2532,11 +2607,11 @@ Returns: a pointer to the first character after the condition, or */ static const uschar * -eval_condition(const uschar *s, BOOL *resetok, BOOL *yield) +eval_condition(const uschar * s, BOOL * resetok, BOOL * yield) { BOOL testfor = TRUE; BOOL tempcond, combined_cond; -BOOL *subcondptr; +BOOL * subcondptr; BOOL sub2_honour_dollar = TRUE; BOOL is_forany, is_json, is_jsons; int rc, cond_type; @@ -2544,7 +2619,8 @@ int_eximarith_t num[2]; struct stat statbuf; uschar * opname; uschar name[256]; -const uschar *sub[10]; +const uschar * sub[10]; +unsigned sub_textonly = 0; for (;;) if (Uskip_whitespace(&s) == '!') { testfor = !testfor; s++; } else break; @@ -2638,8 +2714,14 @@ switch(cond_type = identify_operator(&s, &opname)) if (Uskip_whitespace(&s) != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ - sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE, resetok); - if (!sub[0]) return NULL; + { + BOOL textonly; + sub[0] = expand_string_internal(s+1, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | (yield ? ESI_NOFLAGS : ESI_SKIPPING), + &s, resetok, &textonly); + if (!sub[0]) return NULL; + if (textonly) sub_textonly |= BIT(0); + } /* {-for-text-editors */ if (*s++ != '}') goto COND_FAILED_CURLY_END; @@ -2736,8 +2818,8 @@ switch(cond_type = identify_operator(&s, &opname)) Uskip_whitespace(&s); if (*s++ != '{') goto COND_FAILED_CURLY_START; /*}*/ - switch(read_subs(sub, nelem(sub), 1, - &s, yield == NULL, TRUE, name, resetok)) + switch(read_subs(sub, nelem(sub), 1, &s, + yield ? ESI_NOFLAGS : ESI_SKIPPING, TRUE, name, resetok, NULL)) { case 1: expand_string_message = US"too few arguments or bracketing " "error for acl"; @@ -2788,8 +2870,8 @@ switch(cond_type = identify_operator(&s, &opname)) uschar *sub[4]; Uskip_whitespace(&s); if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ - switch(read_subs(sub, nelem(sub), 2, &s, yield == NULL, TRUE, name, - resetok)) + switch(read_subs(sub, nelem(sub), 2, &s, + yield ? ESI_NOFLAGS : ESI_SKIPPING, TRUE, name, resetok, NULL)) { case 1: expand_string_message = US"too few arguments or bracketing " "error for saslauthd"; @@ -2857,12 +2939,16 @@ switch(cond_type = identify_operator(&s, &opname)) for (int i = 0; i < 2; i++) { + BOOL textonly; /* Sometimes, we don't expand substrings; too many insecure configurations created using match_address{}{} and friends, where the second param includes information from untrustworthy sources. */ - BOOL honour_dollar = TRUE; - if ((i > 0) && !sub2_honour_dollar) - honour_dollar = FALSE; + /*XXX is this moot given taint-tracking? */ + + esi_flags flags = ESI_BRACE_ENDS; + + if (!(i > 0 && !sub2_honour_dollar)) flags |= ESI_HONOR_DOLLAR; + if (!yield) flags |= ESI_SKIPPING; if (Uskip_whitespace(&s) != '{') { @@ -2871,9 +2957,9 @@ switch(cond_type = identify_operator(&s, &opname)) "after \"%s\"", opname); return NULL; } - if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL, - honour_dollar, resetok))) + if (!(sub[i] = expand_string_internal(s+1, flags, &s, resetok, &textonly))) return NULL; + if (textonly) sub_textonly |= BIT(i); DEBUG(D_expand) if (i == 1 && !sub2_honour_dollar && Ustrchr(sub[1], '$')) debug_printf_indent("WARNING: the second arg is NOT expanded," " for security reasons\n"); @@ -2907,152 +2993,125 @@ switch(cond_type = identify_operator(&s, &opname)) { case ECOND_NUM_E: case ECOND_NUM_EE: - tempcond = (num[0] == num[1]); - break; + tempcond = (num[0] == num[1]); break; case ECOND_NUM_G: - tempcond = (num[0] > num[1]); - break; + tempcond = (num[0] > num[1]); break; case ECOND_NUM_GE: - tempcond = (num[0] >= num[1]); - break; + tempcond = (num[0] >= num[1]); break; case ECOND_NUM_L: - tempcond = (num[0] < num[1]); - break; + tempcond = (num[0] < num[1]); break; case ECOND_NUM_LE: - tempcond = (num[0] <= num[1]); - break; + tempcond = (num[0] <= num[1]); break; case ECOND_STR_LT: - tempcond = (Ustrcmp(sub[0], sub[1]) < 0); - break; + tempcond = (Ustrcmp(sub[0], sub[1]) < 0); break; case ECOND_STR_LTI: - tempcond = (strcmpic(sub[0], sub[1]) < 0); - break; + tempcond = (strcmpic(sub[0], sub[1]) < 0); break; case ECOND_STR_LE: - tempcond = (Ustrcmp(sub[0], sub[1]) <= 0); - break; + tempcond = (Ustrcmp(sub[0], sub[1]) <= 0); break; case ECOND_STR_LEI: - tempcond = (strcmpic(sub[0], sub[1]) <= 0); - break; + tempcond = (strcmpic(sub[0], sub[1]) <= 0); break; case ECOND_STR_EQ: - tempcond = (Ustrcmp(sub[0], sub[1]) == 0); - break; + tempcond = (Ustrcmp(sub[0], sub[1]) == 0); break; case ECOND_STR_EQI: - tempcond = (strcmpic(sub[0], sub[1]) == 0); - break; + tempcond = (strcmpic(sub[0], sub[1]) == 0); break; case ECOND_STR_GT: - tempcond = (Ustrcmp(sub[0], sub[1]) > 0); - break; + tempcond = (Ustrcmp(sub[0], sub[1]) > 0); break; case ECOND_STR_GTI: - tempcond = (strcmpic(sub[0], sub[1]) > 0); - break; + tempcond = (strcmpic(sub[0], sub[1]) > 0); break; case ECOND_STR_GE: - tempcond = (Ustrcmp(sub[0], sub[1]) >= 0); - break; + tempcond = (Ustrcmp(sub[0], sub[1]) >= 0); break; case ECOND_STR_GEI: - tempcond = (strcmpic(sub[0], sub[1]) >= 0); - break; + tempcond = (strcmpic(sub[0], sub[1]) >= 0); break; case ECOND_MATCH: /* Regular expression match */ { - const pcre2_code * re; - PCRE2_SIZE offset; - int err; - - if (!(re = pcre2_compile((PCRE2_SPTR)sub[1], PCRE2_ZERO_TERMINATED, - PCRE_COPT, &err, &offset, pcre_cmp_ctx))) - { - uschar errbuf[128]; - pcre2_get_error_message(err, errbuf, sizeof(errbuf)); - expand_string_message = string_sprintf("regular expression error in " - "\"%s\": %s at offset %ld", sub[1], errbuf, (long)offset); + const pcre2_code * re = regex_compile(sub[1], + sub_textonly & BIT(1) ? MCS_CACHEABLE : MCS_NOFLAGS, + &expand_string_message, pcre_gen_cmp_ctx); + if (!re) return NULL; - } tempcond = regex_match_and_setup(re, sub[0], 0, -1); break; } case ECOND_MATCH_ADDRESS: /* Match in an address list */ - rc = match_address_list(sub[0], TRUE, FALSE, &(sub[1]), NULL, -1, 0, NULL); - goto MATCHED_SOMETHING; + rc = match_address_list(sub[0], TRUE, FALSE, &(sub[1]), NULL, -1, 0, + CUSS &lookup_value); + goto MATCHED_SOMETHING; case ECOND_MATCH_DOMAIN: /* Match in a domain list */ - rc = match_isinlist(sub[0], &(sub[1]), 0, &domainlist_anchor, NULL, - MCL_DOMAIN + MCL_NOEXPAND, TRUE, NULL); - goto MATCHED_SOMETHING; + rc = match_isinlist(sub[0], &(sub[1]), 0, &domainlist_anchor, NULL, + MCL_DOMAIN + MCL_NOEXPAND, TRUE, CUSS &lookup_value); + goto MATCHED_SOMETHING; case ECOND_MATCH_IP: /* Match IP address in a host list */ - if (sub[0][0] != 0 && string_is_ip_address(sub[0], NULL) == 0) - { - expand_string_message = string_sprintf("\"%s\" is not an IP address", - sub[0]); - return NULL; - } - else - { - unsigned int *nullcache = NULL; - check_host_block cb; - - cb.host_name = US""; - cb.host_address = sub[0]; - - /* If the host address starts off ::ffff: it is an IPv6 address in - IPv4-compatible mode. Find the IPv4 part for checking against IPv4 - addresses. */ - - cb.host_ipv4 = (Ustrncmp(cb.host_address, "::ffff:", 7) == 0)? - cb.host_address + 7 : cb.host_address; - - rc = match_check_list( - &sub[1], /* the list */ - 0, /* separator character */ - &hostlist_anchor, /* anchor pointer */ - &nullcache, /* cache pointer */ - check_host, /* function for testing */ - &cb, /* argument for function */ - MCL_HOST, /* type of check */ - sub[0], /* text for debugging */ - NULL); /* where to pass back data */ - } - goto MATCHED_SOMETHING; + if (sub[0][0] != 0 && string_is_ip_address(sub[0], NULL) == 0) + { + expand_string_message = string_sprintf("\"%s\" is not an IP address", + sub[0]); + return NULL; + } + else + { + unsigned int *nullcache = NULL; + check_host_block cb; + + cb.host_name = US""; + cb.host_address = sub[0]; + + /* If the host address starts off ::ffff: it is an IPv6 address in + IPv4-compatible mode. Find the IPv4 part for checking against IPv4 + addresses. */ + + cb.host_ipv4 = (Ustrncmp(cb.host_address, "::ffff:", 7) == 0)? + cb.host_address + 7 : cb.host_address; + + rc = match_check_list( + &sub[1], /* the list */ + 0, /* separator character */ + &hostlist_anchor, /* anchor pointer */ + &nullcache, /* cache pointer */ + check_host, /* function for testing */ + &cb, /* argument for function */ + MCL_HOST, /* type of check */ + sub[0], /* text for debugging */ + CUSS &lookup_value); /* where to pass back data */ + } + goto MATCHED_SOMETHING; case ECOND_MATCH_LOCAL_PART: - rc = match_isinlist(sub[0], &(sub[1]), 0, &localpartlist_anchor, NULL, - MCL_LOCALPART + MCL_NOEXPAND, TRUE, NULL); - /* Fall through */ - /* VVVVVVVVVVVV */ - MATCHED_SOMETHING: - switch(rc) - { - case OK: - tempcond = TRUE; - break; - - case FAIL: - tempcond = FALSE; - break; + rc = match_isinlist(sub[0], &(sub[1]), 0, &localpartlist_anchor, NULL, + MCL_LOCALPART + MCL_NOEXPAND, TRUE, CUSS &lookup_value); + /* Fall through */ + /* VVVVVVVVVVVV */ + MATCHED_SOMETHING: + switch(rc) + { + case OK: tempcond = TRUE; break; + case FAIL: tempcond = FALSE; break; - case DEFER: - expand_string_message = string_sprintf("unable to complete match " - "against \"%s\": %s", sub[1], search_error_message); - return NULL; - } + case DEFER: + expand_string_message = string_sprintf("unable to complete match " + "against \"%s\": %s", sub[1], search_error_message); + return NULL; + } - break; + break; /* Various "encrypted" comparisons. If the second string starts with "{" then an encryption type is given. Default to crypt() or crypt16() @@ -3061,138 +3120,138 @@ switch(cond_type = identify_operator(&s, &opname)) case ECOND_CRYPTEQ: #ifndef SUPPORT_CRYPTEQ - goto COND_FAILED_NOT_COMPILED; + goto COND_FAILED_NOT_COMPILED; #else - if (strncmpic(sub[1], US"{md5}", 5) == 0) - { - int sublen = Ustrlen(sub[1]+5); - md5 base; - uschar digest[16]; + if (strncmpic(sub[1], US"{md5}", 5) == 0) + { + int sublen = Ustrlen(sub[1]+5); + md5 base; + uschar digest[16]; - md5_start(&base); - md5_end(&base, sub[0], Ustrlen(sub[0]), digest); + md5_start(&base); + md5_end(&base, sub[0], Ustrlen(sub[0]), digest); - /* If the length that we are comparing against is 24, the MD5 digest - is expressed as a base64 string. This is the way LDAP does it. However, - some other software uses a straightforward hex representation. We assume - this if the length is 32. Other lengths fail. */ + /* If the length that we are comparing against is 24, the MD5 digest + is expressed as a base64 string. This is the way LDAP does it. However, + some other software uses a straightforward hex representation. We assume + this if the length is 32. Other lengths fail. */ - if (sublen == 24) - { - uschar *coded = b64encode(CUS digest, 16); - DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n" - " subject=%s\n crypted=%s\n", coded, sub[1]+5); - tempcond = (Ustrcmp(coded, sub[1]+5) == 0); - } - else if (sublen == 32) - { - uschar coded[36]; - for (int i = 0; i < 16; i++) sprintf(CS (coded+2*i), "%02X", digest[i]); - coded[32] = 0; - DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n" - " subject=%s\n crypted=%s\n", coded, sub[1]+5); - tempcond = (strcmpic(coded, sub[1]+5) == 0); - } - else - { - DEBUG(D_auth) debug_printf("crypteq: length for MD5 not 24 or 32: " - "fail\n crypted=%s\n", sub[1]+5); - tempcond = FALSE; - } - } + if (sublen == 24) + { + uschar *coded = b64encode(CUS digest, 16); + DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n" + " subject=%s\n crypted=%s\n", coded, sub[1]+5); + tempcond = (Ustrcmp(coded, sub[1]+5) == 0); + } + else if (sublen == 32) + { + uschar coded[36]; + for (int i = 0; i < 16; i++) sprintf(CS (coded+2*i), "%02X", digest[i]); + coded[32] = 0; + DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n" + " subject=%s\n crypted=%s\n", coded, sub[1]+5); + tempcond = (strcmpic(coded, sub[1]+5) == 0); + } + else + { + DEBUG(D_auth) debug_printf("crypteq: length for MD5 not 24 or 32: " + "fail\n crypted=%s\n", sub[1]+5); + tempcond = FALSE; + } + } - else if (strncmpic(sub[1], US"{sha1}", 6) == 0) - { - int sublen = Ustrlen(sub[1]+6); - hctx h; - uschar digest[20]; + else if (strncmpic(sub[1], US"{sha1}", 6) == 0) + { + int sublen = Ustrlen(sub[1]+6); + hctx h; + uschar digest[20]; - sha1_start(&h); - sha1_end(&h, sub[0], Ustrlen(sub[0]), digest); + sha1_start(&h); + sha1_end(&h, sub[0], Ustrlen(sub[0]), digest); - /* If the length that we are comparing against is 28, assume the SHA1 - digest is expressed as a base64 string. If the length is 40, assume a - straightforward hex representation. Other lengths fail. */ + /* If the length that we are comparing against is 28, assume the SHA1 + digest is expressed as a base64 string. If the length is 40, assume a + straightforward hex representation. Other lengths fail. */ - if (sublen == 28) - { - uschar *coded = b64encode(CUS digest, 20); - DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n" - " subject=%s\n crypted=%s\n", coded, sub[1]+6); - tempcond = (Ustrcmp(coded, sub[1]+6) == 0); - } - else if (sublen == 40) - { - uschar coded[44]; - for (int i = 0; i < 20; i++) sprintf(CS (coded+2*i), "%02X", digest[i]); - coded[40] = 0; - DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n" - " subject=%s\n crypted=%s\n", coded, sub[1]+6); - tempcond = (strcmpic(coded, sub[1]+6) == 0); - } - else - { - DEBUG(D_auth) debug_printf("crypteq: length for SHA-1 not 28 or 40: " - "fail\n crypted=%s\n", sub[1]+6); - tempcond = FALSE; - } - } + if (sublen == 28) + { + uschar *coded = b64encode(CUS digest, 20); + DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n" + " subject=%s\n crypted=%s\n", coded, sub[1]+6); + tempcond = (Ustrcmp(coded, sub[1]+6) == 0); + } + else if (sublen == 40) + { + uschar coded[44]; + for (int i = 0; i < 20; i++) sprintf(CS (coded+2*i), "%02X", digest[i]); + coded[40] = 0; + DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n" + " subject=%s\n crypted=%s\n", coded, sub[1]+6); + tempcond = (strcmpic(coded, sub[1]+6) == 0); + } + else + { + DEBUG(D_auth) debug_printf("crypteq: length for SHA-1 not 28 or 40: " + "fail\n crypted=%s\n", sub[1]+6); + tempcond = FALSE; + } + } - else /* {crypt} or {crypt16} and non-{ at start */ - /* }-for-text-editors */ - { - int which = 0; - uschar *coded; + else /* {crypt} or {crypt16} and non-{ at start */ + /* }-for-text-editors */ + { + int which = 0; + uschar *coded; - if (strncmpic(sub[1], US"{crypt}", 7) == 0) - { - sub[1] += 7; - which = 1; - } - else if (strncmpic(sub[1], US"{crypt16}", 9) == 0) - { - sub[1] += 9; - which = 2; - } - else if (sub[1][0] == '{') /* }-for-text-editors */ - { - expand_string_message = string_sprintf("unknown encryption mechanism " - "in \"%s\"", sub[1]); - return NULL; - } + if (strncmpic(sub[1], US"{crypt}", 7) == 0) + { + sub[1] += 7; + which = 1; + } + else if (strncmpic(sub[1], US"{crypt16}", 9) == 0) + { + sub[1] += 9; + which = 2; + } + else if (sub[1][0] == '{') /* }-for-text-editors */ + { + expand_string_message = string_sprintf("unknown encryption mechanism " + "in \"%s\"", sub[1]); + return NULL; + } - switch(which) - { - case 0: coded = US DEFAULT_CRYPT(CS sub[0], CS sub[1]); break; - case 1: coded = US crypt(CS sub[0], CS sub[1]); break; - default: coded = US crypt16(CS sub[0], CS sub[1]); break; - } + switch(which) + { + case 0: coded = US DEFAULT_CRYPT(CS sub[0], CS sub[1]); break; + case 1: coded = US crypt(CS sub[0], CS sub[1]); break; + default: coded = US crypt16(CS sub[0], CS sub[1]); break; + } - #define STR(s) # s - #define XSTR(s) STR(s) - DEBUG(D_auth) debug_printf("crypteq: using %s()\n" - " subject=%s\n crypted=%s\n", - which == 0 ? XSTR(DEFAULT_CRYPT) : which == 1 ? "crypt" : "crypt16", - coded, sub[1]); - #undef STR - #undef XSTR - - /* If the encrypted string contains fewer than two characters (for the - salt), force failure. Otherwise we get false positives: with an empty - string the yield of crypt() is an empty string! */ - - if (coded) - tempcond = Ustrlen(sub[1]) < 2 ? FALSE : Ustrcmp(coded, sub[1]) == 0; - else if (errno == EINVAL) - tempcond = FALSE; - else - { - expand_string_message = string_sprintf("crypt error: %s\n", - US strerror(errno)); - return NULL; + #define STR(s) # s + #define XSTR(s) STR(s) + DEBUG(D_auth) debug_printf("crypteq: using %s()\n" + " subject=%s\n crypted=%s\n", + which == 0 ? XSTR(DEFAULT_CRYPT) : which == 1 ? "crypt" : "crypt16", + coded, sub[1]); + #undef STR + #undef XSTR + + /* If the encrypted string contains fewer than two characters (for the + salt), force failure. Otherwise we get false positives: with an empty + string the yield of crypt() is an empty string! */ + + if (coded) + tempcond = Ustrlen(sub[1]) < 2 ? FALSE : Ustrcmp(coded, sub[1]) == 0; + else if (errno == EINVAL) + tempcond = FALSE; + else + { + expand_string_message = string_sprintf("crypt error: %s\n", + US strerror(errno)); + return NULL; + } } - } - break; + break; #endif /* SUPPORT_CRYPTEQ */ case ECOND_INLIST: @@ -3215,6 +3274,7 @@ switch(cond_type = identify_operator(&s, &opname)) if (compare(sub[0], iterate_item) == 0) { tempcond = TRUE; + lookup_value = iterate_item; break; } } @@ -3301,7 +3361,9 @@ switch(cond_type = identify_operator(&s, &opname)) Uskip_whitespace(&s); if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ - if (!(sub[0] = expand_string_internal(s, TRUE, &s, yield == NULL, TRUE, resetok))) + if (!(sub[0] = expand_string_internal(s, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | (yield ? ESI_NOFLAGS : ESI_SKIPPING), + &s, resetok, NULL))) return NULL; /* {-for-text-editors */ if (*s++ != '}') goto COND_FAILED_CURLY_END; @@ -3389,7 +3451,8 @@ switch(cond_type = identify_operator(&s, &opname)) if (Uskip_whitespace(&s) != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ ourname = cond_type == ECOND_BOOL_LAX ? US"bool_lax" : US"bool"; - switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname, resetok)) + switch(read_subs(sub_arg, 1, 1, &s, + yield ? ESI_NOFLAGS : ESI_SKIPPING, FALSE, ourname, resetok, NULL)) { case 1: expand_string_message = string_sprintf( "too few arguments or bracketing error for %s", @@ -3457,7 +3520,8 @@ switch(cond_type = identify_operator(&s, &opname)) uschar cksum[4]; BOOL boolvalue = FALSE; - switch(read_subs(sub, 2, 2, CUSS &s, yield == NULL, FALSE, name, resetok)) + switch(read_subs(sub, 2, 2, CUSS &s, + yield ? ESI_NOFLAGS : ESI_SKIPPING, FALSE, name, resetok, NULL)) { case 1: expand_string_message = US"too few arguments or bracketing " "error for inbound_srs"; @@ -3467,11 +3531,11 @@ switch(cond_type = identify_operator(&s, &opname)) /* Match the given local_part against the SRS-encoded pattern */ - re = regex_must_compile(US"^(?i)SRS0=([^=]+)=([A-Z2-7]+)=([^=]*)=(.*)$", - TRUE, FALSE); + re = regex_must_compile(US"^(?i)SRS0=([^=]+)=([A-Z2-7]{2})=([^=]*)=(.*)$", + MCS_CASELESS | MCS_CACHEABLE, FALSE); md = pcre2_match_data_create(4+1, pcre_gen_ctx); if (pcre2_match(re, sub[0], PCRE2_ZERO_TERMINATED, 0, PCRE_EOPT, - md, pcre_mtc_ctx) < 0) + md, pcre_gen_mtc_ctx) < 0) { DEBUG(D_expand) debug_printf("no match for SRS'd local-part pattern\n"); goto srs_result; @@ -3548,6 +3612,7 @@ switch(cond_type = identify_operator(&s, &opname)) boolvalue = TRUE; srs_result: + /* pcre2_match_data_free(md); gen ctx needs no free */ if (yield) *yield = (boolvalue == testfor); return s; } @@ -3655,7 +3720,8 @@ expanded, to check their syntax, but "skipping" is set when the result is not needed - this avoids unnecessary nested lookups. Arguments: - skipping TRUE if we were skipping when this item was reached + flags + skipping TRUE if we were skipping when this item was reached yes TRUE if the first string is to be used, else use the second save_lookup a value to put back into lookup_value before the 2nd expansion sptr points to the input string pointer @@ -3671,7 +3737,7 @@ Returns: 0 OK; lookup_value has been reset to save_lookup */ static int -process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, const uschar **sptr, +process_yesno(esi_flags flags, BOOL yes, uschar *save_lookup, const uschar **sptr, gstring ** yieldptr, uschar *type, BOOL *resetok) { int rc = 0; @@ -3679,6 +3745,8 @@ const uschar *s = *sptr; /* Local value */ uschar *sub1, *sub2; const uschar * errwhere; +flags &= ESI_SKIPPING; /* Ignore all buf the skipping flag */ + /* If there are no following strings, we substitute the contents of $value for lookups and for extractions in the success case. For the ${if item, the string "true" is substituted. In the fail case, nothing is substituted for all three @@ -3688,12 +3756,12 @@ if (skip_whitespace(&s) == '}') { if (type[0] == 'i') { - if (yes && !skipping) + if (yes && !(flags & ESI_SKIPPING)) *yieldptr = string_catn(*yieldptr, US"true", 4); } else { - if (yes && lookup_value && !skipping) + if (yes && lookup_value && !(flags & ESI_SKIPPING)) *yieldptr = string_cat(*yieldptr, lookup_value); lookup_value = save_lookup; } @@ -3705,7 +3773,7 @@ if (skip_whitespace(&s) == '}') if (*s++ != '{') { - errwhere = US"'yes' part did not start with '{'"; + errwhere = US"'yes' part did not start with '{'"; /*}}*/ goto FAILED_CURLY; } @@ -3713,9 +3781,12 @@ if (*s++ != '{') want this string. Set skipping in the call in the fail case (this will always be the case if we were already skipping). */ -sub1 = expand_string_internal(s, TRUE, &s, !yes, TRUE, resetok); +sub1 = expand_string_internal(s, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | (yes ? ESI_NOFLAGS : ESI_SKIPPING), + &s, resetok, NULL); if (sub1 == NULL && (yes || !f.expand_string_forcedfail)) goto FAILED; f.expand_string_forcedfail = FALSE; + /*{{*/ if (*s++ != '}') { errwhere = US"'yes' part did not end with '}'"; @@ -3740,14 +3811,16 @@ time, forced failures are noticed only if we want the second string. We must set skipping in the nested call if we don't want this string, or if we were already skipping. */ -if (skip_whitespace(&s) == '{') +if (skip_whitespace(&s) == '{') /*}*/ { - sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE, resetok); - if (sub2 == NULL && (!yes || !f.expand_string_forcedfail)) goto FAILED; - f.expand_string_forcedfail = FALSE; + esi_flags s_flags = ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags; + if (yes) s_flags |= ESI_SKIPPING; + sub2 = expand_string_internal(s+1, s_flags, &s, resetok, NULL); + if (!sub2 && (!yes || !f.expand_string_forcedfail)) goto FAILED; + f.expand_string_forcedfail = FALSE; /*{*/ if (*s++ != '}') { - errwhere = US"'no' part did not start with '{'"; + errwhere = US"'no' part did not start with '{'"; /*}*/ goto FAILED_CURLY; } @@ -3756,7 +3829,7 @@ if (skip_whitespace(&s) == '{') if (!yes) *yieldptr = string_cat(*yieldptr, sub2); } - + /*{{*/ /* If there is no second string, but the word "fail" is present when the use of the second string is wanted, set a flag indicating it was a forced failure rather than a syntactic error. Swallow the terminating } in case this is nested @@ -3769,9 +3842,9 @@ else if (*s != '}') s = US read_name(name, sizeof(name), s, US"_"); if (Ustrcmp(name, "fail") == 0) { - if (!yes && !skipping) + if (!yes && !(flags & ESI_SKIPPING)) { - Uskip_whitespace(&s); + Uskip_whitespace(&s); /*{{*/ if (*s++ != '}') { errwhere = US"did not close with '}' after forcedfail"; @@ -3793,7 +3866,7 @@ else if (*s != '}') /* All we have to do now is to check on the final closing brace. */ -skip_whitespace(&s); +skip_whitespace(&s); /*{{*/ if (*s++ != '}') { errwhere = US"did not close with '}'"; @@ -3843,8 +3916,8 @@ Returns: pointer to string containing the last three static uschar * prvs_daystamp(int day_offset) { -uschar *days = store_get(32, FALSE); /* Need at least 24 for cases */ -(void)string_format(days, 32, TIME_T_FMT, /* where TIME_T_FMT is %lld */ +uschar * days = store_get(32, GET_UNTAINTED); /* Need at least 24 for cases */ +(void)string_format(days, 32, TIME_T_FMT, /* where TIME_T_FMT is %lld */ (time(NULL) + day_offset*86400)/86400); return (Ustrlen(days) >= 3) ? &days[Ustrlen(days)-3] : US"100"; } @@ -3891,10 +3964,9 @@ if (Ustrlen(key) > 64) hash_source = string_catn(NULL, key_num, 1); hash_source = string_catn(hash_source, daystamp, 3); hash_source = string_cat(hash_source, address); -(void) string_from_gstring(hash_source); DEBUG(D_expand) - debug_printf_indent("prvs: hash source is '%s'\n", hash_source->s); + debug_printf_indent("prvs: hash source is '%Y'\n", hash_source); memset(innerkey, 0x36, 64); memset(outerkey, 0x5c, 64); @@ -3915,7 +3987,7 @@ chash_end(HMAC_SHA1, &h, innerhash, 20, finalhash); /* Hashing is deemed sufficient to de-taint any input data */ -p = finalhash_hex = store_get(40, FALSE); +p = finalhash_hex = store_get(40, GET_UNTAINTED); for (int i = 0; i < 3; i++) { *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4]; @@ -3946,7 +4018,7 @@ Returns: new pointer for expandable string, terminated if non-null */ gstring * -cat_file(FILE *f, gstring *yield, uschar *eol) +cat_file(FILE * f, gstring * yield, uschar * eol) { uschar buffer[1024]; @@ -3958,8 +4030,6 @@ while (Ufgets(buffer, sizeof(buffer), f)) if (eol && buffer[len]) yield = string_cat(yield, eol); } - -(void) string_from_gstring(yield); return yield; } @@ -3981,7 +4051,6 @@ while ((rc = tls_read(tls_ctx, buffer, sizeof(buffer))) > 0) /* We assume that all errors, and any returns of zero bytes, are actually EOF. */ -(void) string_from_gstring(yield); return yield; } #endif @@ -4356,7 +4425,7 @@ list = ((namedlist_block *)(t->data.ptr))->string; /* The list could be quite long so we (re)use a buffer for each element rather than getting each in new memory */ -if (is_tainted(list)) buffer = store_get(LISTNAMED_BUF_SIZE, TRUE); +if (is_tainted(list)) buffer = store_get(LISTNAMED_BUF_SIZE, GET_TAINTED); while ((item = string_nextinlist(&list, &sep, buffer, LISTNAMED_BUF_SIZE))) { uschar * buf = US" : "; @@ -4398,6 +4467,36 @@ return yield; +/************************************************/ +static void +debug_expansion_interim(const uschar * what, const uschar * value, int nchar, + BOOL skipping) +{ +DEBUG(D_noutf8) + debug_printf_indent("|"); +else + debug_printf_indent(UTF8_VERT_RIGHT); + +for (int fill = 11 - Ustrlen(what); fill > 0; fill--) + DEBUG(D_noutf8) + debug_printf("-"); + else + debug_printf(UTF8_HORIZ); + +debug_printf("%s: %.*s\n", what, nchar, value); +if (is_tainted(value)) + { + DEBUG(D_noutf8) + debug_printf_indent("%s \\__", skipping ? "| " : " "); + else + debug_printf_indent("%s", + skipping + ? UTF8_VERT " " : " " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ); + debug_printf("(tainted)\n"); + } +} + + /************************************************* * Expand string * *************************************************/ @@ -4445,15 +4544,17 @@ string expansion becoming too powerful. Arguments: string the string to be expanded - ket_ends true if expansion is to stop at } + flags + brace_ends expansion is to stop at } + honour_dollar TRUE if $ is to be expanded, + FALSE if it's just another character + skipping TRUE for recursive calls when the value isn't actually going + to be used (to allow for optimisation) left if not NULL, a pointer to the first character after the - expansion is placed here (typically used with ket_ends) - skipping TRUE for recursive calls when the value isn't actually going - to be used (to allow for optimisation) - honour_dollar TRUE if $ is to be expanded, - FALSE if it's just another character + expansion is placed here (typically used with brace_ends) resetok_p if not NULL, pointer to flag - write FALSE if unsafe to reset the store. + textonly_p if not NULL, pointer to flag - write bool for only-met-text Returns: NULL if expansion fails: expand_string_forcedfail is set TRUE if failure was forced @@ -4462,8 +4563,8 @@ Returns: NULL if expansion fails: */ static uschar * -expand_string_internal(const uschar *string, BOOL ket_ends, const uschar **left, - BOOL skipping, BOOL honour_dollar, BOOL *resetok_p) +expand_string_internal(const uschar * string, esi_flags flags, const uschar ** left, + BOOL *resetok_p, BOOL * textonly_p) { rmark reset_point = store_mark(); gstring * yield = string_get(Ustrlen(string) + 64); @@ -4471,19 +4572,19 @@ int item_type; const uschar * s = string; const uschar * save_expand_nstring[EXPAND_MAXN+1]; int save_expand_nlength[EXPAND_MAXN+1]; -BOOL resetok = TRUE, first = TRUE; +BOOL resetok = TRUE, first = TRUE, textonly = TRUE; expand_level++; f.expand_string_forcedfail = FALSE; expand_string_message = US""; -{ uschar *m; -if ((m = is_tainted2(string, LOG_MAIN|LOG_PANIC, "Tainted string '%s' in expansion", s))) +if (is_tainted(string)) { - expand_string_message = m; + expand_string_message = + string_sprintf("attempt to expand tainted string '%s'", s); + log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message); goto EXPAND_FAILED; } -} while (*s) { @@ -4494,11 +4595,11 @@ while (*s) DEBUG(D_noutf8) debug_printf_indent("%c%s: %s\n", first ? '/' : '|', - skipping ? "---scanning" : "considering", s); + flags & ESI_SKIPPING ? "---scanning" : "considering", s); else debug_printf_indent("%s%s: %s\n", first ? UTF8_DOWN_RIGHT : UTF8_VERT_RIGHT, - skipping + flags & ESI_SKIPPING ? UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "scanning" : "considering", s); @@ -4522,14 +4623,11 @@ while (*s) { const uschar * t = s + 2; for (s = t; *s ; s++) if (*s == '\\' && s[1] == 'N') break; + DEBUG(D_expand) - DEBUG(D_noutf8) - debug_printf_indent("|--protected: %.*s\n", (int)(s - t), t); - else - debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ - "protected: %.*s\n", (int)(s - t), t); + debug_expansion_interim(US"protected", t, (int)(s - t), !!(flags & ESI_SKIPPING)); yield = string_catn(yield, t, s - t); - if (*s != 0) s += 2; + if (*s) s += 2; } else { @@ -4550,26 +4648,21 @@ while (*s) /* Anything other than $ is just copied verbatim, unless we are looking for a terminating } character. */ - if (ket_ends && *s == '}') break; + if (flags & ESI_BRACE_ENDS && *s == '}') break; - if (*s != '$' || !honour_dollar) + if (*s != '$' || !(flags & ESI_HONOR_DOLLAR)) { int i = 1; /*{*/ for (const uschar * t = s+1; *t && *t != '$' && *t != '}' && *t != '\\'; t++) i++; - DEBUG(D_expand) - DEBUG(D_noutf8) - debug_printf_indent("|-------text: %.*s\n", i, s); - else - debug_printf_indent(UTF8_VERT_RIGHT - UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ - "text: %.*s\n", i, s); + DEBUG(D_expand) debug_expansion_interim(US"text", s, i, !!(flags & ESI_SKIPPING)); yield = string_catn(yield, s, i); s += i; continue; } + textonly = FALSE; /* No { after the $ - must be a plain name or a number for string match variable. There has to be a fudge for variables that are the @@ -4592,13 +4685,13 @@ while (*s) buffer. */ if (!yield) - g = store_get(sizeof(gstring), FALSE); + g = store_get(sizeof(gstring), GET_UNTAINTED); else if (yield->ptr == 0) { if (resetok) reset_point = store_reset(reset_point); yield = NULL; reset_point = store_mark(); - g = store_get(sizeof(gstring), FALSE); /* alloc _before_ calling find_variable() */ + g = store_get(sizeof(gstring), GET_UNTAINTED); /* alloc _before_ calling find_variable() */ } /* Header */ @@ -4631,7 +4724,7 @@ while (*s) /* Variable */ - else if (!(value = find_variable(name, FALSE, skipping, &newsize))) + else if (!(value = find_variable(name, FALSE, !!(flags & ESI_SKIPPING), &newsize))) { expand_string_message = string_sprintf("unknown variable name \"%s\"", name); @@ -4648,6 +4741,7 @@ while (*s) reset in the middle of the buffer will make it inaccessible. */ len = Ustrlen(value); + DEBUG(D_expand) debug_expansion_interim(US"value", value, len, !!(flags & ESI_SKIPPING)); if (!yield && newsize != 0) { yield = g; @@ -4661,12 +4755,15 @@ while (*s) continue; } - if (isdigit(*s)) + if (isdigit(*s)) /* A $ variable */ { int n; s = read_cnumber(&n, s); if (n >= 0 && n <= expand_nmax) + { + DEBUG(D_expand) debug_expansion_interim(US"value", expand_nstring[n], expand_nlength[n], !!(flags & ESI_SKIPPING)); yield = string_catn(yield, expand_nstring[n], expand_nlength[n]); + } continue; } @@ -4691,7 +4788,10 @@ while (*s) goto EXPAND_FAILED; } if (n >= 0 && n <= expand_nmax) + { + DEBUG(D_expand) debug_expansion_interim(US"value", expand_nstring[n], expand_nlength[n], !!(flags & ESI_SKIPPING)); yield = string_catn(yield, expand_nstring[n], expand_nlength[n]); + } continue; } @@ -4708,6 +4808,11 @@ while (*s) s = read_name(name, sizeof(name), s, US"_-"); item_type = chop_match(name, item_table, nelem(item_table)); + /* Switch on item type. All nondefault choices should "continue* when + skipping, but "break" otherwise so we get debug output for the item + expansion. */ + { + int expansion_start = gstring_length(yield); switch(item_type) { /* Call an ACL from an expansion. We feed data in via $acl_arg1 - $acl_arg9. @@ -4722,18 +4827,17 @@ while (*s) case EITEM_ACL: /* ${acl {name} {arg1}{arg2}...} */ { - uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */ - uschar *user_msg; + uschar * sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */ + uschar * user_msg; int rc; - switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, name, - &resetok)) + switch(read_subs(sub, nelem(sub), 1, &s, flags, TRUE, name, &resetok, NULL)) { + case -1: continue; /* skipping */ case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; } - if (skipping) continue; resetok = FALSE; switch(rc = eval_acl(sub, nelem(sub), &user_msg)) @@ -4744,7 +4848,7 @@ while (*s) debug_printf_indent("acl expansion yield: %s\n", user_msg); if (user_msg) yield = string_cat(yield, user_msg); - continue; + break; case DEFER: f.expand_string_forcedfail = TRUE; @@ -4754,16 +4858,17 @@ while (*s) rc_names[rc], sub[0]); goto EXPAND_FAILED; } + break; } case EITEM_AUTHRESULTS: /* ${authresults {mysystemname}} */ { - uschar *sub_arg[1]; + uschar * sub_arg[1]; - switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, skipping, TRUE, name, - &resetok)) + switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, flags, TRUE, name, &resetok, NULL)) { + case -1: continue; /* If skipping, we don't actually do anything */ case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; @@ -4771,7 +4876,7 @@ while (*s) yield = string_append(yield, 3, US"Authentication-Results: ", sub_arg[0], US"; none"); - yield->ptr -= 6; + yield->ptr -= 6; /* ignore tha ": none" for now */ yield = authres_local(yield, sub_arg[0]); yield = authres_iprev(yield); @@ -4788,7 +4893,7 @@ while (*s) #ifdef EXPERIMENTAL_ARC yield = authres_arc(yield); #endif - continue; + break; } /* Handle conditionals - preserve the values of the numerical expansion @@ -4802,27 +4907,18 @@ while (*s) const uschar *next_s; int save_expand_nmax = save_expand_strings(save_expand_nstring, save_expand_nlength); + uschar * save_lookup_value = lookup_value; Uskip_whitespace(&s); - if (!(next_s = eval_condition(s, &resetok, skipping ? NULL : &cond))) + if (!(next_s = eval_condition(s, &resetok, flags & ESI_SKIPPING ? NULL : &cond))) goto EXPAND_FAILED; /* message already set */ DEBUG(D_expand) - DEBUG(D_noutf8) - { - debug_printf_indent("|--condition: %.*s\n", (int)(next_s - s), s); - debug_printf_indent("|-----result: %s\n", cond ? "true" : "false"); - } - else - { - debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ - "condition: %.*s\n", - (int)(next_s - s), s); - debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ - UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ - "result: %s\n", - cond ? "true" : "false"); - } + { + debug_expansion_interim(US"condition", s, (int)(next_s - s), !!(flags & ESI_SKIPPING)); + debug_expansion_interim(US"result", + cond ? US"true" : US"false", cond ? 4 : 5, !!(flags & ESI_SKIPPING)); + } s = next_s; @@ -4830,12 +4926,12 @@ while (*s) function that is also used by ${lookup} and ${extract} and ${run}. */ switch(process_yesno( - skipping, /* were previously skipping */ - cond, /* success/failure indicator */ - lookup_value, /* value to reset for string2 */ - &s, /* input pointer */ - &yield, /* output pointer */ - US"if", /* condition type */ + flags, /* were previously skipping */ + cond, /* success/failure indicator */ + lookup_value, /* value to reset for string2 */ + &s, /* input pointer */ + &yield, /* output pointer */ + US"if", /* condition type */ &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ @@ -4845,9 +4941,10 @@ while (*s) /* Restore external setting of expansion variables for continuation at this level. */ + lookup_value = save_lookup_value; restore_expand_strings(save_expand_nmax, save_expand_nstring, save_expand_nlength); - continue; + break; } #ifdef SUPPORT_I18N @@ -4856,8 +4953,7 @@ while (*s) uschar *sub_arg[3]; uschar *encoded; - switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, skipping, TRUE, name, - &resetok)) + switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, flags, TRUE, name, &resetok, NULL)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4878,14 +4974,13 @@ while (*s) goto EXPAND_FAILED; } - if (!skipping) - { - if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset, - sub_arg[1][0], sub_arg[2], &expand_string_message))) - goto EXPAND_FAILED; - yield = string_cat(yield, encoded); - } - continue; + if (flags & ESI_SKIPPING) continue; + + if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset, + sub_arg[1][0], sub_arg[2], &expand_string_message))) + goto EXPAND_FAILED; + yield = string_cat(yield, encoded); + break; } #endif @@ -4901,9 +4996,9 @@ while (*s) int stype, partial, affixlen, starflags; int expand_setup = 0; int nameptr = 0; - uschar *key, *filename; + uschar * key, * filename; const uschar * affix, * opts; - uschar *save_lookup_value = lookup_value; + uschar * save_lookup_value = lookup_value; int save_expand_nmax = save_expand_strings(save_expand_nstring, save_expand_nlength); @@ -4918,7 +5013,8 @@ while (*s) if (Uskip_whitespace(&s) == '{') /*}*/ { - key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + key = expand_string_internal(s+1, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL); if (!key) goto EXPAND_FAILED; /*{{*/ if (*s++ != '}') { @@ -4971,15 +5067,12 @@ while (*s) goto EXPAND_FAILED; } } - else - { - if (key) - { - expand_string_message = string_sprintf("a single key was given for " - "lookup type \"%s\", which is not a single-key lookup type", name); - goto EXPAND_FAILED; - } - } + else if (key) + { + expand_string_message = string_sprintf("a single key was given for " + "lookup type \"%s\", which is not a single-key lookup type", name); + goto EXPAND_FAILED; + } /* Get the next string in brackets and expand it. It is the file name for single-key+file lookups, and the whole query otherwise. In the case of @@ -4991,7 +5084,8 @@ while (*s) expand_string_message = US"missing '{' for lookup file-or-query arg"; goto EXPAND_FAILED_CURLY; /*}}*/ } - if (!(filename = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))) + if (!(filename = expand_string_internal(s+1, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL))) goto EXPAND_FAILED; /*{{*/ if (*s++ != '}') @@ -5022,7 +5116,7 @@ while (*s) since new variables will have been set. Note that at the end of this "lookup" section, the old numeric variables are restored. */ - if (skipping) + if (flags & ESI_SKIPPING) lookup_value = NULL; else { @@ -5048,12 +5142,12 @@ while (*s) function that is also used by ${if} and ${extract}. */ switch(process_yesno( - skipping, /* were previously skipping */ - lookup_value != NULL, /* success/failure indicator */ - save_lookup_value, /* value to reset for string2 */ - &s, /* input pointer */ - &yield, /* output pointer */ - US"lookup", /* condition type */ + flags, /* were previously skipping */ + lookup_value != NULL, /* success/failure indicator */ + save_lookup_value, /* value to reset for string2 */ + &s, /* input pointer */ + &yield, /* output pointer */ + US"lookup", /* condition type */ &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ @@ -5065,7 +5159,9 @@ while (*s) restore_expand_strings(save_expand_nmax, save_expand_nstring, save_expand_nlength); - continue; + + if (flags & ESI_SKIPPING) continue; + break; } /* If Perl support is configured, handle calling embedded perl subroutines, @@ -5083,32 +5179,29 @@ while (*s) #else /* EXIM_PERL */ { - uschar *sub_arg[EXIM_PERL_MAX_ARGS + 2]; - gstring *new_yield; + uschar * sub_arg[EXIM_PERL_MAX_ARGS + 2]; + gstring * new_yield; - if ((expand_forbid & RDO_PERL) != 0) + if (expand_forbid & RDO_PERL) { expand_string_message = US"Perl calls are not permitted"; goto EXPAND_FAILED; } - switch(read_subs(sub_arg, EXIM_PERL_MAX_ARGS + 1, 1, &s, skipping, TRUE, - name, &resetok)) + switch(read_subs(sub_arg, EXIM_PERL_MAX_ARGS + 1, 1, &s, flags, TRUE, + name, &resetok, NULL)) { + case -1: continue; /* If skipping, we don't actually do anything */ case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; } - /* If skipping, we don't actually do anything */ - - if (skipping) continue; - /* Start the interpreter if necessary */ if (!opt_perl_started) { - uschar *initerror; + uschar * initerror; if (!opt_perl_startup) { expand_string_message = US"A setting of perl_startup is needed when " @@ -5152,7 +5245,7 @@ while (*s) f.expand_string_forcedfail = FALSE; yield = new_yield; - continue; + break; } #endif /* EXIM_PERL */ @@ -5161,19 +5254,16 @@ while (*s) case EITEM_PRVS: { - uschar *sub_arg[3]; - uschar *p,*domain; + uschar * sub_arg[3], * p, * domain; - switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, name, &resetok)) + switch(read_subs(sub_arg, 3, 2, &s, flags, TRUE, name, &resetok, NULL)) { + case -1: continue; /* If skipping, we don't actually do anything */ case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; } - /* If skipping, we don't actually do anything */ - if (skipping) continue; - /* sub_arg[0] is the address */ if ( !(domain = Ustrrchr(sub_arg[0],'@')) || domain == sub_arg[0] || Ustrlen(domain) == 1) @@ -5211,56 +5301,49 @@ while (*s) yield = string_catn(yield, US"@", 1); yield = string_cat (yield, domain); - continue; + break; } /* Check a prvs-encoded address for validity */ case EITEM_PRVSCHECK: { - uschar *sub_arg[3]; + uschar * sub_arg[3], * p; gstring * g; - const pcre2_code *re; - uschar *p; - - /* TF: Ugliness: We want to expand parameter 1 first, then set - up expansion variables that are used in the expansion of - parameter 2. So we clone the string for the first - expansion, where we only expand parameter 1. - - PH: Actually, that isn't necessary. The read_subs() function is - designed to work this way for the ${if and ${lookup expansions. I've - tidied the code. - */ /*}}*/ + const pcre2_code * re; /* Reset expansion variables */ prvscheck_result = NULL; prvscheck_address = NULL; prvscheck_keynum = NULL; - switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, name, &resetok)) + switch(read_subs(sub_arg, 1, 1, &s, flags, FALSE, name, &resetok, NULL)) { case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; } - re = regex_must_compile(US"^prvs\\=([0-9])([0-9]{3})([A-F0-9]{6})\\=(.+)\\@(.+)$", - TRUE,FALSE); + re = regex_must_compile( + US"^prvs\\=([0-9])([0-9]{3})([A-F0-9]{6})\\=(.+)\\@(.+)$", + MCS_CASELESS | MCS_CACHEABLE, FALSE); if (regex_match_and_setup(re,sub_arg[0],0,-1)) { - uschar *local_part = string_copyn(expand_nstring[4],expand_nlength[4]); - uschar *key_num = string_copyn(expand_nstring[1],expand_nlength[1]); - uschar *daystamp = string_copyn(expand_nstring[2],expand_nlength[2]); - uschar *hash = string_copyn(expand_nstring[3],expand_nlength[3]); - uschar *domain = string_copyn(expand_nstring[5],expand_nlength[5]); - - DEBUG(D_expand) debug_printf_indent("prvscheck localpart: %s\n", local_part); - DEBUG(D_expand) debug_printf_indent("prvscheck key number: %s\n", key_num); - DEBUG(D_expand) debug_printf_indent("prvscheck daystamp: %s\n", daystamp); - DEBUG(D_expand) debug_printf_indent("prvscheck hash: %s\n", hash); - DEBUG(D_expand) debug_printf_indent("prvscheck domain: %s\n", domain); + uschar * local_part = string_copyn(expand_nstring[4],expand_nlength[4]); + uschar * key_num = string_copyn(expand_nstring[1],expand_nlength[1]); + uschar * daystamp = string_copyn(expand_nstring[2],expand_nlength[2]); + uschar * hash = string_copyn(expand_nstring[3],expand_nlength[3]); + uschar * domain = string_copyn(expand_nstring[5],expand_nlength[5]); + + DEBUG(D_expand) + { + debug_printf_indent("prvscheck localpart: %s\n", local_part); + debug_printf_indent("prvscheck key number: %s\n", key_num); + debug_printf_indent("prvscheck daystamp: %s\n", daystamp); + debug_printf_indent("prvscheck hash: %s\n", hash); + debug_printf_indent("prvscheck domain: %s\n", domain); + } /* Set up expansion variables */ g = string_cat (NULL, local_part); @@ -5270,7 +5353,7 @@ while (*s) prvscheck_keynum = string_copy(key_num); /* Now expand the second argument */ - switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, name, &resetok)) + switch(read_subs(sub_arg, 1, 1, &s, flags, FALSE, name, &resetok, NULL)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -5281,7 +5364,6 @@ while (*s) p = prvs_hmac_sha1(prvscheck_address, sub_arg[0], prvscheck_keynum, daystamp); - if (!p) { expand_string_message = US"hmac-sha1 conversion failed"; @@ -5307,24 +5389,24 @@ while (*s) if (iexpire >= inow) { prvscheck_result = US"1"; - DEBUG(D_expand) debug_printf_indent("prvscheck: success, $pvrs_result set to 1\n"); + DEBUG(D_expand) debug_printf_indent("prvscheck: success, $prvscheck_result set to 1\n"); } else { prvscheck_result = NULL; - DEBUG(D_expand) debug_printf_indent("prvscheck: signature expired, $pvrs_result unset\n"); + DEBUG(D_expand) debug_printf_indent("prvscheck: signature expired, $prvscheck_result unset\n"); } } else { prvscheck_result = NULL; - DEBUG(D_expand) debug_printf_indent("prvscheck: hash failure, $pvrs_result unset\n"); + DEBUG(D_expand) debug_printf_indent("prvscheck: hash failure, $prvscheck_result unset\n"); } /* Now expand the final argument. We leave this till now so that it can include $prvscheck_result. */ - switch(read_subs(sub_arg, 1, 0, &s, skipping, TRUE, name, &resetok)) + switch(read_subs(sub_arg, 1, 0, &s, flags, TRUE, name, &resetok, NULL)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -5345,22 +5427,23 @@ while (*s) We need to make sure all subs are expanded first, so as to skip over the entire item. */ - switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, name, &resetok)) + switch(read_subs(sub_arg, 2, 1, &s, flags, TRUE, name, &resetok, NULL)) { case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; } - continue; + if (flags & ESI_SKIPPING) continue; + break; } /* Handle "readfile" to insert an entire file */ case EITEM_READFILE: { - FILE *f; - uschar *sub_arg[2]; + FILE * f; + uschar * sub_arg[2]; if ((expand_forbid & RDO_READFILE) != 0) { @@ -5368,17 +5451,14 @@ while (*s) goto EXPAND_FAILED; } - switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, name, &resetok)) + switch(read_subs(sub_arg, 2, 1, &s, flags, TRUE, name, &resetok, NULL)) { + case -1: continue; /* If skipping, we don't actually do anything */ case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; } - /* If skipping, we don't actually do anything */ - - if (skipping) continue; - /* Open the file and read it */ if (!(f = Ufopen(sub_arg[0], "rb"))) @@ -5389,7 +5469,7 @@ while (*s) yield = cat_file(f, yield, sub_arg[1]); (void)fclose(f); - continue; + break; } /* Handle "readsocket" to insert data from a socket, either @@ -5409,7 +5489,7 @@ while (*s) /* Read up to 4 arguments, but don't do the end of item check afterwards, because there may be a string for expansion on failure. */ - switch(read_subs(sub_arg, 4, 2, &s, skipping, FALSE, name, &resetok)) + switch(read_subs(sub_arg, 4, 2, &s, flags, FALSE, name, &resetok, NULL)) { case 1: goto EXPAND_FAILED_CURLY; case 2: /* Won't occur: no end check */ @@ -5419,7 +5499,7 @@ while (*s) /* If skipping, we don't actually do anything. Otherwise, arrange to connect to either an IP or a Unix socket. */ - if (!skipping) + if (!(flags & ESI_SKIPPING)) { int stype = search_findtype(US"readsock", 8); gstring * g = NULL; @@ -5497,7 +5577,8 @@ while (*s) if (*s == '{') /*}*/ { - if (!expand_string_internal(s+1, TRUE, &s, TRUE, TRUE, &resetok)) + if (!expand_string_internal(s+1, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | ESI_SKIPPING, &s, &resetok, NULL)) goto EXPAND_FAILED; /*{*/ if (*s++ != '}') { /*{*/ @@ -5513,7 +5594,8 @@ while (*s) expand_string_message = US"missing '}' closing readsocket"; goto EXPAND_FAILED_CURLY; } - continue; + if (flags & ESI_SKIPPING) continue; + break; /* Come here on failure to create socket, connect socket, write to the socket, or timeout on reading. If another substring follows, expand and @@ -5522,7 +5604,8 @@ while (*s) SOCK_FAIL: if (*s != '{') goto EXPAND_FAILED; /*}*/ DEBUG(D_any) debug_printf("%s\n", expand_string_message); - if (!(arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE, &resetok))) + if (!(arg = expand_string_internal(s+1, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR, &s, &resetok, NULL))) goto EXPAND_FAILED; yield = string_cat(yield, arg); /*{*/ if (*s++ != '}') @@ -5538,47 +5621,85 @@ while (*s) case EITEM_RUN: { - FILE *f; - uschar *arg; - const uschar **argv; - pid_t pid; - int fd_in, fd_out; + FILE * f; + const uschar * arg, ** argv; + BOOL late_expand = TRUE; + uschar * save_value = lookup_value; + int yesno; - if ((expand_forbid & RDO_RUN) != 0) + if (expand_forbid & RDO_RUN) { expand_string_message = US"running a command is not permitted"; goto EXPAND_FAILED; } + /* Handle options to the "run" */ + + while (*s == ',') + if (Ustrncmp(++s, "preexpand", 9) == 0) + { late_expand = FALSE; s += 9; } + else + { + const uschar * t = s; + while (isalpha(*++t)) ; + expand_string_message = string_sprintf("bad option '%.*s' for run", + (int)(t-s), s); + goto EXPAND_FAILED; + } Uskip_whitespace(&s); - if (*s != '{') + + if (*s != '{') /*}*/ { expand_string_message = US"missing '{' for command arg of run"; - goto EXPAND_FAILED_CURLY; + goto EXPAND_FAILED_CURLY; /*"}*/ } - if (!(arg = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))) - goto EXPAND_FAILED; - Uskip_whitespace(&s); + s++; + + if (late_expand) /* this is the default case */ + { + int n; + const uschar * t; + /* Locate the end of the args */ + (void) expand_string_internal(s, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | ESI_SKIPPING, &t, NULL, NULL); + n = t - s; + arg = flags & ESI_SKIPPING ? NULL : string_copyn(s, n); + s += n; + } + else + { + DEBUG(D_expand) + debug_printf_indent("args string for ${run} expand before split\n"); + if (!(arg = expand_string_internal(s, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL))) + goto EXPAND_FAILED; + Uskip_whitespace(&s); + } + /*{*/ if (*s++ != '}') - { + { /*{*/ expand_string_message = US"missing '}' closing command arg of run"; goto EXPAND_FAILED_CURLY; } - if (skipping) /* Just pretend it worked when we're skipping */ + if (flags & ESI_SKIPPING) /* Just pretend it worked when we're skipping */ { runrc = 0; lookup_value = NULL; } else { + int fd_in, fd_out; + pid_t pid; + if (!transport_set_up_command(&argv, /* anchor for arg list */ arg, /* raw command */ - FALSE, /* don't expand the arguments */ - 0, /* not relevant when... */ - NULL, /* no transporting address */ - US"${run} expansion", /* for error messages */ - &expand_string_message)) /* where to put error message */ + late_expand, /* expand args if not already done */ + 0, /* not relevant when... */ + NULL, /* no transporting address */ + late_expand, /* allow tainted args, when expand-after-split */ + US"${run} expansion", /* for error messages */ + &expand_string_message)) /* where to put error message */ goto EXPAND_FAILED; /* Create the child process, making it a group leader. */ @@ -5589,7 +5710,7 @@ while (*s) expand_string_message = string_sprintf("couldn't create child process: %s", strerror(errno)); goto EXPAND_FAILED; - } + } /* Nothing is written to the standard input. */ @@ -5628,26 +5749,31 @@ while (*s) expand_string_message = string_sprintf("command killed by signal %d", -runrc); + lookup_value = save_value; goto EXPAND_FAILED; } } /* Process the yes/no strings; $value may be useful in both cases */ - switch(process_yesno( - skipping, /* were previously skipping */ - runrc == 0, /* success/failure indicator */ - lookup_value, /* value to reset for string2 */ - &s, /* input pointer */ - &yield, /* output pointer */ - US"run", /* condition type */ - &resetok)) + yesno = process_yesno( + flags, /* were previously skipping */ + runrc == 0, /* success/failure indicator */ + lookup_value, /* value to reset for string2 */ + &s, /* input pointer */ + &yield, /* output pointer */ + US"run", /* condition type */ + &resetok); + lookup_value = save_value; + + switch(yesno) { case 1: goto EXPAND_FAILED; /* when all is well, the */ case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ } - continue; + if (flags & ESI_SKIPPING) continue; + break; } /* Handle character translation for "tr" */ @@ -5656,10 +5782,11 @@ while (*s) { int oldptr = gstring_length(yield); int o2m; - uschar *sub[3]; + uschar * sub[3]; - switch(read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok)) + switch(read_subs(sub, 3, 3, &s, flags, TRUE, name, &resetok, NULL)) { + case -1: continue; /* skipping */ case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; @@ -5670,7 +5797,7 @@ while (*s) if (o2m >= 0) for (; oldptr < yield->ptr; oldptr++) { - uschar *m = Ustrrchr(sub[1], yield->s[oldptr]); + uschar * m = Ustrrchr(sub[1], yield->s[oldptr]); if (m) { int o = m - sub[1]; @@ -5678,7 +5805,7 @@ while (*s) } } - continue; + break; } /* Handle "hash", "length", "nhash", and "substr" when they are given with @@ -5692,15 +5819,16 @@ while (*s) int len; uschar *ret; int val[2] = { 0, -1 }; - uschar *sub[3]; + uschar * sub[3]; /* "length" takes only 2 arguments whereas the others take 2 or 3. Ensure that sub[2] is set in the ${length } case. */ sub[2] = NULL; - switch(read_subs(sub, (item_type == EITEM_LENGTH)? 2:3, 2, &s, skipping, - TRUE, name, &resetok)) + switch(read_subs(sub, item_type == EITEM_LENGTH ? 2:3, 2, &s, flags, + TRUE, name, &resetok, NULL)) { + case -1: continue; /* skipping */ case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; @@ -5741,7 +5869,7 @@ while (*s) if (!ret) goto EXPAND_FAILED; yield = string_catn(yield, ret, len); - continue; + break; } /* Handle HMAC computation: ${hmac{}{}{}} @@ -5756,14 +5884,14 @@ while (*s) case EITEM_HMAC: { - uschar *sub[3]; + uschar * sub[3]; md5 md5_base; hctx sha1_ctx; - void *use_base; + void * use_base; int type; int hashlen; /* Number of octets for the hash algorithm's output */ int hashblocklen; /* Number of octets the hash algorithm processes */ - uschar *keyptr, *p; + uschar * keyptr, * p; unsigned int keylen; uschar keyhash[MAX_HASHLEN]; @@ -5773,86 +5901,84 @@ while (*s) uschar innerkey[MAX_HASHBLOCKLEN]; uschar outerkey[MAX_HASHBLOCKLEN]; - switch (read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok)) + switch (read_subs(sub, 3, 3, &s, flags, TRUE, name, &resetok, NULL)) { + case -1: continue; /* skipping */ case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; } - if (!skipping) + if (Ustrcmp(sub[0], "md5") == 0) { - if (Ustrcmp(sub[0], "md5") == 0) - { - type = HMAC_MD5; - use_base = &md5_base; - hashlen = 16; - hashblocklen = 64; - } - else if (Ustrcmp(sub[0], "sha1") == 0) - { - type = HMAC_SHA1; - use_base = &sha1_ctx; - hashlen = 20; - hashblocklen = 64; - } - else - { - expand_string_message = - string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]); - goto EXPAND_FAILED; - } + type = HMAC_MD5; + use_base = &md5_base; + hashlen = 16; + hashblocklen = 64; + } + else if (Ustrcmp(sub[0], "sha1") == 0) + { + type = HMAC_SHA1; + use_base = &sha1_ctx; + hashlen = 20; + hashblocklen = 64; + } + else + { + expand_string_message = + string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]); + goto EXPAND_FAILED; + } - keyptr = sub[1]; - keylen = Ustrlen(keyptr); + keyptr = sub[1]; + keylen = Ustrlen(keyptr); - /* If the key is longer than the hash block length, then hash the key - first */ + /* If the key is longer than the hash block length, then hash the key + first */ - if (keylen > hashblocklen) - { - chash_start(type, use_base); - chash_end(type, use_base, keyptr, keylen, keyhash); - keyptr = keyhash; - keylen = hashlen; - } + if (keylen > hashblocklen) + { + chash_start(type, use_base); + chash_end(type, use_base, keyptr, keylen, keyhash); + keyptr = keyhash; + keylen = hashlen; + } - /* Now make the inner and outer key values */ + /* Now make the inner and outer key values */ - memset(innerkey, 0x36, hashblocklen); - memset(outerkey, 0x5c, hashblocklen); + memset(innerkey, 0x36, hashblocklen); + memset(outerkey, 0x5c, hashblocklen); - for (int i = 0; i < keylen; i++) - { - innerkey[i] ^= keyptr[i]; - outerkey[i] ^= keyptr[i]; - } + for (int i = 0; i < keylen; i++) + { + innerkey[i] ^= keyptr[i]; + outerkey[i] ^= keyptr[i]; + } - /* Now do the hashes */ + /* Now do the hashes */ - chash_start(type, use_base); - chash_mid(type, use_base, innerkey); - chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash); + chash_start(type, use_base); + chash_mid(type, use_base, innerkey); + chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash); - chash_start(type, use_base); - chash_mid(type, use_base, outerkey); - chash_end(type, use_base, innerhash, hashlen, finalhash); + chash_start(type, use_base); + chash_mid(type, use_base, outerkey); + chash_end(type, use_base, innerhash, hashlen, finalhash); - /* Encode the final hash as a hex string */ + /* Encode the final hash as a hex string */ - p = finalhash_hex; - for (int i = 0; i < hashlen; i++) - { - *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4]; - *p++ = hex_digits[finalhash[i] & 0x0f]; - } + p = finalhash_hex; + for (int i = 0; i < hashlen; i++) + { + *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4]; + *p++ = hex_digits[finalhash[i] & 0x0f]; + } - DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n", - sub[0], (int)keylen, keyptr, sub[2], hashlen*2, finalhash_hex); + DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n", + sub[0], (int)keylen, keyptr, sub[2], hashlen*2, finalhash_hex); - yield = string_catn(yield, finalhash_hex, hashlen*2); - } - continue; + yield = string_catn(yield, finalhash_hex, hashlen*2); + break; } /* Handle global substitution for "sg" - like Perl's s/xxx/yyy/g operator. @@ -5862,15 +5988,16 @@ while (*s) { const pcre2_code * re; int moffset, moffsetextra, slen; - PCRE2_SIZE roffset; pcre2_match_data * md; - int err, emptyopt; + int emptyopt; uschar * subject, * sub[3]; int save_expand_nmax = save_expand_strings(save_expand_nstring, save_expand_nlength); + unsigned sub_textonly = 0; - switch(read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok)) + switch(read_subs(sub, 3, 3, &s, flags, TRUE, name, &resetok, &sub_textonly)) { + case -1: continue; /* skipping */ case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; @@ -5878,15 +6005,12 @@ while (*s) /* Compile the regular expression */ - if (!(re = pcre2_compile((PCRE2_SPTR)sub[1], PCRE2_ZERO_TERMINATED, - PCRE_COPT, &err, &roffset, pcre_cmp_ctx))) - { - uschar errbuf[128]; - pcre2_get_error_message(err, errbuf, sizeof(errbuf)); - expand_string_message = string_sprintf("regular expression error in " - "\"%s\": %s at offset %ld", sub[1], errbuf, (long)roffset); + re = regex_compile(sub[1], + sub_textonly & BIT(1) ? MCS_CACHEABLE : MCS_NOFLAGS, + &expand_string_message, pcre_gen_cmp_ctx); + if (!re) goto EXPAND_FAILED; - } + md = pcre2_match_data_create(EXPAND_MAXN + 1, pcre_gen_ctx); /* Now run a loop to do the substitutions as often as necessary. It ends @@ -5902,7 +6026,7 @@ while (*s) { PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md); int n = pcre2_match(re, (PCRE2_SPTR)subject, slen, moffset + moffsetextra, - PCRE_EOPT | emptyopt, md, pcre_mtc_ctx); + PCRE_EOPT | emptyopt, md, pcre_gen_mtc_ctx); uschar * insert; /* No match - if we previously set PCRE_NOTEMPTY after a null match, this @@ -5938,8 +6062,7 @@ while (*s) /* Copy the characters before the match, plus the expanded insertion. */ - if (ovec[0] > moffset) - yield = string_catn(yield, subject + moffset, ovec[0] - moffset); + yield = string_catn(yield, subject + moffset, ovec[0] - moffset); if (!(insert = expand_string(sub[2]))) goto EXPAND_FAILED; @@ -5965,9 +6088,10 @@ while (*s) /* All done - restore numerical variables. */ + /* pcre2_match_data_free(md); gen ctx needs no free */ restore_expand_strings(save_expand_nmax, save_expand_nstring, save_expand_nlength); - continue; + break; } /* Handle keyed and numbered substring extraction. If the first argument @@ -6001,11 +6125,12 @@ while (*s) available (eg. $item) hence cannot decide on numeric vs. keyed. Read a maximum of 5 arguments (including the yes/no) */ - if (skipping) + if (flags & ESI_SKIPPING) { for (int j = 5; j > 0 && *s == '{'; j--) /*'}'*/ { - if (!expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok)) + if (!expand_string_internal(s+1, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL)) goto EXPAND_FAILED; /*'{'*/ if (*s++ != '}') { @@ -6032,7 +6157,8 @@ while (*s) { if (Uskip_whitespace(&s) == '{') /*'}'*/ { - if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))) + if (!(sub[i] = expand_string_internal(s+1, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL))) goto EXPAND_FAILED; /*'{'*/ if (*s++ != '}') { @@ -6091,7 +6217,7 @@ while (*s) /* Extract either the numbered or the keyed substring into $value. If skipping, just pretend the extraction failed. */ - if (skipping) + if (flags & ESI_SKIPPING) lookup_value = NULL; else switch (fmt) { @@ -6185,12 +6311,12 @@ while (*s) be yes/no strings, as for lookup or if. */ switch(process_yesno( - skipping, /* were previously skipping */ - lookup_value != NULL, /* success/failure indicator */ - save_lookup_value, /* value to reset for string2 */ - &s, /* input pointer */ - &yield, /* output pointer */ - US"extract", /* condition type */ + flags, /* were previously skipping */ + lookup_value != NULL, /* success/failure indicator */ + save_lookup_value, /* value to reset for string2 */ + &s, /* input pointer */ + &yield, /* output pointer */ + US"extract", /* condition type */ &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ @@ -6202,7 +6328,8 @@ while (*s) restore_expand_strings(save_expand_nmax, save_expand_nstring, save_expand_nlength); - continue; + if (flags & ESI_SKIPPING) continue; + break; } /* return the Nth item from a list */ @@ -6215,6 +6342,7 @@ while (*s) save_expand_strings(save_expand_nstring, save_expand_nlength); /* Read the field & list arguments */ + /*XXX Could we use read_subs here (and get better efficiency for skipping)? */ for (int i = 0; i < 2; i++) { @@ -6225,7 +6353,8 @@ while (*s) goto EXPAND_FAILED_CURLY; } - sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + sub[i] = expand_string_internal(s+1, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL); if (!sub[i]) goto EXPAND_FAILED; /*{{*/ if (*s++ != '}') { @@ -6250,7 +6379,7 @@ while (*s) while (len > 0 && isspace(p[len-1])) len--; p[len] = 0; - if (!*p && !skipping) + if (!*p && !(flags & ESI_SKIPPING)) { expand_string_message = US"first argument of \"listextract\" must " "not be empty"; @@ -6276,18 +6405,18 @@ while (*s) /* Extract the numbered element into $value. If skipping, just pretend the extraction failed. */ - lookup_value = skipping ? NULL : expand_getlistele(field_number, sub[1]); + lookup_value = flags & ESI_SKIPPING ? NULL : expand_getlistele(field_number, sub[1]); /* If no string follows, $value gets substituted; otherwise there can be yes/no strings, as for lookup or if. */ switch(process_yesno( - skipping, /* were previously skipping */ - lookup_value != NULL, /* success/failure indicator */ - save_lookup_value, /* value to reset for string2 */ - &s, /* input pointer */ - &yield, /* output pointer */ - US"listextract", /* condition type */ + flags, /* were previously skipping */ + lookup_value != NULL, /* success/failure indicator */ + save_lookup_value, /* value to reset for string2 */ + &s, /* input pointer */ + &yield, /* output pointer */ + US"listextract", /* condition type */ &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ @@ -6299,14 +6428,16 @@ while (*s) restore_expand_strings(save_expand_nmax, save_expand_nstring, save_expand_nlength); - continue; + if (flags & ESI_SKIPPING) continue; + break; } case EITEM_LISTQUOTE: { uschar * sub[2]; - switch(read_subs(sub, 2, 2, &s, skipping, TRUE, name, &resetok)) + switch(read_subs(sub, 2, 2, &s, flags, TRUE, name, &resetok, NULL)) { + case -1: continue; /* skipping */ case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; @@ -6317,7 +6448,7 @@ while (*s) yield = string_catn(yield, sub[1], 1); } else yield = string_catn(yield, US" ", 1); - continue; + break; } #ifndef DISABLE_TLS @@ -6333,7 +6464,8 @@ while (*s) expand_string_message = US"missing '{' for field arg of certextract"; goto EXPAND_FAILED_CURLY; /*}*/ } - sub[0] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + sub[0] = expand_string_internal(s+1, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL); if (!sub[0]) goto EXPAND_FAILED; /*{{*/ if (*s++ != '}') { @@ -6365,7 +6497,8 @@ while (*s) "be a certificate variable"; goto EXPAND_FAILED; } - sub[1] = expand_string_internal(s+1, TRUE, &s, skipping, FALSE, &resetok); + sub[1] = expand_string_internal(s+1, + ESI_BRACE_ENDS | flags & ESI_SKIPPING, &s, &resetok, NULL); if (!sub[1]) goto EXPAND_FAILED; /*{{*/ if (*s++ != '}') { @@ -6373,7 +6506,7 @@ while (*s) goto EXPAND_FAILED_CURLY; } - if (skipping) + if (flags & ESI_SKIPPING) lookup_value = NULL; else { @@ -6381,12 +6514,12 @@ while (*s) if (*expand_string_message) goto EXPAND_FAILED; } switch(process_yesno( - skipping, /* were previously skipping */ - lookup_value != NULL, /* success/failure indicator */ - save_lookup_value, /* value to reset for string2 */ - &s, /* input pointer */ - &yield, /* output pointer */ - US"certextract", /* condition type */ + flags, /* were previously skipping */ + lookup_value != NULL, /* success/failure indicator */ + save_lookup_value, /* value to reset for string2 */ + &s, /* input pointer */ + &yield, /* output pointer */ + US"certextract", /* condition type */ &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ @@ -6395,7 +6528,8 @@ while (*s) restore_expand_strings(save_expand_nmax, save_expand_nstring, save_expand_nlength); - continue; + if (flags & ESI_SKIPPING) continue; + break; } #endif /*DISABLE_TLS*/ @@ -6419,7 +6553,8 @@ while (*s) goto EXPAND_FAILED_CURLY; /*}*/ } - if (!(list = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok))) + if (!(list = expand_string_internal(s, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL))) goto EXPAND_FAILED; /*{{*/ if (*s++ != '}') { @@ -6437,7 +6572,8 @@ while (*s) expand_string_message = US"missing '{' for second arg of reduce"; goto EXPAND_FAILED_CURLY; /*}*/ } - t = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok); + t = expand_string_internal(s, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL); if (!t) goto EXPAND_FAILED; lookup_value = t; /*{{*/ if (*s++ != '}') @@ -6464,7 +6600,8 @@ while (*s) the normal internal expansion function. */ if (item_type != EITEM_FILTER) - temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok); + temp = expand_string_internal(s, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | ESI_SKIPPING, &s, &resetok, NULL); else if ((temp = eval_condition(expr, &resetok, NULL))) s = temp; @@ -6495,7 +6632,7 @@ while (*s) /* If we are skipping, we can now just move on to the next item. When processing for real, we perform the iteration. */ - if (skipping) continue; + if (flags & ESI_SKIPPING) continue; while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0))) { *outsep = (uschar)sep; /* Separator as a string */ @@ -6506,6 +6643,9 @@ while (*s) if (item_type == EITEM_FILTER) { BOOL condresult; + /* the condition could modify $value, as a side-effect */ + uschar * save_value = lookup_value; + if (!eval_condition(expr, &resetok, &condresult)) { iterate_item = save_iterate_item; @@ -6514,6 +6654,7 @@ while (*s) expand_string_message, name); goto EXPAND_FAILED; } + lookup_value = save_value; DEBUG(D_expand) debug_printf_indent("%s: condition is %s\n", name, condresult? "true":"false"); if (condresult) @@ -6522,13 +6663,12 @@ while (*s) continue; /* FALSE => skip this item */ } - /* EITEM_MAP and EITEM_REDUCE */ - - else + else /* EITEM_MAP and EITEM_REDUCE */ { - uschar * t = expand_string_internal(expr, TRUE, NULL, skipping, TRUE, &resetok); - temp = t; - if (!temp) + /* the expansion could modify $value, as a side-effect */ + uschar * t = expand_string_internal(expr, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, NULL, &resetok, NULL); + if (!(temp = t)) { iterate_item = save_iterate_item; expand_string_message = string_sprintf("%s inside \"%s\" item", @@ -6549,6 +6689,9 @@ while (*s) item of the output list, add in a space if the new item begins with the separator character, or is an empty string. */ +/*XXX is there not a standard support function for this, appending to a list? */ +/* yes, string_append_listele(), but it depends on lack of text before the list */ + if ( yield && yield->ptr != save_ptr && (temp[0] == *outsep || temp[0] == 0)) yield = string_catn(yield, US" ", 1); @@ -6595,7 +6738,8 @@ while (*s) /* Restore preserved $item */ iterate_item = save_iterate_item; - continue; + if (flags & ESI_SKIPPING) continue; + break; } case EITEM_SORT: @@ -6613,7 +6757,8 @@ while (*s) goto EXPAND_FAILED_CURLY; /*}*/ } - srclist = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok); + srclist = expand_string_internal(s, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL); if (!srclist) goto EXPAND_FAILED; /*{{*/ if (*s++ != '}') { @@ -6628,7 +6773,8 @@ while (*s) goto EXPAND_FAILED_CURLY; /*}*/ } - cmp = expand_string_internal(s, TRUE, &s, skipping, FALSE, &resetok); + cmp = expand_string_internal(s, + ESI_BRACE_ENDS | flags & ESI_SKIPPING, &s, &resetok, NULL); if (!cmp) goto EXPAND_FAILED; /*{{*/ if (*s++ != '}') { @@ -6663,7 +6809,8 @@ while (*s) } xtract = s; - if (!(tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok))) + if (!(tmp = expand_string_internal(s, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | ESI_SKIPPING, &s, &resetok, NULL))) goto EXPAND_FAILED; xtract = string_copyn(xtract, s - xtract); /*{{*/ @@ -6679,7 +6826,7 @@ while (*s) goto EXPAND_FAILED; } - if (skipping) continue; + if (flags & ESI_SKIPPING) continue; while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0))) { @@ -6690,8 +6837,8 @@ while (*s) /* extract field for comparisons */ iterate_item = srcitem; - if ( !(srcfield = expand_string_internal(xtract, FALSE, NULL, FALSE, - TRUE, &resetok)) + if ( !(srcfield = expand_string_internal(xtract, + ESI_HONOR_DOLLAR, NULL, &resetok, NULL)) || !*srcfield) { expand_string_message = string_sprintf( @@ -6708,7 +6855,7 @@ while (*s) /* field for comparison */ if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0))) - goto sort_mismatch; + goto SORT_MISMATCH; /* String-comparator names start with a letter; numeric names do not */ @@ -6729,7 +6876,7 @@ while (*s) while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0))) { if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0))) - goto sort_mismatch; + goto SORT_MISMATCH; newlist = string_append_listele(newlist, sep, dstitem); newkeylist = string_append_listele(newkeylist, sep, dstfield); } @@ -6760,9 +6907,9 @@ while (*s) /* Restore preserved $item */ iterate_item = save_iterate_item; - continue; + break; - sort_mismatch: + SORT_MISMATCH: expand_string_message = US"Internal error in sort (list mismatch)"; goto EXPAND_FAILED; } @@ -6796,18 +6943,15 @@ while (*s) goto EXPAND_FAILED; } - switch(read_subs(argv, EXPAND_DLFUNC_MAX_ARGS + 2, 2, &s, skipping, - TRUE, name, &resetok)) + switch(read_subs(argv, EXPAND_DLFUNC_MAX_ARGS + 2, 2, &s, flags, + TRUE, name, &resetok, NULL)) { + case -1: continue; /* skipping */ case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; } - /* If skipping, we don't actually do anything */ - - if (skipping) continue; - /* Look up the dynamically loaded object handle in the tree. If it isn't found, dlopen() the file and put the handle in the tree for next time. */ @@ -6821,7 +6965,7 @@ while (*s) log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message); goto EXPAND_FAILED; } - t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0]), is_tainted(argv[0])); + t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0]), argv[0]); Ustrcpy(t->name, argv[0]); t->data.ptr = handle; (void)tree_insertnode(&dlobj_anchor, t); @@ -6861,7 +7005,7 @@ while (*s) } if (result) yield = string_cat(yield, result); - continue; + break; } #endif /* EXPAND_DLFUNC */ @@ -6873,7 +7017,8 @@ while (*s) if (Uskip_whitespace(&s) != '{') /*}*/ goto EXPAND_FAILED; - key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + key = expand_string_internal(s+1, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL); if (!key) goto EXPAND_FAILED; /*{{*/ if (*s++ != '}') { @@ -6884,18 +7029,19 @@ while (*s) lookup_value = US getenv(CS key); switch(process_yesno( - skipping, /* were previously skipping */ - lookup_value != NULL, /* success/failure indicator */ - save_lookup_value, /* value to reset for string2 */ - &s, /* input pointer */ - &yield, /* output pointer */ - US"env", /* condition type */ + flags, /* were previously skipping */ + lookup_value != NULL, /* success/failure indicator */ + save_lookup_value, /* value to reset for string2 */ + &s, /* input pointer */ + &yield, /* output pointer */ + US"env", /* condition type */ &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ } - continue; + if (flags & ESI_SKIPPING) continue; + break; } #ifdef SUPPORT_SRS @@ -6907,79 +7053,99 @@ while (*s) gstring * g = NULL; BOOL quoted = FALSE; - switch (read_subs(sub, 3, 3, CUSS &s, skipping, TRUE, name, &resetok)) + switch (read_subs(sub, 3, 3, CUSS &s, flags, TRUE, name, &resetok, NULL)) { + case -1: continue; /* skipping */ case 1: goto EXPAND_FAILED_CURLY; case 2: case 3: goto EXPAND_FAILED; } + if (flags & ESI_SKIPPING) continue; - g = string_catn(g, US"SRS0=", 5); - - /* ${l_4:${hmac{md5}{SRS_SECRET}{${lc:$return_path}}}}= */ - hmac_md5(sub[0], string_copylc(sub[1]), cksum, sizeof(cksum)); - g = string_catn(g, cksum, sizeof(cksum)); - g = string_catn(g, US"=", 1); - - /* ${base32:${eval:$tod_epoch/86400&0x3ff}}= */ + if (sub[1] && *(sub[1])) { - struct timeval now; - unsigned long i; - gstring * h = NULL; - - gettimeofday(&now, NULL); - for (unsigned long i = (now.tv_sec / 86400) & 0x3ff; i; i >>= 5) - h = string_catn(h, &base32_chars[i & 0x1f], 1); - if (h) while (h->ptr > 0) - g = string_catn(g, &h->s[--h->ptr], 1); - } - g = string_catn(g, US"=", 1); + g = string_catn(g, US"SRS0=", 5); - /* ${domain:$return_path}=${local_part:$return_path} */ - { - int start, end, domain; - uschar * t = parse_extract_address(sub[1], &expand_string_message, - &start, &end, &domain, FALSE); - uschar * s; + /* ${l_4:${hmac{md5}{SRS_SECRET}{${lc:$return_path}}}}= */ + hmac_md5(sub[0], string_copylc(sub[1]), cksum, sizeof(cksum)); + g = string_catn(g, cksum, sizeof(cksum)); + g = string_catn(g, US"=", 1); - if (!t) - goto EXPAND_FAILED; + /* ${base32:${eval:$tod_epoch/86400&0x3ff}}= */ + { + struct timeval now; + unsigned long i; - if (domain > 0) g = string_cat(g, t + domain); + gettimeofday(&now, NULL); + i = (now.tv_sec / 86400) & 0x3ff; + g = string_catn(g, &base32_chars[i >> 5], 1); + g = string_catn(g, &base32_chars[i & 0x1f], 1); + } g = string_catn(g, US"=", 1); - s = domain > 0 ? string_copyn(t, domain - 1) : t; - if ((quoted = Ustrchr(s, '"') != NULL)) + /* ${domain:$return_path}=${local_part:$return_path} */ { - gstring * h = NULL; - DEBUG(D_expand) debug_printf_indent("auto-quoting local part\n"); - while (*s) /* de-quote */ + int start, end, domain; + uschar * t = parse_extract_address(sub[1], &expand_string_message, + &start, &end, &domain, FALSE); + uschar * s; + + if (!t) + goto EXPAND_FAILED; + + if (domain > 0) g = string_cat(g, t + domain); + g = string_catn(g, US"=", 1); + + s = domain > 0 ? string_copyn(t, domain - 1) : t; + if ((quoted = Ustrchr(s, '"') != NULL)) { - while (*s && *s != '"') h = string_catn(h, s++, 1); - if (*s) s++; - while (*s && *s != '"') h = string_catn(h, s++, 1); - if (*s) s++; + gstring * h = NULL; + DEBUG(D_expand) debug_printf_indent("auto-quoting local part\n"); + while (*s) /* de-quote */ + { + while (*s && *s != '"') h = string_catn(h, s++, 1); + if (*s) s++; + while (*s && *s != '"') h = string_catn(h, s++, 1); + if (*s) s++; + } + gstring_release_unused(h); + s = string_from_gstring(h); } - gstring_release_unused(h); - s = string_from_gstring(h); + g = string_cat(g, s); } - g = string_cat(g, s); - } - /* Assume that if the original local_part had quotes - it was for good reason */ + /* Assume that if the original local_part had quotes + it was for good reason */ - if (quoted) yield = string_catn(yield, US"\"", 1); - yield = string_catn(yield, g->s, g->ptr); - if (quoted) yield = string_catn(yield, US"\"", 1); + if (quoted) yield = string_catn(yield, US"\"", 1); + yield = gstring_append(yield, g); + if (quoted) yield = string_catn(yield, US"\"", 1); - /* @$original_domain */ - yield = string_catn(yield, US"@", 1); - yield = string_cat(yield, sub[2]); - continue; + /* @$original_domain */ + yield = string_catn(yield, US"@", 1); + yield = string_cat(yield, sub[2]); + } + else + DEBUG(D_expand) debug_printf_indent("null return_path for srs-encode\n"); + + break; } #endif /*SUPPORT_SRS*/ + + default: + goto NOT_ITEM; } /* EITEM_* switch */ + /*NOTREACHED*/ + + DEBUG(D_expand) /* only if not the sole expansion of the line */ + if (yield && (expansion_start > 0 || *s)) + debug_expansion_interim(US"item-res", + yield->s + expansion_start, yield->ptr - expansion_start, + !!(flags & ESI_SKIPPING)); + continue; + +NOT_ITEM: ; + } /* Control reaches here if the name is not recognized as one of the more complicated expansion items. Check for the "operator" syntax (name terminated @@ -6989,10 +7155,9 @@ while (*s) if (*s == ':') { int c; - uschar *arg = NULL; - uschar *sub; + uschar * arg = NULL, * sub; #ifndef DISABLE_TLS - var_entry *vp = NULL; + var_entry * vp = NULL; #endif /* Owing to an historical mis-design, an underscore may be part of the @@ -7012,6 +7177,7 @@ while (*s) /* Deal specially with operators that might take a certificate variable as we do not want to do the usual expansion. For most, expand the string.*/ + switch(c) { #ifndef DISABLE_TLS @@ -7022,8 +7188,8 @@ while (*s) if (s[1] == '$') { const uschar * s1 = s; - sub = expand_string_internal(s+2, TRUE, &s1, skipping, - FALSE, &resetok); + sub = expand_string_internal(s+2, + ESI_BRACE_ENDS | flags & ESI_SKIPPING, &s1, &resetok, NULL); if (!sub) goto EXPAND_FAILED; /*{*/ if (*s1 != '}') { /*{*/ @@ -7041,7 +7207,8 @@ while (*s) /*FALLTHROUGH*/ #endif default: - sub = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + sub = expand_string_internal(s+1, + ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL); if (!sub) goto EXPAND_FAILED; s++; break; @@ -7053,22 +7220,22 @@ while (*s) for the existence of $sender_host_address before trying to mask it. For other operations, doing them may not fail, but it is a waste of time. */ - if (skipping && c >= 0) continue; + if (flags & ESI_SKIPPING && c >= 0) continue; /* Otherwise, switch on the operator type. After handling go back to the main loop top. */ { - int start = yield->ptr; + unsigned expansion_start = gstring_length(yield); switch(c) { case EOP_BASE32: { - uschar *t; + uschar * t; unsigned long int n = Ustrtoul(sub, &t, 10); gstring * g = NULL; - if (*t != 0) + if (*t) { expand_string_message = string_sprintf("argument for base32 " "operator is \"%s\", which is not a decimal number", sub); @@ -7104,13 +7271,13 @@ while (*s) { uschar *t; unsigned long int n = Ustrtoul(sub, &t, 10); - if (*t != 0) + if (*t) { expand_string_message = string_sprintf("argument for base62 " "operator is \"%s\", which is not a decimal number", sub); goto EXPAND_FAILED; } - yield = string_cat(yield, string_base62(n)); + yield = string_cat(yield, string_base62_32(n)); /*XXX only handles 32b input range. Need variants? */ break; } @@ -7120,7 +7287,7 @@ while (*s) { uschar *tt = sub; unsigned long int n = 0; - while (*tt != 0) + while (*tt) { uschar *t = Ustrchr(base62_chars, *tt++); if (!t) @@ -7138,7 +7305,8 @@ while (*s) case EOP_EXPAND: { - uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE, &resetok); + uschar *expanded = expand_string_internal(sub, + ESI_HONOR_DOLLAR | flags & ESI_SKIPPING, NULL, &resetok, NULL); if (!expanded) { expand_string_message = @@ -7169,14 +7337,14 @@ while (*s) } case EOP_MD5: - #ifndef DISABLE_TLS +#ifndef DISABLE_TLS if (vp && *(void **)vp->value) { uschar * cp = tls_cert_fprt_md5(*(void **)vp->value); yield = string_cat(yield, cp); } else - #endif +#endif { md5 base; uschar digest[16]; @@ -7188,14 +7356,14 @@ while (*s) break; case EOP_SHA1: - #ifndef DISABLE_TLS +#ifndef DISABLE_TLS if (vp && *(void **)vp->value) { uschar * cp = tls_cert_fprt_sha1(*(void **)vp->value); yield = string_cat(yield, cp); } else - #endif +#endif { hctx h; uschar digest[20]; @@ -7208,7 +7376,7 @@ while (*s) case EOP_SHA2: case EOP_SHA256: - #ifdef EXIM_HAVE_SHA2 +#ifdef EXIM_HAVE_SHA2 if (vp && *(void **)vp->value) if (c == EOP_SHA256) yield = string_cat(yield, tls_cert_fprt_sha256(*(void **)vp->value)); @@ -7230,18 +7398,18 @@ while (*s) goto EXPAND_FAILED; } - exim_sha_update(&h, sub, Ustrlen(sub)); + exim_sha_update_string(&h, sub); exim_sha_finish(&h, &b); while (b.len-- > 0) yield = string_fmt_append(yield, "%02X", *b.data++); } - #else +#else expand_string_message = US"sha256 only supported with TLS"; - #endif +#endif break; case EOP_SHA3: - #ifdef EXIM_HAVE_SHA3 +#ifdef EXIM_HAVE_SHA3 { hctx h; blob b; @@ -7258,16 +7426,39 @@ while (*s) goto EXPAND_FAILED; } - exim_sha_update(&h, sub, Ustrlen(sub)); + exim_sha_update_string(&h, sub); exim_sha_finish(&h, &b); while (b.len-- > 0) yield = string_fmt_append(yield, "%02X", *b.data++); } break; - #else +#else expand_string_message = US"sha3 only supported with GnuTLS 3.5.0 + or OpenSSL 1.1.1 +"; goto EXPAND_FAILED; - #endif +#endif + + /* Line-wrap a string as if it is a header line */ + + case EOP_HEADERWRAP: + { + unsigned col = 80, lim = 998; + uschar * s; + + if (arg) + { + const uschar * list = arg; + int sep = '_'; + if ((s = string_nextinlist(&list, &sep, NULL, 0))) + { + col = atoi(CS s); + if ((s = string_nextinlist(&list, &sep, NULL, 0))) + lim = atoi(CS s); + } + } + if ((s = wrap_header(sub, col, lim, US"\t", 8))) + yield = string_cat(yield, s); + } + break; /* Convert hex encoding to base64 encoding */ @@ -7335,7 +7526,7 @@ while (*s) case EOP_LISTCOUNT: { int cnt = 0, sep = 0; - uschar * buf = store_get(2, is_tainted(sub)); + uschar * buf = store_get(2, sub); while (string_nextinlist(CUSS &sub, &sep, buf, 1)) cnt++; yield = string_fmt_append(yield, "%d", cnt); @@ -7595,10 +7786,10 @@ while (*s) goto EXPAND_FAILED; } - if (lookup_list[n]->quote) - sub = (lookup_list[n]->quote)(sub, opt); - else if (opt) - sub = NULL; + if (lookup_list[n]->quote) + sub = (lookup_list[n]->quote)(sub, opt, (unsigned)n); + else if (opt) + sub = NULL; if (!sub) { @@ -7658,7 +7849,7 @@ while (*s) case EOP_FROM_UTF8: { - uschar * buff = store_get(4, is_tainted(sub)); + uschar * buff = store_get(4, sub); while (*sub) { int c; @@ -7676,21 +7867,21 @@ while (*s) case EOP_UTF8CLEAN: { - int seq_len = 0, index = 0; - int bytes_left = 0; - long codepoint = -1; - int complete; + int seq_len = 0, index = 0, bytes_left = 0, complete; + u_long codepoint = (u_long)-1; uschar seq_buff[4]; /* accumulate utf-8 here */ /* Manually track tainting, as we deal in individual chars below */ - if (is_tainted(sub)) + if (!yield) + yield = string_get_tainted(Ustrlen(sub), sub); + else if (!yield->s || !yield->ptr) { - if (yield->s && yield->ptr) - gstring_rebuffer(yield); - else - yield->s = store_get(yield->size = Ustrlen(sub), is_tainted(sub)); + yield->s = store_get(yield->size = Ustrlen(sub), sub); + gstring_reset(yield); } + else if (is_incompatible(yield->s, sub)) + gstring_rebuffer(yield, sub); /* Check the UTF-8, byte-by-byte */ @@ -7711,6 +7902,15 @@ while (*s) if (--bytes_left == 0) /* codepoint complete */ if(codepoint > 0x10FFFF) /* is it too large? */ complete = -1; /* error (RFC3629 limit) */ + else if ( (codepoint & 0x1FF800 ) == 0xD800 ) /* surrogate */ + /* A UTF-16 surrogate (which should be one of a pair that + encode a Unicode codepoint that is outside the Basic + Multilingual Plane). Error, not UTF8. + RFC2279.2 is slightly unclear on this, but + https://unicodebook.readthedocs.io/issues.html#strict-utf8-decoder + says "Surrogates characters are also invalid in UTF-8: + characters in U+D800—U+DFFF have to be rejected." */ + complete = -1; else { /* finished; output utf-8 sequence */ yield = string_catn(yield, seq_buff, seq_len); @@ -7720,27 +7920,25 @@ while (*s) } else /* no bytes left: new sequence */ { - if(!(c & 0x80)) /* 1-byte sequence, US-ASCII, keep it */ + if (!(c & 0x80)) /* 1-byte sequence, US-ASCII, keep it */ { yield = string_catn(yield, &c, 1); continue; } - if((c & 0xe0) == 0xc0) /* 2-byte sequence */ - { - if(c == 0xc0 || c == 0xc1) /* 0xc0 and 0xc1 are illegal */ + if ((c & 0xe0) == 0xc0) /* 2-byte sequence */ + if (c == 0xc0 || c == 0xc1) /* 0xc0 and 0xc1 are illegal */ complete = -1; else { - bytes_left = 1; - codepoint = c & 0x1f; + bytes_left = 1; + codepoint = c & 0x1f; } - } - else if((c & 0xf0) == 0xe0) /* 3-byte sequence */ + else if ((c & 0xf0) == 0xe0) /* 3-byte sequence */ { bytes_left = 2; codepoint = c & 0x0f; } - else if((c & 0xf8) == 0xf0) /* 4-byte sequence */ + else if ((c & 0xf8) == 0xf0) /* 4-byte sequence */ { bytes_left = 3; codepoint = c & 0x07; @@ -7771,7 +7969,7 @@ while (*s) break; } - #ifdef SUPPORT_I18N +#ifdef SUPPORT_I18N case EOP_UTF8_DOMAIN_TO_ALABEL: { uschar * error = NULL; @@ -7814,7 +8012,7 @@ while (*s) goto EXPAND_FAILED; } yield = string_cat(yield, s); - DEBUG(D_expand) debug_printf_indent("yield: '%s'\n", yield->s); + DEBUG(D_expand) debug_printf_indent("yield: '%Y'\n", yield); break; } @@ -7832,7 +8030,7 @@ while (*s) yield = string_cat(yield, s); break; } - #endif /* EXPERIMENTAL_INTERNATIONAL */ +#endif /* EXPERIMENTAL_INTERNATIONAL */ /* escape turns all non-printing characters into escape sequences. */ @@ -7908,13 +8106,13 @@ while (*s) case EOP_STR2B64: case EOP_BASE64: { - #ifndef DISABLE_TLS +#ifndef DISABLE_TLS uschar * s = vp && *(void **)vp->value ? tls_cert_der_b64(*(void **)vp->value) : b64encode(CUS sub, Ustrlen(sub)); - #else +#else uschar * s = b64encode(CUS sub, Ustrlen(sub)); - #endif +#endif yield = string_cat(yield, s); break; } @@ -8123,8 +8321,9 @@ while (*s) DEBUG(D_expand) { - const uschar * s = yield->s + start; - int i = yield->ptr - start; + const uschar * res = string_from_gstring(yield); + const uschar * s = res + expansion_start; + int i = gstring_length(yield) - expansion_start; BOOL tainted = is_tainted(s); DEBUG(D_noutf8) @@ -8132,8 +8331,8 @@ while (*s) debug_printf_indent("|-----op-res: %.*s\n", i, s); if (tainted) { - debug_printf_indent("%s \\__", skipping ? "| " : " "); - debug_printf("(tainted)\n"); + debug_printf_indent("%s \\__", flags & ESI_SKIPPING ? "| " : " "); + debug_print_taint(res); } } else @@ -8144,9 +8343,9 @@ while (*s) if (tainted) { debug_printf_indent("%s", - skipping + flags & ESI_SKIPPING ? UTF8_VERT " " : " " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ); - debug_printf("(tainted)\n"); + debug_print_taint(res); } } } @@ -8170,15 +8369,15 @@ while (*s) gstring * g = NULL; if (!yield) - g = store_get(sizeof(gstring), FALSE); + g = store_get(sizeof(gstring), GET_UNTAINTED); else if (yield->ptr == 0) { if (resetok) reset_point = store_reset(reset_point); yield = NULL; reset_point = store_mark(); - g = store_get(sizeof(gstring), FALSE); /* alloc _before_ calling find_variable() */ + g = store_get(sizeof(gstring), GET_UNTAINTED); /* alloc _before_ calling find_variable() */ } - if (!(value = find_variable(name, FALSE, skipping, &newsize))) + if (!(value = find_variable(name, FALSE, !!(flags & ESI_SKIPPING), &newsize))) { expand_string_message = string_sprintf("unknown variable in \"${%s}\"", name); @@ -8206,11 +8405,11 @@ while (*s) goto EXPAND_FAILED; } -/* If we hit the end of the string when ket_ends is set, there is a missing +/* If we hit the end of the string when brace_ends is set, there is a missing terminating brace. */ -if (ket_ends && !*s) - { +if (flags & ESI_BRACE_ENDS && !*s) + { /*{{*/ expand_string_message = malformed_header ? US"missing } at end of string - could be header name not terminated by colon" : US"missing } at end of string"; @@ -8221,52 +8420,62 @@ if (ket_ends && !*s) added to the string. If so, set up an empty string. Add a terminating zero. If left != NULL, return a pointer to the terminator. */ -if (!yield) - yield = string_get(1); -(void) string_from_gstring(yield); -if (left) *left = s; + { + uschar * res; -/* Any stacking store that was used above the final string is no longer needed. -In many cases the final string will be the first one that was got and so there -will be optimal store usage. */ + if (!yield) + yield = string_get(1); + res = string_from_gstring(yield); + if (left) *left = s; -if (resetok) gstring_release_unused(yield); -else if (resetok_p) *resetok_p = FALSE; + /* Any stacking store that was used above the final string is no longer needed. + In many cases the final string will be the first one that was got and so there + will be optimal store usage. */ -DEBUG(D_expand) - { - BOOL tainted = is_tainted(yield->s); - DEBUG(D_noutf8) - { - debug_printf_indent("|--expanding: %.*s\n", (int)(s - string), string); - debug_printf_indent("%sresult: %s\n", - skipping ? "|-----" : "\\_____", yield->s); - if (tainted) - debug_printf_indent("%s \\__(tainted)\n", - skipping ? "| " : " "); - if (skipping) - debug_printf_indent("\\___skipping: result is not used\n"); - } - else + if (resetok) gstring_release_unused(yield); + else if (resetok_p) *resetok_p = FALSE; + + DEBUG(D_expand) { - debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ - "expanding: %.*s\n", - (int)(s - string), string); - debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ - "result: %s\n", - skipping ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT, - yield->s); - if (tainted) - debug_printf_indent("%s(tainted)\n", - skipping - ? UTF8_VERT " " : " " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ); - if (skipping) - debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ - "skipping: result is not used\n"); + BOOL tainted = is_tainted(res); + DEBUG(D_noutf8) + { + debug_printf_indent("|--expanding: %.*s\n", (int)(s - string), string); + debug_printf_indent("%sresult: %s\n", + flags & ESI_SKIPPING ? "|-----" : "\\_____", res); + if (tainted) + { + debug_printf_indent("%s \\__", flags & ESI_SKIPPING ? "| " : " "); + debug_print_taint(res); + } + if (flags & ESI_SKIPPING) + debug_printf_indent("\\___skipping: result is not used\n"); + } + else + { + debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ + "expanding: %.*s\n", + (int)(s - string), string); + debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ + "result: %s\n", + flags & ESI_SKIPPING ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT, + res); + if (tainted) + { + debug_printf_indent("%s", + flags & ESI_SKIPPING + ? UTF8_VERT " " : " " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ); + debug_print_taint(res); + } + if (flags & ESI_SKIPPING) + debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ + "skipping: result is not used\n"); + } } - } -expand_level--; -return yield->s; + if (textonly_p) *textonly_p = textonly; + expand_level--; + return res; + } /* This is the failure exit: easiest to program with a goto. We still need to update the pointer to the terminator, for cases of nested calls with "fail". @@ -8313,16 +8522,20 @@ return NULL; } + /* This is the external function call. Do a quick check for any expansion metacharacters, and if there are none, just return the input string. -Argument: the string to be expanded +Arguments + the string to be expanded + optional pointer for return boolean indicating no-dynamic-expansions + Returns: the expanded string, or NULL if expansion failed; if failure was due to a lookup deferring, search_find_defer will be TRUE */ const uschar * -expand_cstring(const uschar * string) +expand_string_2(const uschar * string, BOOL * textonly_p) { if (Ustrpbrk(string, "$\\") != NULL) { @@ -8332,19 +8545,22 @@ if (Ustrpbrk(string, "$\\") != NULL) f.search_find_defer = FALSE; malformed_header = FALSE; store_pool = POOL_MAIN; - s = expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL); + s = expand_string_internal(string, ESI_HONOR_DOLLAR, NULL, NULL, textonly_p); store_pool = old_pool; return s; } +if (textonly_p) *textonly_p = TRUE; return string; } +const uschar * +expand_cstring(const uschar * string) +{ return expand_string_2(string, NULL); } uschar * expand_string(uschar * string) -{ -return US expand_cstring(CUS string); -} +{ return US expand_string_2(CUS string, NULL); } + @@ -8641,12 +8857,14 @@ assert_variable_notin() treats as const, so deconst is safe. */ for (int i = 0; i < AUTH_VARS; i++) if (auth_vars[i]) assert_variable_notin(US"auth", US auth_vars[i], &e); +#ifdef WITH_CONTENT_SCAN /* check regex variables. assert_variable_notin() treats as const. */ for (int i = 0; i < REGEX_VARS; i++) if (regex_vars[i]) assert_variable_notin(US"regex", US regex_vars[i], &e); +#endif /* check known-name variables */ -for (var_entry * v = var_table; v < var_table + var_table_size; v++) +for (var_entry * v = var_table; v < var_table + nelem(var_table); v++) if (v->type == vtype_stringptr) assert_variable_notin(US v->name, *(USS v->value), &e); @@ -8780,8 +8998,9 @@ search_tidyup(); return 0; } -#endif +#endif /*STAND_ALONE*/ +#endif /*!MACRO_PREDEF*/ /* vi: aw ai sw=2 */ /* End of expand.c */