X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/18eacc1185bea956bd9c793ab34e582c08941799..HEAD:/src/src/expand.c diff --git a/src/src/expand.c b/src/src/expand.c index 1d0ddec2a..e1e6e1999 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) The Exim Maintainers 2020 - 2023 */ +/* Copyright (c) The Exim Maintainers 2020 - 2024 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* SPDX-License-Identifier: GPL-2.0-or-later */ @@ -22,6 +22,7 @@ typedef unsigned esi_flags; #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 */ +#define ESI_EXISTS_ONLY BIT(3) /* actual value not needed */ #ifdef STAND_ALONE # ifndef SUPPORT_CRYPTEQ @@ -29,10 +30,6 @@ typedef unsigned esi_flags; # endif #endif /*!STAND_ALONE*/ -#ifdef LOOKUP_LDAP -# include "lookups/ldap.h" -#endif - #ifdef SUPPORT_CRYPTEQ # ifdef CRYPT_H # include @@ -259,7 +256,9 @@ static uschar *op_table_main[] = { US"strlen", US"substr", US"uc", - US"utf8clean" }; + US"utf8clean", + US"xtextd", + }; enum { EOP_ADDRESS = nelem(op_table_underscore), @@ -307,7 +306,9 @@ enum { EOP_STRLEN, EOP_SUBSTR, EOP_UC, - EOP_UTF8CLEAN }; + EOP_UTF8CLEAN, + EOP_XTEXTD, + }; /* Table of condition names, and corresponding switch numbers. The names must @@ -420,51 +421,6 @@ enum { }; -/* Types of table entry */ - -enum vtypes { - vtype_int, /* value is address of int */ - vtype_filter_int, /* ditto, but recognized only when filtering */ - vtype_ino, /* value is address of ino_t (not always an int) */ - vtype_uid, /* value is address of uid_t (not always an int) */ - vtype_gid, /* value is address of gid_t (not always an int) */ - vtype_bool, /* value is address of bool */ - vtype_stringptr, /* value is address of pointer to string */ - vtype_msgbody, /* as stringptr, but read when first required */ - vtype_msgbody_end, /* ditto, the end of the message */ - vtype_msgheaders, /* the message's headers, processed */ - vtype_msgheaders_raw, /* the message's headers, unprocessed */ - vtype_localpart, /* extract local part from string */ - vtype_domain, /* extract domain from string */ - vtype_string_func, /* value is string returned by given function */ - vtype_todbsdin, /* value not used; generate BSD inbox tod */ - vtype_tode, /* value not used; generate tod in epoch format */ - vtype_todel, /* value not used; generate tod in epoch/usec format */ - vtype_todf, /* value not used; generate full tod */ - vtype_todl, /* value not used; generate log tod */ - vtype_todlf, /* value not used; generate log file datestamp tod */ - vtype_todzone, /* value not used; generate time zone only */ - vtype_todzulu, /* value not used; generate zulu tod */ - vtype_reply, /* value not used; get reply from headers */ - vtype_pid, /* value not used; result is pid */ - vtype_host_lookup, /* value not used; get host name */ - vtype_load_avg, /* value not used; result is int from os_getloadavg */ - 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 - ,vtype_dkim /* Lookup of value in DKIM signature */ -#endif -}; - -/* Type for main variable table */ - -typedef struct { - const char *name; - enum vtypes type; - void *value; -} var_entry; - /* Type for entries pointing to address/length pairs. Not currently in use. */ @@ -498,10 +454,10 @@ static var_entry var_table[] = { { "address_file", vtype_stringptr, &address_file }, { "address_pipe", vtype_stringptr, &address_pipe }, #ifdef EXPERIMENTAL_ARC - { "arc_domains", vtype_string_func, (void *) &fn_arc_domains }, - { "arc_oldest_pass", vtype_int, &arc_oldest_pass }, - { "arc_state", vtype_stringptr, &arc_state }, - { "arc_state_reason", vtype_stringptr, &arc_state_reason }, + { "arc_domains", vtype_module, US"arc" }, + { "arc_oldest_pass", vtype_module, US"arc" }, + { "arc_state", vtype_module, US"arc" }, + { "arc_state_reason", vtype_module, US"arc" }, #endif { "authenticated_fail_id",vtype_stringptr, &authenticated_fail_id }, { "authenticated_id", vtype_stringptr, &authenticated_id }, @@ -527,39 +483,41 @@ static var_entry var_table[] = { { "compile_number", vtype_stringptr, &version_cnumber }, { "config_dir", vtype_stringptr, &config_main_directory }, { "config_file", vtype_stringptr, &config_main_filename }, + { "connection_id", vtype_stringptr, &connection_id }, { "csa_status", vtype_stringptr, &csa_status }, #ifdef EXPERIMENTAL_DCC { "dcc_header", vtype_stringptr, &dcc_header }, { "dcc_result", vtype_stringptr, &dcc_result }, #endif #ifndef DISABLE_DKIM - { "dkim_algo", vtype_dkim, (void *)DKIM_ALGO }, - { "dkim_bodylength", vtype_dkim, (void *)DKIM_BODYLENGTH }, - { "dkim_canon_body", vtype_dkim, (void *)DKIM_CANON_BODY }, - { "dkim_canon_headers", vtype_dkim, (void *)DKIM_CANON_HEADERS }, - { "dkim_copiedheaders", vtype_dkim, (void *)DKIM_COPIEDHEADERS }, - { "dkim_created", vtype_dkim, (void *)DKIM_CREATED }, - { "dkim_cur_signer", vtype_stringptr, &dkim_cur_signer }, - { "dkim_domain", vtype_stringptr, &dkim_signing_domain }, - { "dkim_expires", vtype_dkim, (void *)DKIM_EXPIRES }, - { "dkim_headernames", vtype_dkim, (void *)DKIM_HEADERNAMES }, - { "dkim_identity", vtype_dkim, (void *)DKIM_IDENTITY }, - { "dkim_key_granularity",vtype_dkim, (void *)DKIM_KEY_GRANULARITY }, - { "dkim_key_length", vtype_int, &dkim_key_length }, - { "dkim_key_nosubdomains",vtype_dkim, (void *)DKIM_NOSUBDOMAINS }, - { "dkim_key_notes", vtype_dkim, (void *)DKIM_KEY_NOTES }, - { "dkim_key_srvtype", vtype_dkim, (void *)DKIM_KEY_SRVTYPE }, - { "dkim_key_testing", vtype_dkim, (void *)DKIM_KEY_TESTING }, - { "dkim_selector", vtype_stringptr, &dkim_signing_selector }, - { "dkim_signers", vtype_stringptr, &dkim_signers }, - { "dkim_verify_reason", vtype_stringptr, &dkim_verify_reason }, - { "dkim_verify_status", vtype_stringptr, &dkim_verify_status }, + { "dkim_algo", vtype_module, US"dkim" }, + { "dkim_bodylength", vtype_module, US"dkim" }, + { "dkim_canon_body", vtype_module, US"dkim" }, + { "dkim_canon_headers", vtype_module, US"dkim" }, + { "dkim_copiedheaders", vtype_module, US"dkim" }, + { "dkim_created", vtype_module, US"dkim" }, + { "dkim_cur_signer", vtype_module, US"dkim" }, + { "dkim_domain", vtype_module, US"dkim" }, + { "dkim_expires", vtype_module, US"dkim" }, + { "dkim_headernames", vtype_module, US"dkim" }, + { "dkim_identity", vtype_module, US"dkim" }, + { "dkim_key_granularity",vtype_module, US"dkim" }, + { "dkim_key_length", vtype_module, US"dkim" }, + { "dkim_key_nosubdomains",vtype_module, US"dkim" }, + { "dkim_key_notes", vtype_module, US"dkim" }, + { "dkim_key_srvtype", vtype_module, US"dkim" }, + { "dkim_key_testing", vtype_module, US"dkim" }, + { "dkim_selector", vtype_module, US"dkim" }, + { "dkim_signers", vtype_module, US"dkim" }, + { "dkim_verify_reason", vtype_module, US"dkim" }, + { "dkim_verify_signers", vtype_module, US"dkim" }, + { "dkim_verify_status", vtype_module, US"dkim" }, #endif #ifdef SUPPORT_DMARC - { "dmarc_domain_policy", vtype_stringptr, &dmarc_domain_policy }, - { "dmarc_status", vtype_stringptr, &dmarc_status }, - { "dmarc_status_text", vtype_stringptr, &dmarc_status_text }, - { "dmarc_used_domain", vtype_stringptr, &dmarc_used_domain }, + { "dmarc_domain_policy", vtype_module, US"dmarc" }, + { "dmarc_status", vtype_module, US"dmarc" }, + { "dmarc_status_text", vtype_module, US"dmarc" }, + { "dmarc_used_domain", vtype_module, US"dmarc" }, #endif { "dnslist_domain", vtype_stringptr, &dnslist_domain }, { "dnslist_matched", vtype_stringptr, &dnslist_matched }, @@ -752,12 +710,12 @@ static var_entry var_table[] = { { "spam_score_int", vtype_stringptr, &spam_score_int }, #endif #ifdef SUPPORT_SPF - { "spf_guess", vtype_stringptr, &spf_guess }, - { "spf_header_comment", vtype_stringptr, &spf_header_comment }, - { "spf_received", vtype_stringptr, &spf_received }, - { "spf_result", vtype_stringptr, &spf_result }, - { "spf_result_guessed", vtype_bool, &spf_result_guessed }, - { "spf_smtp_comment", vtype_stringptr, &spf_smtp_comment }, + { "spf_guess", vtype_module, US"spf" }, + { "spf_header_comment", vtype_module, US"spf" }, + { "spf_received", vtype_module, US"spf" }, + { "spf_result", vtype_module, US"spf" }, + { "spf_result_guessed", vtype_module, US"spf" }, + { "spf_smtp_comment", vtype_module, US"spf" }, #endif { "spool_directory", vtype_stringptr, &spool_directory }, { "spool_inodes", vtype_pinodes, (void *)TRUE }, @@ -1038,9 +996,10 @@ Returns: TRUE if condition is met, FALSE if not */ BOOL -expand_check_condition(uschar *condition, uschar *m1, uschar *m2) +expand_check_condition(const uschar * condition, + const uschar * m1, const uschar * m2) { -uschar * ss = expand_string(condition); +const uschar * ss = expand_cstring(condition); if (!ss) { if (!f.expand_string_forcedfail && !f.search_find_defer) @@ -1262,7 +1221,8 @@ while (*s) while (*s && *s != '=' && !isspace(*s)) s++; dkeylength = s - dkey; - if (Uskip_whitespace(&s) == '=') while (isspace(*++s)); + if (Uskip_whitespace(&s) == '=') + while (isspace(*++s)) ; data = string_dequote(&s); if (length == dkeylength && strncmpic(key, dkey, length) == 0) @@ -1277,19 +1237,19 @@ return NULL; static var_entry * -find_var_ent(uschar * name) +find_var_ent(uschar * name, var_entry * table, unsigned nent) { int first = 0; -int last = nelem(var_table); +int last = nent; while (last > first) { int middle = (first + last)/2; - int c = Ustrcmp(name, var_table[middle].name); + int c = Ustrcmp(name, table[middle].name); if (c > 0) { first = middle + 1; continue; } if (c < 0) { last = middle; continue; } - return &var_table[middle]; + return &table[middle]; } return NULL; } @@ -1416,7 +1376,7 @@ expand_getcertele(uschar * field, uschar * certvar) { var_entry * vp; -if (!(vp = find_var_ent(certvar))) +if (!(vp = find_var_ent(certvar, var_table, nelem(var_table)))) { expand_string_message = string_sprintf("no variable named \"%s\"", certvar); @@ -1804,20 +1764,19 @@ return g; /* A recipients list is available only during system message filtering, during ACL processing after DATA, and while expanding pipe commands generated from a system filter, but not elsewhere. Note that this does -not check for comman in the elements, and uses comma-space as seperator - +not check for commas in the elements, and uses comma-space as seperator - so cannot be used as an exim list as-is. */ static uschar * fn_recipients(void) { -uschar * s; gstring * g = NULL; if (!f.enable_dollar_recipients) return NULL; for (int i = 0; i < recipients_count; i++) { - s = recipients_list[i].address; + const uschar * s = recipients_list[i].address; g = string_append2_listele_n(g, US", ", s, Ustrlen(s)); } gstring_release_unused(g); @@ -1915,8 +1874,9 @@ chop. Arguments: name the name of the variable being sought - exists_only TRUE if this is a def: test; passed on to find_header() - skipping TRUE => skip any processing evaluation; this is not the same as + flags + exists_only TRUE if this is a def: test; passed on to find_header() + skipping TRUE => skip any processing evaluation; this is not the same as exists_only because def: may test for values that are first evaluated here newsize pointer to an int which is initially zero; if the answer is in @@ -1928,12 +1888,14 @@ Returns: NULL if the variable does not exist, or */ static const uschar * -find_variable(uschar *name, BOOL exists_only, BOOL skipping, int *newsize) +find_variable(uschar * name, esi_flags flags, int * newsize) { var_entry * vp; -uschar *s, *domain; -uschar **ss; +uschar * s, * domain; +uschar ** ss; void * val; +var_entry * table = var_table; +unsigned table_count = nelem(var_table); /* Handle ACL variables, whose names are of the form acl_cxxx or acl_mxxx. Originally, xxx had to be a number in the range 0-9 (later 0-19), but from @@ -1978,15 +1940,17 @@ else if (Ustrncmp(name, "regex", 5) == 0) } #endif +sublist: + /* For all other variables, search the table */ -if (!(vp = find_var_ent(name))) +if (!(vp = find_var_ent(name, table, table_count))) return NULL; /* Unknown variable name */ /* Found an existing variable. If in skipping state, the value isn't needed, and we want to avoid processing (such as looking up the host name). */ -if (skipping) +if (flags & ESI_SKIPPING) return US""; val = vp->value; @@ -2047,11 +2011,13 @@ switch (vp->type) return domain ? domain + 1 : US""; case vtype_msgheaders: - return find_header(NULL, newsize, exists_only ? FH_EXISTS_ONLY : 0, NULL); + return find_header(NULL, newsize, + flags & ESI_EXISTS_ONLY ? FH_EXISTS_ONLY : 0, NULL); case vtype_msgheaders_raw: return find_header(NULL, newsize, - exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW, NULL); + flags & ESI_EXISTS_ONLY ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW, + NULL); case vtype_msgbody: /* Pointer to msgbody string */ case vtype_msgbody_end: /* Ditto, the end of the msg */ @@ -2118,15 +2084,15 @@ switch (vp->type) case vtype_reply: /* Get reply address */ s = find_header(US"reply-to:", newsize, - exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW, - headers_charset); + flags & ESI_EXISTS_ONLY ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW, + headers_charset); if (s) Uskip_whitespace(&s); if (!s || !*s) { *newsize = 0; /* For the *s==0 case */ s = find_header(US"from:", newsize, - exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW, - headers_charset); + flags & ESI_EXISTS_ONLY ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW, + headers_charset); } if (s) { @@ -2166,9 +2132,29 @@ switch (vp->type) #ifndef DISABLE_DKIM case vtype_dkim: - return dkim_exim_expand_query((int)(long)val); + { + misc_module_info * mi = misc_mod_findonly(US"dkim"); + typedef uschar * (*fn_t)(int); + return mi + ? (((fn_t *) mi->functions)[DKIM_EXPAND_QUERY]) ((int)(long)val) + : US""; + } #endif + case vtype_module: + { + uschar * errstr; + misc_module_info * mi = misc_mod_find(val, &errstr); + if (mi) + { + table = mi->variables; + table_count = mi->variables_count; + goto sublist; + } + log_write(0, LOG_MAIN|LOG_PANIC, + "failed to find %s module for %s: %s", US val, name, errstr); + return US""; + } } return NULL; /* Unknown variable. Silences static checkers. */ @@ -2181,7 +2167,8 @@ void modify_variable(uschar *name, void * value) { var_entry * vp; -if ((vp = find_var_ent(name))) vp->value = value; +if ((vp = find_var_ent(name, var_table, nelem(var_table)))) + vp->value = value; return; /* Unknown variable name, fail silently */ } @@ -2479,6 +2466,7 @@ if (!name[0]) "but found \"%.16s\"", s); return -1; } +DEBUG(D_expand) debug_printf_indent("cond: %s\n", name); if (opname) *opname = string_copy(name); @@ -2631,19 +2619,18 @@ Returns: a pointer to the first character after the condition, or static const uschar * eval_condition(const uschar * s, BOOL * resetok, BOOL * yield) { -BOOL testfor = TRUE; -BOOL tempcond, combined_cond; +BOOL testfor = TRUE, tempcond, combined_cond; BOOL * subcondptr; -BOOL sub2_honour_dollar = TRUE; -BOOL is_forany, is_json, is_jsons; +BOOL sub2_honour_dollar = TRUE, is_forany, is_json, is_jsons; int rc, cond_type; int_eximarith_t num[2]; struct stat statbuf; uschar * opname; uschar name[256]; -const uschar * sub[10]; +const uschar * sub[10], * next; unsigned sub_textonly = 0; +expand_level++; for (;;) if (Uskip_whitespace(&s) == '!') { testfor = !testfor; s++; } else break; @@ -2659,7 +2646,7 @@ switch(cond_type = identify_operator(&s, &opname)) if (*s != ':') { expand_string_message = US"\":\" expected after \"def\""; - return NULL; + goto failout; } s = read_name(name, sizeof(name), s+1, US"_"); @@ -2686,18 +2673,19 @@ switch(cond_type = identify_operator(&s, &opname)) else { - if (!(t = find_variable(name, TRUE, yield == NULL, NULL))) + if (!(t = find_variable(name, + yield ? ESI_EXISTS_ONLY : ESI_EXISTS_ONLY | ESI_SKIPPING, NULL))) { expand_string_message = name[0] ? string_sprintf("unknown variable \"%s\" after \"def:\"", name) : US"variable name omitted after \"def:\""; check_variable_error_message(name); - return NULL; + goto failout; } if (yield) *yield = (t[0] != 0) == testfor; } - return s; + next = s; goto out; } @@ -2705,14 +2693,14 @@ switch(cond_type = identify_operator(&s, &opname)) case ECOND_FIRST_DELIVERY: if (yield) *yield = f.deliver_firsttime == testfor; - return s; + next = s; goto out; /* queue_running tests for any process started by a queue runner */ case ECOND_QUEUE_RUNNING: if (yield) *yield = (queue_run_pid != (pid_t)0) == testfor; - return s; + next = s; goto out; /* exists: tests for file existence @@ -2741,13 +2729,13 @@ switch(cond_type = identify_operator(&s, &opname)) 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 (!sub[0]) goto failout; if (textonly) sub_textonly |= BIT(0); } /* {-for-text-editors */ if (*s++ != '}') goto COND_FAILED_CURLY_END; - if (!yield) return s; /* No need to run the test if skipping */ + if (!yield) { next = s; goto out; } /* No need to run the test if skipping */ switch(cond_type) { @@ -2755,7 +2743,7 @@ switch(cond_type = identify_operator(&s, &opname)) if ((expand_forbid & RDO_EXISTS) != 0) { expand_string_message = US"File existence tests are not permitted"; - return NULL; + goto failout; } *yield = (Ustat(sub[0], &statbuf) == 0) == testfor; break; @@ -2763,39 +2751,64 @@ switch(cond_type = identify_operator(&s, &opname)) case ECOND_ISIP: case ECOND_ISIP4: case ECOND_ISIP6: - rc = string_is_ip_address(sub[0], NULL); - *yield = ((cond_type == ECOND_ISIP)? (rc != 0) : - (cond_type == ECOND_ISIP4)? (rc == 4) : (rc == 6)) == testfor; + { + const uschar *errp; + const uschar **errpp; + DEBUG(D_expand) errpp = &errp; else errpp = 0; + if (0 == (rc = string_is_ip_addressX(sub[0], NULL, errpp))) + DEBUG(D_expand) debug_printf("failed: %s\n", errp); + + *yield = ( cond_type == ECOND_ISIP ? rc != 0 : + cond_type == ECOND_ISIP4 ? rc == 4 : rc == 6) == testfor; + } + break; /* Various authentication tests - all optionally compiled */ case ECOND_PAM: - #ifdef SUPPORT_PAM - rc = auth_call_pam(sub[0], &expand_string_message); - goto END_AUTH; - #else - goto COND_FAILED_NOT_COMPILED; - #endif /* SUPPORT_PAM */ +#ifdef SUPPORT_PAM + { + const misc_module_info * mi = misc_mod_find(US"pam", NULL); + typedef int (*fn_t)(const uschar *, uschar **); + if (!mi) + goto COND_FAILED_NOT_COMPILED; + rc = (((fn_t *) mi->functions)[PAM_AUTH_CALL]) + (sub[0], &expand_string_message); + goto END_AUTH; + } +#else + goto COND_FAILED_NOT_COMPILED; +#endif /* SUPPORT_PAM */ case ECOND_RADIUS: - #ifdef RADIUS_CONFIG_FILE - rc = auth_call_radius(sub[0], &expand_string_message); - goto END_AUTH; - #else - goto COND_FAILED_NOT_COMPILED; - #endif /* RADIUS_CONFIG_FILE */ +#ifdef RADIUS_CONFIG_FILE + { + const misc_module_info * mi = misc_mod_find(US"radius", NULL); + typedef int (*fn_t)(const uschar *, uschar **); + if (!mi) + goto COND_FAILED_NOT_COMPILED; + rc = (((fn_t *) mi->functions)[RADIUS_AUTH_CALL]) + (sub[0], &expand_string_message); + goto END_AUTH; + } +#else + goto COND_FAILED_NOT_COMPILED; +#endif /* RADIUS_CONFIG_FILE */ case ECOND_LDAPAUTH: #ifdef LOOKUP_LDAP { - /* Just to keep the interface the same */ - BOOL do_cache; - int old_pool = store_pool; - store_pool = POOL_SEARCH; - rc = eldapauth_find((void *)(-1), NULL, sub[0], Ustrlen(sub[0]), NULL, - &expand_string_message, &do_cache); - store_pool = old_pool; + int expand_setup = -1; + const lookup_info * li = search_findtype(US"ldapauth", 8); + void * handle; + + if (li && (handle = search_open(NULL, li, 0, NULL, NULL))) + rc = search_find(handle, NULL, sub[0], + -1, NULL, 0, 0, &expand_setup, NULL) + ? OK : f.search_find_defer ? DEFER : FAIL; + else + { expand_string_message = search_error_message; rc = FAIL; } } goto END_AUTH; #else @@ -2813,11 +2826,11 @@ switch(cond_type = identify_operator(&s, &opname)) #if defined(SUPPORT_PAM) || defined(RADIUS_CONFIG_FILE) || \ defined(LOOKUP_LDAP) || defined(CYRUS_PWCHECK_SOCKET) END_AUTH: - if (rc == ERROR || rc == DEFER) return NULL; + if (rc == ERROR || rc == DEFER) goto failout; *yield = (rc == OK) == testfor; #endif } - return s; + next = s; goto out; /* call ACL (in a conditional context). Accept true, deny false. @@ -2846,7 +2859,7 @@ switch(cond_type = identify_operator(&s, &opname)) case 1: expand_string_message = US"too few arguments or bracketing " "error for acl"; case 2: - case 3: return NULL; + case 3: goto failout; } if (yield) @@ -2870,10 +2883,10 @@ switch(cond_type = identify_operator(&s, &opname)) default: expand_string_message = string_sprintf("%s from acl \"%s\"", rc_names[rc], sub[0]); - return NULL; + goto failout; } } - return s; + next = s; goto out; } @@ -2898,17 +2911,17 @@ switch(cond_type = identify_operator(&s, &opname)) case 1: expand_string_message = US"too few arguments or bracketing " "error for saslauthd"; case 2: - case 3: return NULL; + case 3: goto failout; } if (!sub[2]) sub[3] = NULL; /* realm if no service */ if (yield) { int rc = auth_call_saslauthd(sub[0], sub[1], sub[2], sub[3], &expand_string_message); - if (rc == ERROR || rc == DEFER) return NULL; + if (rc == ERROR || rc == DEFER) goto failout; *yield = (rc == OK) == testfor; } - return s; + next = s; goto out; } #endif /* CYRUS_SASLAUTHD_SOCKET */ @@ -2977,10 +2990,10 @@ switch(cond_type = identify_operator(&s, &opname)) if (i == 0) goto COND_FAILED_CURLY_START; expand_string_message = string_sprintf("missing 2nd string in {} " "after \"%s\"", opname); - return NULL; + goto failout; } if (!(sub[i] = expand_string_internal(s+1, flags, &s, resetok, &textonly))) - return NULL; + goto failout; 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," @@ -3001,13 +3014,13 @@ switch(cond_type = identify_operator(&s, &opname)) else { num[i] = expanded_string_integer(sub[i], FALSE); - if (expand_string_message) return NULL; + if (expand_string_message) goto failout; } } /* Result not required */ - if (!yield) return s; + if (!yield) { next = s; goto out; } /* Do an appropriate comparison */ @@ -3065,7 +3078,7 @@ switch(cond_type = identify_operator(&s, &opname)) sub_textonly & BIT(1) ? MCS_CACHEABLE : MCS_NOFLAGS, &expand_string_message, pcre_gen_cmp_ctx); if (!re) - return NULL; + goto failout; tempcond = regex_match_and_setup(re, sub[0], 0, -1); break; @@ -3086,7 +3099,7 @@ switch(cond_type = identify_operator(&s, &opname)) { expand_string_message = string_sprintf("\"%s\" is not an IP address", sub[0]); - return NULL; + goto failout; } else { @@ -3130,7 +3143,7 @@ switch(cond_type = identify_operator(&s, &opname)) case DEFER: expand_string_message = string_sprintf("unable to complete match " "against \"%s\": %s", sub[1], search_error_message); - return NULL; + goto failout; } break; @@ -3239,7 +3252,7 @@ switch(cond_type = identify_operator(&s, &opname)) { expand_string_message = string_sprintf("unknown encryption mechanism " "in \"%s\"", sub[1]); - return NULL; + goto failout; } switch(which) @@ -3270,7 +3283,7 @@ switch(cond_type = identify_operator(&s, &opname)) { expand_string_message = string_sprintf("crypt error: %s\n", US strerror(errno)); - return NULL; + goto failout; } } break; @@ -3306,7 +3319,7 @@ switch(cond_type = identify_operator(&s, &opname)) } /* Switch for comparison conditions */ *yield = tempcond == testfor; - return s; /* End of comparison conditions */ + next = s; goto out; /* End of comparison conditions */ /* and/or: computes logical and/or of several conditions */ @@ -3327,14 +3340,14 @@ switch(cond_type = identify_operator(&s, &opname)) { expand_string_message = string_sprintf("each subcondition " "inside an \"%s{...}\" condition must be in its own {}", opname); - return NULL; + goto failout; } if (!(s = eval_condition(s+1, resetok, subcondptr))) { expand_string_message = string_sprintf("%s inside \"%s{...}\" condition", expand_string_message, opname); - return NULL; + goto failout; } Uskip_whitespace(&s); @@ -3344,7 +3357,7 @@ switch(cond_type = identify_operator(&s, &opname)) /* {-for-text-editors */ expand_string_message = string_sprintf("missing } at end of condition " "inside \"%s\" group", opname); - return NULL; + goto failout; } if (yield) @@ -3361,7 +3374,7 @@ switch(cond_type = identify_operator(&s, &opname)) } if (yield) *yield = (combined_cond == testfor); - return ++s; + next = ++s; goto out; /* forall/forany: iterates a condition with different values */ @@ -3386,7 +3399,7 @@ switch(cond_type = identify_operator(&s, &opname)) if (!(sub[0] = expand_string_internal(s, ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | (yield ? ESI_NOFLAGS : ESI_SKIPPING), &s, resetok, NULL))) - return NULL; + goto failout; /* {-for-text-editors */ if (*s++ != '}') goto COND_FAILED_CURLY_END; @@ -3403,7 +3416,7 @@ switch(cond_type = identify_operator(&s, &opname)) { expand_string_message = string_sprintf("%s inside \"%s\" condition", expand_string_message, opname); - return NULL; + goto failout; } Uskip_whitespace(&s); @@ -3413,7 +3426,7 @@ switch(cond_type = identify_operator(&s, &opname)) /* {-for-text-editors */ expand_string_message = string_sprintf("missing } at end of condition " "inside \"%s\"", opname); - return NULL; + goto failout; } if (yield) *yield = !testfor; @@ -3429,7 +3442,7 @@ switch(cond_type = identify_operator(&s, &opname)) string_sprintf("%s wrapping string result for extract jsons", expand_string_message); iterate_item = save_iterate_item; - return NULL; + goto failout; } DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", opname, iterate_item); @@ -3438,7 +3451,7 @@ switch(cond_type = identify_operator(&s, &opname)) expand_string_message = string_sprintf("%s inside \"%s\" condition", expand_string_message, opname); iterate_item = save_iterate_item; - return NULL; + goto failout; } DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", opname, tempcond? "true":"false"); @@ -3448,7 +3461,7 @@ switch(cond_type = identify_operator(&s, &opname)) } iterate_item = save_iterate_item; - return s; + next = s; goto out; } @@ -3481,7 +3494,7 @@ switch(cond_type = identify_operator(&s, &opname)) ourname); /*FALLTHROUGH*/ case 2: - case 3: return NULL; + case 3: goto failout; } t = sub_arg[0]; Uskip_whitespace(&t); @@ -3522,12 +3535,12 @@ switch(cond_type = identify_operator(&s, &opname)) { expand_string_message = string_sprintf("unrecognised boolean " "value \"%s\"", t); - return NULL; + goto failout; } DEBUG(D_expand) debug_printf_indent("%s: condition evaluated to %s\n", ourname, boolvalue? "true":"false"); if (yield) *yield = (boolvalue == testfor); - return s; + next = s; goto out; } #ifdef SUPPORT_SRS @@ -3548,7 +3561,7 @@ switch(cond_type = identify_operator(&s, &opname)) case 1: expand_string_message = US"too few arguments or bracketing " "error for inbound_srs"; case 2: - case 3: return NULL; + case 3: goto failout; } /* Match the given local_part against the SRS-encoded pattern */ @@ -3633,7 +3646,7 @@ switch(cond_type = identify_operator(&s, &opname)) srs_result: /* pcre2_match_data_free(md); gen ctx needs no free */ if (yield) *yield = (boolvalue == testfor); - return s; + next = s; goto out; } #endif /*SUPPORT_SRS*/ @@ -3642,19 +3655,19 @@ srs_result: default: if (!expand_string_message || !*expand_string_message) expand_string_message = string_sprintf("unknown condition \"%s\"", opname); - return NULL; + goto failout; } /* End switch on condition type */ /* Missing braces at start and end of data */ COND_FAILED_CURLY_START: expand_string_message = string_sprintf("missing { after \"%s\"", opname); -return NULL; +goto failout; COND_FAILED_CURLY_END: expand_string_message = string_sprintf("missing } at end of \"%s\" condition", opname); -return NULL; +goto failout; /* A condition requires code that is not compiled */ @@ -3664,8 +3677,14 @@ return NULL; COND_FAILED_NOT_COMPILED: expand_string_message = string_sprintf("support for \"%s\" not compiled", opname); -return NULL; +goto failout; #endif + +failout: + next = NULL; +out: + expand_level--; + return next; } @@ -4107,7 +4126,7 @@ if (!*error) if (*s != ')') *error = US"expecting closing parenthesis"; else - while (isspace(*++s)); + while (isspace(*++s)) ; else if (*s) *error = US"expecting operator"; *sptr = s; @@ -4489,30 +4508,17 @@ return yield; /************************************************/ static void debug_expansion_interim(const uschar * what, const uschar * value, int nchar, - BOOL skipping) + esi_flags flags) { -DEBUG(D_noutf8) - debug_printf_indent("|"); -else - debug_printf_indent(UTF8_VERT_RIGHT); +debug_printf_indent("%V", "K"); for (int fill = 11 - Ustrlen(what); fill > 0; fill--) - DEBUG(D_noutf8) - debug_printf("-"); - else - debug_printf(UTF8_HORIZ); + debug_printf("%V", "-"); -debug_printf("%s: %.*s\n", what, nchar, value); +debug_printf("%s: %.*W\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"); - } + debug_printf_indent("%V %V(tainted)\n", + flags & ESI_SKIPPING ? "|" : " ", "\\__"); } @@ -4611,17 +4617,10 @@ while (*s) DEBUG(D_expand) { - DEBUG(D_noutf8) - debug_printf_indent("%c%s: %s\n", - first ? '/' : '|', - flags & ESI_SKIPPING ? "---scanning" : "considering", s); - else - debug_printf_indent("%s%s: %s\n", - first ? UTF8_DOWN_RIGHT : UTF8_VERT_RIGHT, - flags & ESI_SKIPPING - ? UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "scanning" - : "considering", - s); + debug_printf_indent("%V%V%s: %W\n", + first ? "/" : "K", + flags & ESI_SKIPPING ? "---" : "", + flags & ESI_SKIPPING ? "scanning" : "considering", s); first = FALSE; } @@ -4644,21 +4643,20 @@ while (*s) for (s = t; *s ; s++) if (*s == '\\' && s[1] == 'N') break; DEBUG(D_expand) - debug_expansion_interim(US"protected", t, (int)(s - t), !!(flags & ESI_SKIPPING)); - yield = string_catn(yield, t, s - t); + debug_expansion_interim(US"protected", t, (int)(s - t), flags); + if (!(flags & ESI_SKIPPING)) + yield = string_catn(yield, t, s - t); if (*s) s += 2; } else { uschar ch[1]; DEBUG(D_expand) - DEBUG(D_noutf8) - debug_printf_indent("|backslashed: '\\%c'\n", s[1]); - else - debug_printf_indent(UTF8_VERT_RIGHT "backslashed: '\\%c'\n", s[1]); + debug_printf_indent("%Vbackslashed: '\\%c'\n", "K", s[1]); ch[0] = string_interpret_escape(&s); + if (!(flags & ESI_SKIPPING)) + yield = string_catn(yield, ch, 1); s++; - yield = string_catn(yield, ch, 1); } continue; } @@ -4675,9 +4673,10 @@ while (*s) for (const uschar * t = s+1; *t && *t != '$' && *t != '}' && *t != '\\'; t++) i++; - DEBUG(D_expand) debug_expansion_interim(US"text", s, i, !!(flags & ESI_SKIPPING)); + DEBUG(D_expand) debug_expansion_interim(US"text", s, i, flags); - yield = string_catn(yield, s, i); + if (!(flags & ESI_SKIPPING)) + yield = string_catn(yield, s, i); s += i; continue; } @@ -4703,15 +4702,16 @@ while (*s) /* If this is the first thing to be expanded, release the pre-allocated buffer. */ - if (!yield) - 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), GET_UNTAINTED); /* alloc _before_ calling find_variable() */ - } + if (!(flags & ESI_SKIPPING)) + if (!yield) + 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), GET_UNTAINTED); /* alloc _before_ calling find_variable() */ + } /* Header */ @@ -4743,7 +4743,7 @@ while (*s) /* Variable */ - else if (!(value = find_variable(name, FALSE, !!(flags & ESI_SKIPPING), &newsize))) + else if (!(value = find_variable(name, flags, &newsize))) { expand_string_message = string_sprintf("unknown variable name \"%s\"", name); @@ -4760,16 +4760,17 @@ 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; - yield->size = newsize; - yield->ptr = len; - yield->s = US value; /* known to be in new store i.e. a copy, so deconst safe */ - } - else - yield = string_catn(yield, value, len); + DEBUG(D_expand) debug_expansion_interim(US"value", value, len, flags); + if (!(flags & ESI_SKIPPING)) + if (!yield && newsize != 0) + { + yield = g; + yield->size = newsize; + yield->ptr = len; + yield->s = US value; /* known to be in new store i.e. a copy, so deconst safe */ + } + else + yield = string_catn(yield, value, len); continue; } @@ -4780,8 +4781,9 @@ while (*s) 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]); + DEBUG(D_expand) debug_expansion_interim(US"value", expand_nstring[n], expand_nlength[n], flags); + if (!(flags & ESI_SKIPPING)) + yield = string_catn(yield, expand_nstring[n], expand_nlength[n]); } continue; } @@ -4808,8 +4810,9 @@ while (*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]); + DEBUG(D_expand) debug_expansion_interim(US"value", expand_nstring[n], expand_nlength[n], flags); + if (!(flags & ESI_SKIPPING)) + yield = string_catn(yield, expand_nstring[n], expand_nlength[n]); } continue; } @@ -4900,18 +4903,7 @@ while (*s) yield = authres_local(yield, sub_arg[0]); yield = authres_iprev(yield); yield = authres_smtpauth(yield); -#ifdef SUPPORT_SPF - yield = authres_spf(yield); -#endif -#ifndef DISABLE_DKIM - yield = authres_dkim(yield); -#endif -#ifdef SUPPORT_DMARC - yield = authres_dmarc(yield); -#endif -#ifdef EXPERIMENTAL_ARC - yield = authres_arc(yield); -#endif + yield = misc_mod_authres(yield); break; } @@ -4934,9 +4926,9 @@ while (*s) DEBUG(D_expand) { - debug_expansion_interim(US"condition", s, (int)(next_s - s), !!(flags & ESI_SKIPPING)); + debug_expansion_interim(US"condition", s, (int)(next_s - s), flags); debug_expansion_interim(US"result", - cond ? US"true" : US"false", cond ? 4 : 5, !!(flags & ESI_SKIPPING)); + cond ? US"true" : US"false", cond ? 4 : 5, flags); } s = next_s; @@ -5012,9 +5004,9 @@ while (*s) case EITEM_LOOKUP: { - int stype, partial, affixlen, starflags; - int expand_setup = 0; - int nameptr = 0; + int expand_setup = 0, nameptr = 0; + int partial, affixlen, starflags; + const lookup_info * li; uschar * key, * filename; const uschar * affix, * opts; uschar * save_lookup_value = lookup_value; @@ -5067,8 +5059,8 @@ while (*s) /* Now check for the individual search type and any partial or default options. Only those types that are actually in the binary are valid. */ - if ((stype = search_findtype_partial(name, &partial, &affix, &affixlen, - &starflags, &opts)) < 0) + if (!(li = search_findtype_partial(name, &partial, &affix, &affixlen, + &starflags, &opts))) { expand_string_message = search_error_message; goto EXPAND_FAILED; @@ -5077,7 +5069,7 @@ while (*s) /* Check that a key was provided for those lookup types that need it, and was not supplied for those that use the query style. */ - if (!mac_islookup(stype, lookup_querystyle|lookup_absfilequery)) + if (!mac_islookup(li, lookup_querystyle|lookup_absfilequery)) { if (!key) { @@ -5120,7 +5112,7 @@ while (*s) file types, the query (i.e. "key") starts with a file name. */ if (!key) - key = search_args(stype, name, filename, &filename, opts); + key = search_args(li, name, filename, &filename, opts); /* If skipping, don't do the next bit - just lookup_value == NULL, as if the entry was not found. Note that there is no search_close() function. @@ -5139,7 +5131,7 @@ while (*s) lookup_value = NULL; else { - void * handle = search_open(filename, stype, 0, NULL, NULL); + void * handle = search_open(filename, li, 0, NULL, NULL); if (!handle) { expand_string_message = search_error_message; @@ -5200,6 +5192,8 @@ while (*s) { uschar * sub_arg[EXIM_PERL_MAX_ARGS + 2]; gstring * new_yield; + const misc_module_info * mi; + uschar * errstr; if (expand_forbid & RDO_PERL) { @@ -5207,6 +5201,13 @@ while (*s) goto EXPAND_FAILED; } + if (!(mi = misc_mod_find(US"perl", &errstr))) + { + expand_string_message = + string_sprintf("failed to locate perl module: %s", errstr); + goto EXPAND_FAILED; + } + switch(read_subs(sub_arg, EXIM_PERL_MAX_ARGS + 1, 1, &s, flags, TRUE, name, &resetok, NULL)) { @@ -5221,6 +5222,8 @@ while (*s) if (!opt_perl_started) { uschar * initerror; + typedef uschar * (*fn_t)(uschar *); + if (!opt_perl_startup) { expand_string_message = US"A setting of perl_startup is needed when " @@ -5228,7 +5231,8 @@ while (*s) goto EXPAND_FAILED; } DEBUG(D_any) debug_printf("Starting Perl interpreter\n"); - if ((initerror = init_perl(opt_perl_startup))) + initerror = (((fn_t *) mi->functions)[PERL_STARTUP]) (opt_perl_startup); + if (initerror) { expand_string_message = string_sprintf("error in perl_startup code: %s\n", initerror); @@ -5240,8 +5244,12 @@ while (*s) /* Call the function */ sub_arg[EXIM_PERL_MAX_ARGS + 1] = NULL; - new_yield = call_perl_cat(yield, &expand_string_message, - sub_arg[0], sub_arg + 1); + { + typedef gstring * (*fn_t)(gstring *, uschar **, uschar *, uschar **); + new_yield = (((fn_t *) mi->functions)[PERL_CAT]) + (yield, &expand_string_message, + sub_arg[0], sub_arg + 1); + } /* NULL yield indicates failure; if the message pointer has been set to NULL, the yield was undef, indicating a forced failure. Otherwise the @@ -5520,12 +5528,18 @@ while (*s) if (!(flags & ESI_SKIPPING)) { - int stype = search_findtype(US"readsock", 8); + const lookup_info * li = search_findtype(US"readsock", 8); gstring * g = NULL; void * handle; int expand_setup = -1; uschar * s; + if (!li) + { + expand_string_message = search_error_message; + goto EXPAND_FAILED; + } + /* If the reqstr is empty, flag that and set a dummy */ if (!sub_arg[1][0]) @@ -5544,8 +5558,7 @@ while (*s) /* First option has no tag and is timeout */ if ((item = string_nextinlist(&list, &sep, NULL, 0))) - g = string_append_listele(g, ',', - string_sprintf("timeout=%s", item)); + g = string_append_listele_fmt(g, ',', TRUE, "timeout=%s", item); /* The rest of the options from the expansion */ while ((item = string_nextinlist(&list, &sep, NULL, 0))) @@ -5556,14 +5569,13 @@ while (*s) options is the readsock expansion. */ if (sub_arg[3] && *sub_arg[3]) - g = string_append_listele(g, ',', - string_sprintf("eol=%s", - string_printing2(sub_arg[3], SP_TAB|SP_SPACE))); + g = string_append_listele_fmt(g, ',', TRUE, + "eol=%s", string_printing2(sub_arg[3], SP_TAB|SP_SPACE)); } /* Gat a (possibly cached) handle for the connection */ - if (!(handle = search_open(sub_arg[0], stype, 0, NULL, NULL))) + if (!(handle = search_open(sub_arg[0], li, 0, NULL, NULL))) { if (*expand_string_message) goto EXPAND_FAILED; expand_string_message = search_error_message; @@ -7125,7 +7137,7 @@ while (*s) gstring_release_unused(h); s = string_from_gstring(h); } - g = string_cat(g, s); + if (s) g = string_cat(g, s); } /* Assume that if the original local_part had quotes @@ -7155,7 +7167,7 @@ while (*s) if (yield && (expansion_start > 0 || *s)) debug_expansion_interim(US"item-res", yield->s + expansion_start, yield->ptr - expansion_start, - !!(flags & ESI_SKIPPING)); + flags); continue; NOT_ITEM: ; @@ -7211,7 +7223,8 @@ NOT_ITEM: ; string_sprintf("missing '}' closing cert arg of %s", name); goto EXPAND_FAILED_CURLY; } - if ((vp = find_var_ent(sub)) && vp->type == vtype_cert) + if ( (vp = find_var_ent(sub, var_table, nelem(var_table))) + && vp->type == vtype_cert) { s = s1+1; break; @@ -7334,19 +7347,17 @@ NOT_ITEM: ; case EOP_LC: { - int count = 0; - uschar *t = sub - 1; - while (*(++t) != 0) { *t = tolower(*t); count++; } - yield = string_catn(yield, sub, count); + uschar * t = sub - 1; + while (*++t) *t = tolower(*t); + yield = string_catn(yield, sub, t-sub); break; } case EOP_UC: { - int count = 0; - uschar *t = sub - 1; - while (*(++t) != 0) { *t = toupper(*t); count++; } - yield = string_catn(yield, sub, count); + uschar * t = sub - 1; + while (*++t) *t = toupper(*t); + yield = string_catn(yield, sub, t-sub); break; } @@ -7782,26 +7793,25 @@ NOT_ITEM: ; } else yield = string_cat(yield, sub); - break; } /* quote_lookuptype does lookup-specific quoting */ else { - int n; + const lookup_info * li; uschar * opt = Ustrchr(arg, '_'); if (opt) *opt++ = 0; - if ((n = search_findtype(arg, Ustrlen(arg))) < 0) + if (!(li = search_findtype(arg, Ustrlen(arg)))) { expand_string_message = search_error_message; goto EXPAND_FAILED; } - if (lookup_list[n]->quote) - sub = (lookup_list[n]->quote)(sub, opt, (unsigned)n); + if (li->quote) + sub = (li->quote)(sub, opt, li->acq_num); else if (opt) sub = NULL; @@ -7814,553 +7824,546 @@ NOT_ITEM: ; } yield = string_cat(yield, sub); - break; } + break; - /* rx quote sticks in \ before any non-alphameric character so that - the insertion works in a regular expression. */ + /* rx quote sticks in \ before any non-alphameric character so that + the insertion works in a regular expression. */ - case EOP_RXQUOTE: + case EOP_RXQUOTE: + { + uschar *t = sub - 1; + while (*(++t) != 0) { - uschar *t = sub - 1; - while (*(++t) != 0) - { - if (!isalnum(*t)) - yield = string_catn(yield, US"\\", 1); - yield = string_catn(yield, t, 1); - } - break; + if (!isalnum(*t)) + yield = string_catn(yield, US"\\", 1); + yield = string_catn(yield, t, 1); } + break; + } - /* RFC 2047 encodes, assuming headers_charset (default ISO 8859-1) as - prescribed by the RFC, if there are characters that need to be encoded */ + /* RFC 2047 encodes, assuming headers_charset (default ISO 8859-1) as + prescribed by the RFC, if there are characters that need to be encoded */ - case EOP_RFC2047: - yield = string_cat(yield, - parse_quote_2047(sub, Ustrlen(sub), headers_charset, - FALSE)); - break; + case EOP_RFC2047: + yield = string_cat(yield, + parse_quote_2047(sub, Ustrlen(sub), headers_charset, + FALSE)); + break; - /* RFC 2047 decode */ + /* RFC 2047 decode */ - case EOP_RFC2047D: + case EOP_RFC2047D: + { + int len; + uschar *error; + uschar *decoded = rfc2047_decode(sub, check_rfc2047_length, + headers_charset, '?', &len, &error); + if (error) { - int len; - uschar *error; - uschar *decoded = rfc2047_decode(sub, check_rfc2047_length, - headers_charset, '?', &len, &error); - if (error) - { - expand_string_message = error; - goto EXPAND_FAILED; - } - yield = string_catn(yield, decoded, len); - break; + expand_string_message = error; + goto EXPAND_FAILED; } + yield = string_catn(yield, decoded, len); + break; + } - /* from_utf8 converts UTF-8 to 8859-1, turning non-existent chars into - underscores */ + /* from_utf8 converts UTF-8 to 8859-1, turning non-existent chars into + underscores */ - case EOP_FROM_UTF8: + case EOP_FROM_UTF8: + { + uschar * buff = store_get(4, sub); + while (*sub) { - uschar * buff = store_get(4, sub); - while (*sub) - { - int c; - GETUTF8INC(c, sub); - if (c > 255) c = '_'; - buff[0] = c; - yield = string_catn(yield, buff, 1); - } - break; + int c; + GETUTF8INC(c, sub); + if (c > 255) c = '_'; + buff[0] = c; + yield = string_catn(yield, buff, 1); } + break; + } + + /* replace illegal UTF-8 sequences by replacement character */ + + #define UTF8_REPLACEMENT_CHAR US"?" - /* replace illegal UTF-8 sequences by replacement character */ + case EOP_UTF8CLEAN: + { + int seq_len = 0, index = 0, bytes_left = 0, complete; + u_long codepoint = (u_long)-1; + uschar seq_buff[4]; /* accumulate utf-8 here */ - #define UTF8_REPLACEMENT_CHAR US"?" + /* Manually track tainting, as we deal in individual chars below */ - case EOP_UTF8CLEAN: + if (!yield) + yield = string_get_tainted(Ustrlen(sub), sub); + else if (!yield->s || !yield->ptr) { - int seq_len = 0, index = 0, bytes_left = 0, complete; - u_long codepoint = (u_long)-1; - uschar seq_buff[4]; /* accumulate utf-8 here */ + 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 */ - /* Manually track tainting, as we deal in individual chars below */ + while (*sub) + { + complete = 0; + uschar c = *sub++; - if (!yield) - yield = string_get_tainted(Ustrlen(sub), sub); - else if (!yield->s || !yield->ptr) + if (bytes_left) { - yield->s = store_get(yield->size = Ustrlen(sub), sub); - gstring_reset(yield); + if ((c & 0xc0) != 0x80) + /* wrong continuation byte; invalidate all bytes */ + complete = 1; /* error */ + else + { + codepoint = (codepoint << 6) | (c & 0x3f); + seq_buff[index++] = c; + 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); + index = 0; + } + } } - else if (is_incompatible(yield->s, sub)) - gstring_rebuffer(yield, sub); - - /* Check the UTF-8, byte-by-byte */ - - while (*sub) + else /* no bytes left: new sequence */ { - complete = 0; - uschar c = *sub++; - - if (bytes_left) + if (!(c & 0x80)) /* 1-byte sequence, US-ASCII, keep it */ { - if ((c & 0xc0) != 0x80) - /* wrong continuation byte; invalidate all bytes */ - complete = 1; /* error */ + yield = string_catn(yield, &c, 1); + continue; + } + if ((c & 0xe0) == 0xc0) /* 2-byte sequence */ + if (c == 0xc0 || c == 0xc1) /* 0xc0 and 0xc1 are illegal */ + complete = -1; else { - codepoint = (codepoint << 6) | (c & 0x3f); - seq_buff[index++] = c; - 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); - index = 0; - } + bytes_left = 1; + codepoint = c & 0x1f; } + else if ((c & 0xf0) == 0xe0) /* 3-byte sequence */ + { + bytes_left = 2; + codepoint = c & 0x0f; } - else /* no bytes left: new sequence */ + else if ((c & 0xf8) == 0xf0) /* 4-byte sequence */ { - 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 */ - complete = -1; - else - { - bytes_left = 1; - codepoint = c & 0x1f; - } - else if ((c & 0xf0) == 0xe0) /* 3-byte sequence */ - { - bytes_left = 2; - codepoint = c & 0x0f; - } - else if ((c & 0xf8) == 0xf0) /* 4-byte sequence */ - { - bytes_left = 3; - codepoint = c & 0x07; - } - else /* invalid or too long (RFC3629 allows only 4 bytes) */ - complete = -1; + bytes_left = 3; + codepoint = c & 0x07; + } + else /* invalid or too long (RFC3629 allows only 4 bytes) */ + complete = -1; - seq_buff[index++] = c; - seq_len = bytes_left + 1; - } /* if(bytes_left) */ + seq_buff[index++] = c; + seq_len = bytes_left + 1; + } /* if(bytes_left) */ - if (complete != 0) - { - bytes_left = index = 0; - yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1); - } - if ((complete == 1) && ((c & 0x80) == 0)) - /* ASCII character follows incomplete sequence */ - yield = string_catn(yield, &c, 1); + if (complete != 0) + { + bytes_left = index = 0; + yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1); } - /* If given a sequence truncated mid-character, we also want to report ? - Eg, ${length_1:フィル} is one byte, not one character, so we expect - ${utf8clean:${length_1:フィル}} to yield '?' */ + if ((complete == 1) && ((c & 0x80) == 0)) + /* ASCII character follows incomplete sequence */ + yield = string_catn(yield, &c, 1); + } + /* If given a sequence truncated mid-character, we also want to report ? + Eg, ${length_1:フィル} is one byte, not one character, so we expect + ${utf8clean:${length_1:フィル}} to yield '?' */ - if (bytes_left != 0) - yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1); + if (bytes_left != 0) + yield = string_catn(yield, UTF8_REPLACEMENT_CHAR, 1); - break; - } + break; + } #ifdef SUPPORT_I18N - case EOP_UTF8_DOMAIN_TO_ALABEL: + case EOP_UTF8_DOMAIN_TO_ALABEL: + { + uschar * error = NULL; + uschar * s = string_domain_utf8_to_alabel(sub, &error); + if (error) { - uschar * error = NULL; - uschar * s = string_domain_utf8_to_alabel(sub, &error); - if (error) - { - expand_string_message = string_sprintf( - "error converting utf8 (%s) to alabel: %s", - string_printing(sub), error); - goto EXPAND_FAILED; - } - yield = string_cat(yield, s); - break; + expand_string_message = string_sprintf( + "error converting utf8 (%s) to alabel: %s", + string_printing(sub), error); + goto EXPAND_FAILED; } + yield = string_cat(yield, s); + break; + } - case EOP_UTF8_DOMAIN_FROM_ALABEL: + case EOP_UTF8_DOMAIN_FROM_ALABEL: + { + uschar * error = NULL; + uschar * s = string_domain_alabel_to_utf8(sub, &error); + if (error) { - uschar * error = NULL; - uschar * s = string_domain_alabel_to_utf8(sub, &error); - if (error) - { - expand_string_message = string_sprintf( - "error converting alabel (%s) to utf8: %s", - string_printing(sub), error); - goto EXPAND_FAILED; - } - yield = string_cat(yield, s); - break; + expand_string_message = string_sprintf( + "error converting alabel (%s) to utf8: %s", + string_printing(sub), error); + goto EXPAND_FAILED; } + yield = string_cat(yield, s); + break; + } - case EOP_UTF8_LOCALPART_TO_ALABEL: + case EOP_UTF8_LOCALPART_TO_ALABEL: + { + uschar * error = NULL; + uschar * s = string_localpart_utf8_to_alabel(sub, &error); + if (error) { - uschar * error = NULL; - uschar * s = string_localpart_utf8_to_alabel(sub, &error); - if (error) - { - expand_string_message = string_sprintf( - "error converting utf8 (%s) to alabel: %s", - string_printing(sub), error); - goto EXPAND_FAILED; - } - yield = string_cat(yield, s); - DEBUG(D_expand) debug_printf_indent("yield: '%Y'\n", yield); - break; + expand_string_message = string_sprintf( + "error converting utf8 (%s) to alabel: %s", + string_printing(sub), error); + goto EXPAND_FAILED; } + yield = string_cat(yield, s); + DEBUG(D_expand) debug_printf_indent("yield: '%Y'\n", yield); + break; + } - case EOP_UTF8_LOCALPART_FROM_ALABEL: + case EOP_UTF8_LOCALPART_FROM_ALABEL: + { + uschar * error = NULL; + uschar * s = string_localpart_alabel_to_utf8(sub, &error); + if (error) { - uschar * error = NULL; - uschar * s = string_localpart_alabel_to_utf8(sub, &error); - if (error) - { - expand_string_message = string_sprintf( - "error converting alabel (%s) to utf8: %s", - string_printing(sub), error); - goto EXPAND_FAILED; - } - yield = string_cat(yield, s); - break; + expand_string_message = string_sprintf( + "error converting alabel (%s) to utf8: %s", + string_printing(sub), error); + goto EXPAND_FAILED; } + yield = string_cat(yield, s); + break; + } #endif /* EXPERIMENTAL_INTERNATIONAL */ - /* escape turns all non-printing characters into escape sequences. */ + /* escape turns all non-printing characters into escape sequences. */ - case EOP_ESCAPE: - { - const uschar * t = string_printing(sub); - yield = string_cat(yield, t); - break; - } + case EOP_ESCAPE: + { + const uschar * t = string_printing(sub); + yield = string_cat(yield, t); + break; + } - case EOP_ESCAPE8BIT: - { - uschar c; + case EOP_ESCAPE8BIT: + { + uschar c; - for (const uschar * s = sub; (c = *s); s++) - yield = c < 127 && c != '\\' - ? string_catn(yield, s, 1) - : string_fmt_append(yield, "\\%03o", c); - break; - } + for (const uschar * s = sub; (c = *s); s++) + yield = c < 127 && c != '\\' + ? string_catn(yield, s, 1) + : string_fmt_append(yield, "\\%03o", c); + break; + } - /* Handle numeric expression evaluation */ + /* Handle numeric expression evaluation */ - case EOP_EVAL: - case EOP_EVAL10: + case EOP_EVAL: + case EOP_EVAL10: + { + uschar *save_sub = sub; + uschar *error = NULL; + int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE); + if (error) { - uschar *save_sub = sub; - uschar *error = NULL; - int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE); - if (error) - { - expand_string_message = string_sprintf("error in expression " - "evaluation: %s (after processing \"%.*s\")", error, - (int)(sub-save_sub), save_sub); - goto EXPAND_FAILED; - } - yield = string_fmt_append(yield, PR_EXIM_ARITH, n); - break; + expand_string_message = string_sprintf("error in expression " + "evaluation: %s (after processing \"%.*s\")", error, + (int)(sub-save_sub), save_sub); + goto EXPAND_FAILED; } + yield = string_fmt_append(yield, PR_EXIM_ARITH, n); + break; + } - /* Handle time period formatting */ + /* Handle time period formatting */ - case EOP_TIME_EVAL: + case EOP_TIME_EVAL: + { + int n = readconf_readtime(sub, 0, FALSE); + if (n < 0) { - int n = readconf_readtime(sub, 0, FALSE); - if (n < 0) - { - expand_string_message = string_sprintf("string \"%s\" is not an " - "Exim time interval in \"%s\" operator", sub, name); - goto EXPAND_FAILED; - } - yield = string_fmt_append(yield, "%d", n); - break; + expand_string_message = string_sprintf("string \"%s\" is not an " + "Exim time interval in \"%s\" operator", sub, name); + goto EXPAND_FAILED; } + yield = string_fmt_append(yield, "%d", n); + break; + } - case EOP_TIME_INTERVAL: + case EOP_TIME_INTERVAL: + { + int n; + uschar *t = read_number(&n, sub); + if (*t != 0) /* Not A Number*/ { - int n; - uschar *t = read_number(&n, sub); - if (*t != 0) /* Not A Number*/ - { - expand_string_message = string_sprintf("string \"%s\" is not a " - "positive number in \"%s\" operator", sub, name); - goto EXPAND_FAILED; - } - t = readconf_printtime(n); - yield = string_cat(yield, t); - break; + expand_string_message = string_sprintf("string \"%s\" is not a " + "positive number in \"%s\" operator", sub, name); + goto EXPAND_FAILED; } + t = readconf_printtime(n); + yield = string_cat(yield, t); + break; + } - /* Convert string to base64 encoding */ + /* Convert string to base64 encoding */ - case EOP_STR2B64: - case EOP_BASE64: - { + case EOP_STR2B64: + case EOP_BASE64: + { #ifndef DISABLE_TLS - uschar * s = vp && *(void **)vp->value - ? tls_cert_der_b64(*(void **)vp->value) - : b64encode(CUS sub, Ustrlen(sub)); + uschar * s = vp && *(void **)vp->value + ? tls_cert_der_b64(*(void **)vp->value) + : b64encode(CUS sub, Ustrlen(sub)); #else - uschar * s = b64encode(CUS sub, Ustrlen(sub)); + uschar * s = b64encode(CUS sub, Ustrlen(sub)); #endif - yield = string_cat(yield, s); - break; - } + yield = string_cat(yield, s); + break; + } - case EOP_BASE64D: + case EOP_BASE64D: + { + uschar * s; + int len = b64decode(sub, &s, sub); + if (len < 0) { - uschar * s; - int len = b64decode(sub, &s, sub); - if (len < 0) - { - expand_string_message = string_sprintf("string \"%s\" is not " - "well-formed for \"%s\" operator", sub, name); - goto EXPAND_FAILED; - } - yield = string_cat(yield, s); - break; + expand_string_message = string_sprintf("string \"%s\" is not " + "well-formed for \"%s\" operator", sub, name); + goto EXPAND_FAILED; } + yield = string_cat(yield, s); + break; + } - /* strlen returns the length of the string */ + /* strlen returns the length of the string */ - case EOP_STRLEN: - yield = string_fmt_append(yield, "%d", Ustrlen(sub)); - break; + case EOP_STRLEN: + yield = string_fmt_append(yield, "%d", Ustrlen(sub)); + break; + + /* length_n or l_n takes just the first n characters or the whole string, + whichever is the shorter; + + substr_m_n, and s_m_n take n characters from offset m; negative m take + from the end; l_n is synonymous with s_0_n. If n is omitted in substr it + takes the rest, either to the right or to the left. + + hash_n or h_n makes a hash of length n from the string, yielding n + characters from the set a-z; hash_n_m makes a hash of length n, but + uses m characters from the set a-zA-Z0-9. + + nhash_n returns a single number between 0 and n-1 (in text form), while + nhash_n_m returns a div/mod hash as two numbers "a/b". The first lies + between 0 and n-1 and the second between 0 and m-1. */ + + case EOP_LENGTH: + case EOP_L: + case EOP_SUBSTR: + case EOP_S: + case EOP_HASH: + case EOP_H: + case EOP_NHASH: + case EOP_NH: + { + int sign = 1; + int value1 = 0; + int value2 = -1; + int *pn; + int len; + uschar *ret; + + if (!arg) + { + expand_string_message = string_sprintf("missing values after %s", + name); + goto EXPAND_FAILED; + } + + /* "length" has only one argument, effectively being synonymous with + substr_0_n. */ - /* length_n or l_n takes just the first n characters or the whole string, - whichever is the shorter; - - substr_m_n, and s_m_n take n characters from offset m; negative m take - from the end; l_n is synonymous with s_0_n. If n is omitted in substr it - takes the rest, either to the right or to the left. - - hash_n or h_n makes a hash of length n from the string, yielding n - characters from the set a-z; hash_n_m makes a hash of length n, but - uses m characters from the set a-zA-Z0-9. - - nhash_n returns a single number between 0 and n-1 (in text form), while - nhash_n_m returns a div/mod hash as two numbers "a/b". The first lies - between 0 and n-1 and the second between 0 and m-1. */ - - case EOP_LENGTH: - case EOP_L: - case EOP_SUBSTR: - case EOP_S: - case EOP_HASH: - case EOP_H: - case EOP_NHASH: - case EOP_NH: + if (c == EOP_LENGTH || c == EOP_L) { - int sign = 1; - int value1 = 0; - int value2 = -1; - int *pn; - int len; - uschar *ret; + pn = &value2; + value2 = 0; + } - if (!arg) - { - expand_string_message = string_sprintf("missing values after %s", - name); - goto EXPAND_FAILED; - } + /* The others have one or two arguments; for "substr" the first may be + negative. The second being negative means "not supplied". */ + + else + { + pn = &value1; + if (name[0] == 's' && *arg == '-') { sign = -1; arg++; } + } - /* "length" has only one argument, effectively being synonymous with - substr_0_n. */ + /* Read up to two numbers, separated by underscores */ - if (c == EOP_LENGTH || c == EOP_L) + ret = arg; + while (*arg != 0) + { + if (arg != ret && *arg == '_' && pn == &value1) { pn = &value2; value2 = 0; + if (arg[1] != 0) arg++; } - - /* The others have one or two arguments; for "substr" the first may be - negative. The second being negative means "not supplied". */ - - else + else if (!isdigit(*arg)) { - pn = &value1; - if (name[0] == 's' && *arg == '-') { sign = -1; arg++; } + expand_string_message = + string_sprintf("non-digit after underscore in \"%s\"", name); + goto EXPAND_FAILED; } + else *pn = (*pn)*10 + *arg++ - '0'; + } + value1 *= sign; - /* Read up to two numbers, separated by underscores */ + /* Perform the required operation */ - ret = arg; - while (*arg != 0) - { - if (arg != ret && *arg == '_' && pn == &value1) - { - pn = &value2; - value2 = 0; - if (arg[1] != 0) arg++; - } - else if (!isdigit(*arg)) - { - expand_string_message = - string_sprintf("non-digit after underscore in \"%s\"", name); - goto EXPAND_FAILED; - } - else *pn = (*pn)*10 + *arg++ - '0'; - } - value1 *= sign; + ret = c == EOP_HASH || c == EOP_H + ? compute_hash(sub, value1, value2, &len) + : c == EOP_NHASH || c == EOP_NH + ? compute_nhash(sub, value1, value2, &len) + : extract_substr(sub, value1, value2, &len); + if (!ret) goto EXPAND_FAILED; - /* Perform the required operation */ - - ret = c == EOP_HASH || c == EOP_H - ? compute_hash(sub, value1, value2, &len) - : c == EOP_NHASH || c == EOP_NH - ? compute_nhash(sub, value1, value2, &len) - : extract_substr(sub, value1, value2, &len); - if (!ret) goto EXPAND_FAILED; + yield = string_catn(yield, ret, len); + break; + } - yield = string_catn(yield, ret, len); - break; - } + /* Stat a path */ - /* Stat a path */ + case EOP_STAT: + { + uschar smode[12]; + uschar **modetable[3]; + mode_t mode; + struct stat st; - case EOP_STAT: + if (expand_forbid & RDO_EXISTS) { - uschar smode[12]; - uschar **modetable[3]; - mode_t mode; - struct stat st; + expand_string_message = US"Use of the stat() expansion is not permitted"; + goto EXPAND_FAILED; + } - if (expand_forbid & RDO_EXISTS) - { - expand_string_message = US"Use of the stat() expansion is not permitted"; - goto EXPAND_FAILED; - } + if (stat(CS sub, &st) < 0) + { + expand_string_message = string_sprintf("stat(%s) failed: %s", + sub, strerror(errno)); + goto EXPAND_FAILED; + } + mode = st.st_mode; + switch (mode & S_IFMT) + { + case S_IFIFO: smode[0] = 'p'; break; + case S_IFCHR: smode[0] = 'c'; break; + case S_IFDIR: smode[0] = 'd'; break; + case S_IFBLK: smode[0] = 'b'; break; + case S_IFREG: smode[0] = '-'; break; + default: smode[0] = '?'; break; + } - if (stat(CS sub, &st) < 0) - { - expand_string_message = string_sprintf("stat(%s) failed: %s", - sub, strerror(errno)); - goto EXPAND_FAILED; - } - mode = st.st_mode; - switch (mode & S_IFMT) - { - case S_IFIFO: smode[0] = 'p'; break; - case S_IFCHR: smode[0] = 'c'; break; - case S_IFDIR: smode[0] = 'd'; break; - case S_IFBLK: smode[0] = 'b'; break; - case S_IFREG: smode[0] = '-'; break; - default: smode[0] = '?'; break; - } + modetable[0] = ((mode & 01000) == 0)? mtable_normal : mtable_sticky; + modetable[1] = ((mode & 02000) == 0)? mtable_normal : mtable_setid; + modetable[2] = ((mode & 04000) == 0)? mtable_normal : mtable_setid; - modetable[0] = ((mode & 01000) == 0)? mtable_normal : mtable_sticky; - modetable[1] = ((mode & 02000) == 0)? mtable_normal : mtable_setid; - modetable[2] = ((mode & 04000) == 0)? mtable_normal : mtable_setid; + for (int i = 0; i < 3; i++) + { + memcpy(CS(smode + 7 - i*3), CS(modetable[i][mode & 7]), 3); + mode >>= 3; + } - for (int i = 0; i < 3; i++) - { - memcpy(CS(smode + 7 - i*3), CS(modetable[i][mode & 7]), 3); - mode >>= 3; - } + smode[10] = 0; + yield = string_fmt_append(yield, + "mode=%04lo smode=%s inode=%ld device=%ld links=%ld " + "uid=%ld gid=%ld size=" OFF_T_FMT " atime=%ld mtime=%ld ctime=%ld", + (long)(st.st_mode & 077777), smode, (long)st.st_ino, + (long)st.st_dev, (long)st.st_nlink, (long)st.st_uid, + (long)st.st_gid, st.st_size, (long)st.st_atime, + (long)st.st_mtime, (long)st.st_ctime); + break; + } - smode[10] = 0; - yield = string_fmt_append(yield, - "mode=%04lo smode=%s inode=%ld device=%ld links=%ld " - "uid=%ld gid=%ld size=" OFF_T_FMT " atime=%ld mtime=%ld ctime=%ld", - (long)(st.st_mode & 077777), smode, (long)st.st_ino, - (long)st.st_dev, (long)st.st_nlink, (long)st.st_uid, - (long)st.st_gid, st.st_size, (long)st.st_atime, - (long)st.st_mtime, (long)st.st_ctime); - break; - } + /* vaguely random number less than N */ - /* vaguely random number less than N */ + case EOP_RANDINT: + { + int_eximarith_t max = expanded_string_integer(sub, TRUE); - case EOP_RANDINT: - { - int_eximarith_t max = expanded_string_integer(sub, TRUE); + if (expand_string_message) + goto EXPAND_FAILED; + yield = string_fmt_append(yield, "%d", vaguely_random_number((int)max)); + break; + } - if (expand_string_message) - goto EXPAND_FAILED; - yield = string_fmt_append(yield, "%d", vaguely_random_number((int)max)); - break; - } + /* Reverse IP, including IPv6 to dotted-nibble */ - /* Reverse IP, including IPv6 to dotted-nibble */ + case EOP_REVERSE_IP: + { + int family, maskptr; + uschar reversed[128]; - case EOP_REVERSE_IP: + family = string_is_ip_address(sub, &maskptr); + if (family == 0) { - int family, maskptr; - uschar reversed[128]; - - family = string_is_ip_address(sub, &maskptr); - if (family == 0) - { - expand_string_message = string_sprintf( - "reverse_ip() not given an IP address [%s]", sub); - goto EXPAND_FAILED; - } - invert_address(reversed, sub); - yield = string_cat(yield, reversed); - break; + expand_string_message = string_sprintf( + "reverse_ip() not given an IP address [%s]", sub); + goto EXPAND_FAILED; } + invert_address(reversed, sub); + yield = string_cat(yield, reversed); + break; + } - /* Unknown operator */ + case EOP_XTEXTD: + { + uschar * s; + int len = xtextdecode(sub, &s); + yield = string_catn(yield, s, len); + break; + } - default: - expand_string_message = - string_sprintf("unknown expansion operator \"%s\"", name); - goto EXPAND_FAILED; - } /* EOP_* switch */ + /* Unknown operator */ + default: + expand_string_message = + string_sprintf("unknown expansion operator \"%s\"", name); + goto EXPAND_FAILED; + } /* EOP_* switch */ - DEBUG(D_expand) + DEBUG(D_expand) { 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) - { - debug_printf_indent("|-----op-res: %.*s\n", i, s); - if (tainted) - { - debug_printf_indent("%s \\__", flags & ESI_SKIPPING ? "| " : " "); - debug_print_taint(res); - } - } - else + debug_printf_indent("%Vop-res: %.*s\n", "K-----", i, s); + if (tainted) { - debug_printf_indent(UTF8_VERT_RIGHT - UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ - "op-res: %.*s\n", i, s); - if (tainted) - { - debug_printf_indent("%s", - flags & ESI_SKIPPING - ? UTF8_VERT " " : " " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ); - debug_print_taint(res); - } + debug_printf_indent("%V %V", + flags & ESI_SKIPPING ? "|" : " ", + "\\__"); + debug_print_taint(res); } } continue; @@ -8391,7 +8394,7 @@ NOT_ITEM: ; reset_point = store_mark(); g = store_get(sizeof(gstring), GET_UNTAINTED); /* alloc _before_ calling find_variable() */ } - if (!(value = find_variable(name, FALSE, !!(flags & ESI_SKIPPING), &newsize))) + if (!(value = find_variable(name, flags, &newsize))) { expand_string_message = string_sprintf("unknown variable in \"${%s}\"", name); @@ -8452,39 +8455,25 @@ left != NULL, return a pointer to the terminator. */ DEBUG(D_expand) { 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"); - } + debug_printf_indent("%Vexpanded: %.*W\n", + "K---", + (int)(s - string), string); + debug_printf_indent("%Vresult: ", + flags & ESI_SKIPPING ? "K-----" : "\\_____"); + if (*res || !(flags & ESI_SKIPPING)) + debug_printf("%W\n", res); else + debug_printf(" %Vskipped%V\n", "<", ">"); + if (tainted) { - 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"); + debug_printf_indent("%V %V", + flags & ESI_SKIPPING ? "|" : " ", + "\\__" + ); + debug_print_taint(res); } + if (flags & ESI_SKIPPING) + debug_printf_indent("%Vskipping: result is not used\n", "\\___"); } if (textonly_p) *textonly_p = textonly; expand_level--; @@ -8510,25 +8499,11 @@ EXPAND_FAILED: if (left) *left = s; DEBUG(D_expand) { - DEBUG(D_noutf8) - { - debug_printf_indent("|failed to expand: %s\n", string); - debug_printf_indent("%serror message: %s\n", - f.expand_string_forcedfail ? "|---" : "\\___", expand_string_message); - if (f.expand_string_forcedfail) - debug_printf_indent("\\failure was forced\n"); - } - else - { - debug_printf_indent(UTF8_VERT_RIGHT "failed to expand: %s\n", - string); - debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ - "error message: %s\n", - f.expand_string_forcedfail ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT, - expand_string_message); - if (f.expand_string_forcedfail) - debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n"); - } + debug_printf_indent("%Vfailed to expand: %s\n", "K", string); + debug_printf_indent("%Verror message: %s\n", + f.expand_string_forcedfail ? "K---" : "\\___", expand_string_message); + if (f.expand_string_forcedfail) + debug_printf_indent("%Vfailure was forced\n", "\\"); } if (resetok_p && !resetok) *resetok_p = FALSE; expand_level--; @@ -8551,13 +8526,12 @@ Returns: the expanded string, or NULL if expansion failed; if failure was const uschar * expand_string_2(const uschar * string, BOOL * textonly_p) { +f.expand_string_forcedfail = f.search_find_defer = malformed_header = FALSE; if (Ustrpbrk(string, "$\\") != NULL) { int old_pool = store_pool; uschar * s; - f.search_find_defer = FALSE; - malformed_header = FALSE; store_pool = POOL_MAIN; s = expand_string_internal(string, ESI_HONOR_DOLLAR, NULL, NULL, textonly_p); store_pool = old_pool; @@ -8731,15 +8705,17 @@ Returns: OK value placed in rvalue */ int -exp_bool(address_item *addr, - uschar *mtype, uschar *mname, unsigned dbg_opt, - uschar *oname, BOOL bvalue, - uschar *svalue, BOOL *rvalue) +exp_bool(address_item * addr, + const uschar * mtype, const uschar * mname, unsigned dbg_opt, + uschar * oname, BOOL bvalue, + const uschar * svalue, BOOL * rvalue) { -uschar *expanded; +const uschar * expanded; + +DEBUG(D_expand) debug_printf("try option %s\n", oname); if (!svalue) { *rvalue = bvalue; return OK; } -if (!(expanded = expand_string(svalue))) +if (!(expanded = expand_cstring(svalue))) { if (f.expand_string_forcedfail) {