X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fsrc%2Fexpand.c;h=325b05178890d13bc544204155860d908cea090c;hb=aa26e1378803587c24924ad0055318959d597802;hp=8d47f3223638d5e4bb566d42f4449601729deed0;hpb=e58c13cc0600761b3b39b8ae67566acef3a03dcb;p=user%2Fhenk%2Fcode%2Fexim.git diff --git a/src/src/expand.c b/src/src/expand.c index 8d47f3223..325b05178 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/expand.c,v 1.84 2007/02/26 12:25:10 ph10 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2007 */ +/* Copyright (c) University of Cambridge 1995 - 2012 */ /* See the file NOTICE for conditions of use and distribution. */ @@ -15,7 +13,7 @@ /* Recursively called function */ -static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL); +static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL, BOOL, BOOL *); #ifdef STAND_ALONE #ifndef SUPPORT_CRYPTEQ @@ -104,6 +102,7 @@ bcrypt ({CRYPT}$2a$). alphabetical order. */ static uschar *item_table[] = { + US"acl", US"dlfunc", US"extract", US"filter", @@ -111,6 +110,7 @@ static uschar *item_table[] = { US"hmac", US"if", US"length", + US"listextract", US"lookup", US"map", US"nhash", @@ -126,6 +126,7 @@ static uschar *item_table[] = { US"tr" }; enum { + EITEM_ACL, EITEM_DLFUNC, EITEM_EXTRACT, EITEM_FILTER, @@ -133,6 +134,7 @@ enum { EITEM_HMAC, EITEM_IF, EITEM_LENGTH, + EITEM_LISTEXTRACT, EITEM_LOOKUP, EITEM_MAP, EITEM_NHASH, @@ -156,6 +158,7 @@ static uschar *op_table_underscore[] = { US"from_utf8", US"local_part", US"quote_local_part", + US"reverse_ip", US"time_eval", US"time_interval"}; @@ -163,6 +166,7 @@ enum { EOP_FROM_UTF8, EOP_LOCAL_PART, EOP_QUOTE_LOCAL_PART, + EOP_REVERSE_IP, EOP_TIME_EVAL, EOP_TIME_INTERVAL }; @@ -179,14 +183,18 @@ static uschar *op_table_main[] = { US"h", US"hash", US"hex2b64", + US"hexquote", US"l", US"lc", US"length", + US"listcount", + US"listnamed", US"mask", US"md5", US"nh", US"nhash", US"quote", + US"randint", US"rfc2047", US"rfc2047d", US"rxquote", @@ -211,14 +219,18 @@ enum { EOP_H, EOP_HASH, EOP_HEX2B64, + EOP_HEXQUOTE, EOP_L, EOP_LC, EOP_LENGTH, + EOP_LISTCOUNT, + EOP_LISTNAMED, EOP_MASK, EOP_MD5, EOP_NH, EOP_NHASH, EOP_QUOTE, + EOP_RANDINT, EOP_RFC2047, EOP_RFC2047D, EOP_RXQUOTE, @@ -241,7 +253,10 @@ static uschar *cond_table[] = { US"==", /* Backward compatibility */ US">", US">=", + US"acl", US"and", + US"bool", + US"bool_lax", US"crypteq", US"def", US"eq", @@ -254,6 +269,8 @@ static uschar *cond_table[] = { US"gei", US"gt", US"gti", + US"inlist", + US"inlisti", US"isip", US"isip4", US"isip6", @@ -282,7 +299,10 @@ enum { ECOND_NUM_EE, ECOND_NUM_G, ECOND_NUM_GE, + ECOND_ACL, ECOND_AND, + ECOND_BOOL, + ECOND_BOOL_LAX, ECOND_CRYPTEQ, ECOND_DEF, ECOND_STR_EQ, @@ -295,6 +315,8 @@ enum { ECOND_STR_GEI, ECOND_STR_GT, ECOND_STR_GTI, + ECOND_INLIST, + ECOND_INLISTI, ECOND_ISIP, ECOND_ISIP4, ECOND_ISIP6, @@ -320,9 +342,9 @@ enum { /* Type for main variable table */ typedef struct { - char *name; - int type; - void *value; + const char *name; + int type; + void *value; } var_entry; /* Type for entries pointing to address/length pairs. Not currently @@ -341,6 +363,7 @@ enum { 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 */ @@ -348,11 +371,10 @@ enum { vtype_msgheaders_raw, /* the message's headers, unprocessed */ vtype_localpart, /* extract local part from string */ vtype_domain, /* extract domain from string */ - vtype_recipients, /* extract recipients from recipients list */ - /* (available only in system filters, ACLs, and */ - /* local_scan()) */ + 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 */ @@ -364,23 +386,39 @@ enum { 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 */ -#ifdef EXPERIMENTAL_DOMAINKEYS - ,vtype_dk_verify /* Serve request out of DomainKeys verification structure */ -#endif + #ifndef DISABLE_DKIM + ,vtype_dkim /* Lookup of value in DKIM signature */ + #endif }; +static uschar * fn_recipients(void); + /* This table must be kept in alphabetical order. */ static var_entry var_table[] = { /* WARNING: Do not invent variables whose names start acl_c or acl_m because they will be confused with user-creatable ACL variables. */ + { "acl_arg1", vtype_stringptr, &acl_arg[0] }, + { "acl_arg2", vtype_stringptr, &acl_arg[1] }, + { "acl_arg3", vtype_stringptr, &acl_arg[2] }, + { "acl_arg4", vtype_stringptr, &acl_arg[3] }, + { "acl_arg5", vtype_stringptr, &acl_arg[4] }, + { "acl_arg6", vtype_stringptr, &acl_arg[5] }, + { "acl_arg7", vtype_stringptr, &acl_arg[6] }, + { "acl_arg8", vtype_stringptr, &acl_arg[7] }, + { "acl_arg9", vtype_stringptr, &acl_arg[8] }, + { "acl_narg", vtype_int, &acl_narg }, { "acl_verify_message", vtype_stringptr, &acl_verify_message }, { "address_data", vtype_stringptr, &deliver_address_data }, { "address_file", vtype_stringptr, &address_file }, { "address_pipe", vtype_stringptr, &address_pipe }, + { "authenticated_fail_id",vtype_stringptr, &authenticated_fail_id }, { "authenticated_id", vtype_stringptr, &authenticated_id }, { "authenticated_sender",vtype_stringptr, &authenticated_sender }, { "authentication_failed",vtype_int, &authentication_failed }, +#ifdef WITH_CONTENT_SCAN + { "av_failed", vtype_int, &av_failed }, +#endif #ifdef EXPERIMENTAL_BRIGHTMAIL { "bmi_alt_location", vtype_stringptr, &bmi_alt_location }, { "bmi_base64_tracker_verdict", vtype_stringptr, &bmi_base64_tracker_verdict }, @@ -396,24 +434,44 @@ static var_entry var_table[] = { { "compile_date", vtype_stringptr, &version_date }, { "compile_number", vtype_stringptr, &version_cnumber }, { "csa_status", vtype_stringptr, &csa_status }, +#ifdef EXPERIMENTAL_DCC + { "dcc_header", vtype_stringptr, &dcc_header }, + { "dcc_result", vtype_stringptr, &dcc_result }, +#endif #ifdef WITH_OLD_DEMIME { "demime_errorlevel", vtype_int, &demime_errorlevel }, { "demime_reason", vtype_stringptr, &demime_reason }, #endif -#ifdef EXPERIMENTAL_DOMAINKEYS - { "dk_domain", vtype_stringptr, &dk_signing_domain }, - { "dk_is_signed", vtype_dk_verify, NULL }, - { "dk_result", vtype_dk_verify, NULL }, - { "dk_selector", vtype_stringptr, &dk_signing_selector }, - { "dk_sender", vtype_dk_verify, NULL }, - { "dk_sender_domain", vtype_dk_verify, NULL }, - { "dk_sender_local_part",vtype_dk_verify, NULL }, - { "dk_sender_source", vtype_dk_verify, NULL }, - { "dk_signsall", vtype_dk_verify, NULL }, - { "dk_status", vtype_dk_verify, NULL }, - { "dk_testing", vtype_dk_verify, NULL }, +#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_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_dkim, (void *)DKIM_VERIFY_REASON }, + { "dkim_verify_status", vtype_dkim, (void *)DKIM_VERIFY_STATUS}, +#endif +#ifdef EXPERIMENTAL_DMARC + { "dmarc_ar_header", vtype_stringptr, &dmarc_ar_header }, + { "dmarc_status", vtype_stringptr, &dmarc_status }, + { "dmarc_status_text", vtype_stringptr, &dmarc_status_text }, + { "dmarc_used_domain", vtype_stringptr, &dmarc_used_domain }, #endif { "dnslist_domain", vtype_stringptr, &dnslist_domain }, + { "dnslist_matched", vtype_stringptr, &dnslist_matched }, { "dnslist_text", vtype_stringptr, &dnslist_text }, { "dnslist_value", vtype_stringptr, &dnslist_value }, { "domain", vtype_stringptr, &deliver_domain }, @@ -424,6 +482,7 @@ static var_entry var_table[] = { #ifdef WITH_OLD_DEMIME { "found_extension", vtype_stringptr, &found_extension }, #endif + { "headers_added", vtype_string_func, &fn_hdrs_added }, { "home", vtype_stringptr, &deliver_home }, { "host", vtype_stringptr, &deliver_host }, { "host_address", vtype_stringptr, &deliver_host_address }, @@ -452,6 +511,7 @@ static var_entry var_table[] = { #ifdef WITH_CONTENT_SCAN { "malware_name", vtype_stringptr, &malware_name }, #endif + { "max_received_linelength", vtype_int, &max_received_linelength }, { "message_age", vtype_int, &message_age }, { "message_body", vtype_msgbody, &message_body }, { "message_body_end", vtype_msgbody_end, &message_body_end }, @@ -498,6 +558,11 @@ static var_entry var_table[] = { { "parent_local_part", vtype_stringptr, &deliver_localpart_parent }, { "pid", vtype_pid, NULL }, { "primary_hostname", vtype_stringptr, &primary_hostname }, +#ifdef EXPERIMENTAL_PROXY + { "proxy_host", vtype_stringptr, &proxy_host }, + { "proxy_port", vtype_int, &proxy_port }, + { "proxy_session", vtype_bool, &proxy_session }, +#endif { "prvscheck_address", vtype_stringptr, &prvscheck_address }, { "prvscheck_keynum", vtype_stringptr, &prvscheck_keynum }, { "prvscheck_result", vtype_stringptr, &prvscheck_result }, @@ -514,7 +579,7 @@ static var_entry var_table[] = { { "received_time", vtype_int, &received_time }, { "recipient_data", vtype_stringptr, &recipient_data }, { "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure }, - { "recipients", vtype_recipients, NULL }, + { "recipients", vtype_string_func, &fn_recipients }, { "recipients_count", vtype_int, &recipients_count }, #ifdef WITH_CONTENT_SCAN { "regex_match_string", vtype_stringptr, ®ex_match_string }, @@ -522,6 +587,7 @@ static var_entry var_table[] = { { "reply_address", vtype_reply, NULL }, { "return_path", vtype_stringptr, &return_path }, { "return_size_limit", vtype_int, &bounce_return_size_limit }, + { "router_name", vtype_stringptr, &router_name }, { "runrc", vtype_int, &runrc }, { "self_hostname", vtype_stringptr, &self_hostname }, { "sender_address", vtype_stringptr, &sender_address }, @@ -533,6 +599,7 @@ static var_entry var_table[] = { { "sender_helo_name", vtype_stringptr, &sender_helo_name }, { "sender_host_address", vtype_stringptr, &sender_host_address }, { "sender_host_authenticated",vtype_stringptr, &sender_host_authenticated }, + { "sender_host_dnssec", vtype_bool, &sender_host_dnssec }, { "sender_host_name", vtype_host_lookup, NULL }, { "sender_host_port", vtype_int, &sender_host_port }, { "sender_ident", vtype_stringptr, &sender_ident }, @@ -547,6 +614,7 @@ static var_entry var_table[] = { { "smtp_command", vtype_stringptr, &smtp_cmd_buffer }, { "smtp_command_argument", vtype_stringptr, &smtp_cmd_argument }, { "smtp_count_at_connection_start", vtype_int, &smtp_accept_count }, + { "smtp_notquit_reason", vtype_stringptr, &smtp_notquit_reason }, { "sn0", vtype_filter_int, &filter_sn[0] }, { "sn1", vtype_filter_int, &filter_sn[1] }, { "sn2", vtype_filter_int, &filter_sn[2] }, @@ -564,6 +632,7 @@ static var_entry var_table[] = { { "spam_score_int", vtype_stringptr, &spam_score_int }, #endif #ifdef EXPERIMENTAL_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 }, @@ -581,16 +650,51 @@ static var_entry var_table[] = { { "srs_status", vtype_stringptr, &srs_status }, #endif { "thisaddress", vtype_stringptr, &filter_thisaddress }, - { "tls_certificate_verified", vtype_int, &tls_certificate_verified }, - { "tls_cipher", vtype_stringptr, &tls_cipher }, - { "tls_peerdn", vtype_stringptr, &tls_peerdn }, + + /* The non-(in,out) variables are now deprecated */ + { "tls_bits", vtype_int, &tls_in.bits }, + { "tls_certificate_verified", vtype_int, &tls_in.certificate_verified }, + { "tls_cipher", vtype_stringptr, &tls_in.cipher }, + + { "tls_in_bits", vtype_int, &tls_in.bits }, + { "tls_in_certificate_verified", vtype_int, &tls_in.certificate_verified }, + { "tls_in_cipher", vtype_stringptr, &tls_in.cipher }, + { "tls_in_peerdn", vtype_stringptr, &tls_in.peerdn }, +#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) + { "tls_in_sni", vtype_stringptr, &tls_in.sni }, +#endif + { "tls_out_bits", vtype_int, &tls_out.bits }, + { "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified }, + { "tls_out_cipher", vtype_stringptr, &tls_out.cipher }, + { "tls_out_peerdn", vtype_stringptr, &tls_out.peerdn }, +#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) + { "tls_out_sni", vtype_stringptr, &tls_out.sni }, +#endif + + { "tls_peerdn", vtype_stringptr, &tls_in.peerdn }, /* mind the alphabetical order! */ +#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS) + { "tls_sni", vtype_stringptr, &tls_in.sni }, /* mind the alphabetical order! */ +#endif + { "tod_bsdinbox", vtype_todbsdin, NULL }, { "tod_epoch", vtype_tode, NULL }, + { "tod_epoch_l", vtype_todel, NULL }, { "tod_full", vtype_todf, NULL }, { "tod_log", vtype_todl, NULL }, { "tod_logfile", vtype_todlf, NULL }, { "tod_zone", vtype_todzone, NULL }, { "tod_zulu", vtype_todzulu, NULL }, +#ifdef EXPERIMENTAL_TPDA + { "tpda_defer_errno", vtype_int, &tpda_defer_errno }, + { "tpda_defer_errstr", vtype_stringptr, &tpda_defer_errstr }, + { "tpda_delivery_confirmation", vtype_stringptr, &tpda_delivery_confirmation }, + { "tpda_delivery_domain", vtype_stringptr, &tpda_delivery_domain }, + { "tpda_delivery_fqdn", vtype_stringptr, &tpda_delivery_fqdn }, + { "tpda_delivery_ip", vtype_stringptr, &tpda_delivery_ip }, + { "tpda_delivery_local_part",vtype_stringptr,&tpda_delivery_local_part }, + { "tpda_delivery_port", vtype_int, &tpda_delivery_port }, +#endif + { "transport_name", vtype_stringptr, &transport_name }, { "value", vtype_stringptr, &lookup_value }, { "version_number", vtype_stringptr, &version_string }, { "warn_message_delay", vtype_stringptr, &warnmsg_delay }, @@ -607,9 +711,9 @@ static BOOL malformed_header; /* For textual hashes */ -static char *hashcodes = "abcdefghijklmnopqrtsuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "0123456789"; +static const char *hashcodes = "abcdefghijklmnopqrtsuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; enum { HMAC_MD5, HMAC_SHA1 }; @@ -708,8 +812,13 @@ return -1; /* This function is called to expand a string, and test the result for a "true" or "false" value. Failure of the expansion yields FALSE; logged unless it was a -forced fail or lookup defer. All store used by the function can be released on -exit. +forced fail or lookup defer. + +We used to release all store used, but this is not not safe due +to ${dlfunc } and ${acl }. In any case expand_string_internal() +is reasonably careful to release what it can. + +The actual false-value tests should be replicated for ECOND_BOOL_LAX. Arguments: condition the condition string @@ -723,7 +832,6 @@ BOOL expand_check_condition(uschar *condition, uschar *m1, uschar *m2) { int rc; -void *reset_point = store_get(0); uschar *ss = expand_string(condition); if (ss == NULL) { @@ -734,12 +842,86 @@ if (ss == NULL) } rc = ss[0] != 0 && Ustrcmp(ss, "0") != 0 && strcmpic(ss, US"no") != 0 && strcmpic(ss, US"false") != 0; -store_reset(reset_point); return rc; } + +/************************************************* +* Pseudo-random number generation * +*************************************************/ + +/* Pseudo-random number generation. The result is not "expected" to be +cryptographically strong but not so weak that someone will shoot themselves +in the foot using it as a nonce in some email header scheme or whatever +weirdness they'll twist this into. The result should ideally handle fork(). + +However, if we're stuck unable to provide this, then we'll fall back to +appallingly bad randomness. + +If SUPPORT_TLS is defined then this will not be used except as an emergency +fallback. + +Arguments: + max range maximum +Returns a random number in range [0, max-1] +*/ + +#ifdef SUPPORT_TLS +# define vaguely_random_number vaguely_random_number_fallback +#endif +int +vaguely_random_number(int max) +{ +#ifdef SUPPORT_TLS +# undef vaguely_random_number +#endif + static pid_t pid = 0; + pid_t p2; +#if defined(HAVE_SRANDOM) && !defined(HAVE_SRANDOMDEV) + struct timeval tv; +#endif + + p2 = getpid(); + if (p2 != pid) + { + if (pid != 0) + { + +#ifdef HAVE_ARC4RANDOM + /* cryptographically strong randomness, common on *BSD platforms, not + so much elsewhere. Alas. */ + arc4random_stir(); +#elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV) +#ifdef HAVE_SRANDOMDEV + /* uses random(4) for seeding */ + srandomdev(); +#else + gettimeofday(&tv, NULL); + srandom(tv.tv_sec | tv.tv_usec | getpid()); +#endif +#else + /* Poor randomness and no seeding here */ +#endif + + } + pid = p2; + } + +#ifdef HAVE_ARC4RANDOM + return arc4random() % max; +#elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV) + return random() % max; +#else + /* This one returns a 16-bit number, definitely not crypto-strong */ + return random_number(max); +#endif +} + + + + /************************************************* * Pick out a name from a string * *************************************************/ @@ -951,6 +1133,22 @@ return fieldtext; } +static uschar * +expand_getlistele (int field, uschar *list) +{ +uschar * tlist= list; +int sep= 0; +uschar dummy; + +if(field<0) +{ + for(field++; string_nextinlist(&tlist, &sep, &dummy, 1); ) field++; + sep= 0; +} +if(field==0) return NULL; +while(--field>0 && (string_nextinlist(&list, &sep, &dummy, 1))) ; +return string_nextinlist(&list, &sep, NULL, 0); +} /************************************************* * Extract a substring from a string * @@ -1294,6 +1492,34 @@ return yield; +/************************************************* +* Return list of recipients * +*************************************************/ +/* 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. */ + +static uschar * +fn_recipients(void) +{ +if (!enable_dollar_recipients) return NULL; else + { + int size = 128; + int ptr = 0; + int i; + uschar * s = store_get(size); + for (i = 0; i < recipients_count; i++) + { + if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2); + s = string_cat(s, &size, &ptr, recipients_list[i].address, + Ustrlen(recipients_list[i].address)); + } + s[ptr] = 0; /* string_cat() leaves room */ + return s; + } +} + + /************************************************* * Find value of a variable * *************************************************/ @@ -1370,51 +1596,6 @@ while (last > first) switch (var_table[middle].type) { -#ifdef EXPERIMENTAL_DOMAINKEYS - - case vtype_dk_verify: - if (dk_verify_block == NULL) return US""; - s = NULL; - if (Ustrcmp(var_table[middle].name, "dk_result") == 0) - s = dk_verify_block->result_string; - if (Ustrcmp(var_table[middle].name, "dk_sender") == 0) - s = dk_verify_block->address; - if (Ustrcmp(var_table[middle].name, "dk_sender_domain") == 0) - s = dk_verify_block->domain; - if (Ustrcmp(var_table[middle].name, "dk_sender_local_part") == 0) - s = dk_verify_block->local_part; - - if (Ustrcmp(var_table[middle].name, "dk_sender_source") == 0) - switch(dk_verify_block->address_source) { - case DK_EXIM_ADDRESS_NONE: s = US"0"; break; - case DK_EXIM_ADDRESS_FROM_FROM: s = US"from"; break; - case DK_EXIM_ADDRESS_FROM_SENDER: s = US"sender"; break; - } - - if (Ustrcmp(var_table[middle].name, "dk_status") == 0) - switch(dk_verify_block->result) { - case DK_EXIM_RESULT_ERR: s = US"error"; break; - case DK_EXIM_RESULT_BAD_FORMAT: s = US"bad format"; break; - case DK_EXIM_RESULT_NO_KEY: s = US"no key"; break; - case DK_EXIM_RESULT_NO_SIGNATURE: s = US"no signature"; break; - case DK_EXIM_RESULT_REVOKED: s = US"revoked"; break; - case DK_EXIM_RESULT_NON_PARTICIPANT: s = US"non-participant"; break; - case DK_EXIM_RESULT_GOOD: s = US"good"; break; - case DK_EXIM_RESULT_BAD: s = US"bad"; break; - } - - if (Ustrcmp(var_table[middle].name, "dk_signsall") == 0) - s = (dk_verify_block->signsall)? US"1" : US"0"; - - if (Ustrcmp(var_table[middle].name, "dk_testing") == 0) - s = (dk_verify_block->testing)? US"1" : US"0"; - - if (Ustrcmp(var_table[middle].name, "dk_is_signed") == 0) - s = (dk_verify_block->is_signed)? US"1" : US"0"; - - return (s == NULL)? US"" : s; -#endif - case vtype_filter_int: if (!filter_running) return NULL; /* Fall through */ @@ -1435,6 +1616,10 @@ while (last > first) sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(var_table[middle].value))); /* uid */ return var_buffer; + case vtype_bool: + sprintf(CS var_buffer, "%s", *(BOOL *)(var_table[middle].value) ? "yes" : "no"); /* bool */ + return var_buffer; + case vtype_stringptr: /* Pointer to string */ s = *((uschar **)(var_table[middle].value)); return (s == NULL)? US"" : s; @@ -1444,7 +1629,7 @@ while (last > first) return var_buffer; case vtype_load_avg: - sprintf(CS var_buffer, "%d", os_getloadavg()); /* load_average */ + sprintf(CS var_buffer, "%d", OS_GETLOADAVG()); /* load_average */ return var_buffer; case vtype_host_lookup: /* Lookup if not done so */ @@ -1459,8 +1644,8 @@ while (last > first) domain = Ustrrchr(s, '@'); if (domain == NULL) return s; if (domain - s > sizeof(var_buffer) - 1) - log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than %d in " - "string expansion", sizeof(var_buffer)); + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than " SIZE_T_FMT + " in string expansion", sizeof(var_buffer)); Ustrncpy(var_buffer, s, domain - s); var_buffer[domain - s] = 0; return var_buffer; @@ -1503,9 +1688,15 @@ while (last > first) if (len > 0) { body[len] = 0; - while (len > 0) + if (message_body_newlines) /* Separate loops for efficiency */ { - if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; + while (len > 0) + { if (body[--len] == 0) body[len] = ' '; } + } + else + { + while (len > 0) + { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; } } } } @@ -1517,6 +1708,9 @@ while (last > first) case vtype_tode: /* Unix epoch time of day */ return tod_stamp(tod_epoch); + case vtype_todel: /* Unix epoch/usec time of day */ + return tod_stamp(tod_epoch_l); + case vtype_todf: /* Full time of day */ return tod_stamp(tod_full); @@ -1530,7 +1724,7 @@ while (last > first) return tod_stamp(tod_zulu); case vtype_todlf: /* Log file datestamp tod */ - return tod_stamp(tod_log_datestamp); + return tod_stamp(tod_log_datestamp_daily); case vtype_reply: /* Get reply address */ s = find_header(US"reply-to:", exists_only, newsize, TRUE, @@ -1551,26 +1745,11 @@ while (last > first) } return (s == NULL)? US"" : s; - /* 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. */ - - case vtype_recipients: - if (!enable_dollar_recipients) return NULL; else + case vtype_string_func: { - int size = 128; - int ptr = 0; - int i; - s = store_get(size); - for (i = 0; i < recipients_count; i++) - { - if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2); - s = string_cat(s, &size, &ptr, recipients_list[i].address, - Ustrlen(recipients_list[i].address)); - } - s[ptr] = 0; /* string_cat() leaves room */ + uschar * (*fn)() = var_table[middle].value; + return fn(); } - return s; case vtype_pspace: { @@ -1587,6 +1766,12 @@ while (last > first) sprintf(CS var_buffer, "%d", inodes); } return var_buffer; + + #ifndef DISABLE_DKIM + case vtype_dkim: + return dkim_exim_expand_query((int)(long)var_table[middle].value); + #endif + } } @@ -1596,6 +1781,31 @@ return NULL; /* Unknown variable name */ +void +modify_variable(uschar *name, void * value) +{ +int first = 0; +int last = var_table_size; + +while (last > first) + { + int middle = (first + last)/2; + int c = Ustrcmp(name, var_table[middle].name); + + if (c > 0) { first = middle + 1; continue; } + if (c < 0) { last = middle; continue; } + + /* Found an existing variable; change the item it refers to */ + var_table[middle].value = value; + return; + } +return; /* Unknown variable name, fail silently */ +} + + + + + /************************************************* * Read and expand substrings * *************************************************/ @@ -1612,6 +1822,8 @@ Arguments: 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. Returns: 0 OK; string pointer updated 1 curly bracketing error (too few arguments) @@ -1621,7 +1833,7 @@ Returns: 0 OK; string pointer updated static int read_subs(uschar **sub, int n, int m, uschar **sptr, BOOL skipping, - BOOL check_end, uschar *name) + BOOL check_end, uschar *name, BOOL *resetok) { int i; uschar *s = *sptr; @@ -1635,7 +1847,7 @@ for (i = 0; i < n; i++) sub[i] = NULL; break; } - sub[i] = expand_string_internal(s+1, TRUE, &s, skipping); + sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok); if (sub[i] == NULL) return 3; if (*s++ != '}') return 1; while (isspace(*s)) s++; @@ -1685,6 +1897,58 @@ if (Ustrncmp(name, "acl_", 4) == 0) +/* +Load args from sub array to globals, and call acl_check(). +Sub array will be corrupted on return. + +Returns: OK access is granted by an ACCEPT verb + DISCARD access is granted by a DISCARD verb + FAIL access is denied + FAIL_DROP access is denied; drop the connection + DEFER can't tell at the moment + ERROR disaster +*/ +static int +eval_acl(uschar ** sub, int nsub, uschar ** user_msgp) +{ +int i; +uschar *tmp; +int sav_narg = acl_narg; +int ret; +extern int acl_where; + +if(--nsub > sizeof(acl_arg)/sizeof(*acl_arg)) nsub = sizeof(acl_arg)/sizeof(*acl_arg); +for (i = 0; i < nsub && sub[i+1]; i++) + { + tmp = acl_arg[i]; + acl_arg[i] = sub[i+1]; /* place callers args in the globals */ + sub[i+1] = tmp; /* stash the old args using our caller's storage */ + } +acl_narg = i; +while (i < nsub) + { + sub[i+1] = acl_arg[i]; + acl_arg[i++] = NULL; + } + +DEBUG(D_expand) + debug_printf("expanding: acl: %s arg: %s%s\n", + sub[0], + acl_narg>0 ? acl_arg[0] : US"", + acl_narg>1 ? " +more" : ""); + +ret = acl_eval(acl_where, sub[0], user_msgp, &tmp); + +for (i = 0; i < nsub; i++) + acl_arg[i] = sub[i+1]; /* restore old args */ +acl_narg = sav_narg; + +return ret; +} + + + + /************************************************* * Read and evaluate a condition * *************************************************/ @@ -1692,6 +1956,9 @@ if (Ustrncmp(name, "acl_", 4) == 0) /* Arguments: s points to the start of the condition text + resetok points to a BOOL which is written false if it is unsafe to + free memory. Certain condition types (acl) may have side-effect + allocation which must be preserved. yield points to a BOOL to hold the result of the condition test; if NULL, we are just reading through a condition that is part of an "or" combination to check syntax, or in a state @@ -1702,16 +1969,17 @@ Returns: a pointer to the first character after the condition, or */ static uschar * -eval_condition(uschar *s, BOOL *yield) +eval_condition(uschar *s, BOOL *resetok, BOOL *yield) { BOOL testfor = TRUE; BOOL tempcond, combined_cond; BOOL *subcondptr; +BOOL sub2_honour_dollar = TRUE; int i, rc, cond_type, roffset; -int num[2]; +int_eximarith_t num[2]; struct stat statbuf; uschar name[256]; -uschar *sub[4]; +uschar *sub[10]; const pcre *re; const uschar *rerror; @@ -1778,6 +2046,7 @@ switch(cond_type) Ustrncmp(name, "bheader_", 8) == 0) { s = read_header_name(name, 256, s); + /* {-for-text-editors */ if (Ustrchr(name, '}') != NULL) malformed_header = TRUE; if (yield != NULL) *yield = (find_header(name, TRUE, NULL, FALSE, NULL) != NULL) == testfor; @@ -1837,10 +2106,11 @@ switch(cond_type) case ECOND_PWCHECK: while (isspace(*s)) s++; - if (*s != '{') goto COND_FAILED_CURLY_START; + if (*s != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ - sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL); + sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE, resetok); if (sub[0] == NULL) return NULL; + /* {-for-text-editors */ if (*s++ != '}') goto COND_FAILED_CURLY_END; if (yield == NULL) return s; /* No need to run the test if skipping */ @@ -1916,20 +2186,75 @@ switch(cond_type) return s; + /* call ACL (in a conditional context). Accept true, deny false. + Defer is a forced-fail. Anything set by message= goes to $value. + Up to ten parameters are used; we use the braces round the name+args + like the saslauthd condition does, to permit a variable number of args. + See also the expansion-item version EITEM_ACL and the traditional + acl modifier ACLC_ACL. + Since the ACL may allocate new global variables, tell our caller to not + reclaim memory. + */ + + case ECOND_ACL: + /* ${if acl {{name}{arg1}{arg2}...} {yes}{no}} */ + { + uschar *user_msg; + BOOL cond = FALSE; + int size = 0; + int ptr = 0; + + while (isspace(*s)) s++; + if (*s++ != '{') goto COND_FAILED_CURLY_START; /*}*/ + + switch(read_subs(sub, sizeof(sub)/sizeof(*sub), 1, + &s, yield == NULL, TRUE, US"acl", resetok)) + { + case 1: expand_string_message = US"too few arguments or bracketing " + "error for acl"; + case 2: + case 3: return NULL; + } + + *resetok = FALSE; + if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg)) + { + case OK: + cond = TRUE; + case FAIL: + lookup_value = NULL; + if (user_msg) + { + lookup_value = string_cat(NULL, &size, &ptr, user_msg, Ustrlen(user_msg)); + lookup_value[ptr] = '\0'; + } + *yield = cond == testfor; + break; + + case DEFER: + expand_string_forcedfail = TRUE; + default: + expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]); + return NULL; + } + return s; + } + + /* saslauthd: does Cyrus saslauthd authentication. Four parameters are used: - ${if saslauthd {{username}{password}{service}{realm}} {yes}[no}} + ${if saslauthd {{username}{password}{service}{realm}} {yes}{no}} However, the last two are optional. That is why the whole set is enclosed - in their own set or braces. */ + in their own set of braces. */ case ECOND_SASLAUTHD: #ifndef CYRUS_SASLAUTHD_SOCKET goto COND_FAILED_NOT_COMPILED; #else while (isspace(*s)) s++; - if (*s++ != '{') goto COND_FAILED_CURLY_START; - switch(read_subs(sub, 4, 2, &s, yield == NULL, TRUE, US"saslauthd")) + if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ + switch(read_subs(sub, 4, 2, &s, yield == NULL, TRUE, US"saslauthd", resetok)) { case 1: expand_string_message = US"too few arguments or bracketing " "error for saslauthd"; @@ -1952,22 +2277,30 @@ switch(cond_type) /* symbolic operators for numeric and string comparison, and a number of other operators, all requiring two arguments. + crypteq: encrypts plaintext and compares against an encrypted text, + using crypt(), crypt16(), MD5 or SHA-1 + inlist/inlisti: checks if first argument is in the list of the second match: does a regular expression match and sets up the numerical variables if it succeeds match_address: matches in an address list match_domain: matches in a domain list match_ip: matches a host list that is restricted to IP addresses match_local_part: matches in a local part list - crypteq: encrypts plaintext and compares against an encrypted text, - using crypt(), crypt16(), MD5 or SHA-1 */ - case ECOND_MATCH: case ECOND_MATCH_ADDRESS: case ECOND_MATCH_DOMAIN: case ECOND_MATCH_IP: case ECOND_MATCH_LOCAL_PART: +#ifndef EXPAND_LISTMATCH_RHS + sub2_honour_dollar = FALSE; +#endif + /* FALLTHROUGH */ + case ECOND_CRYPTEQ: + case ECOND_INLIST: + case ECOND_INLISTI: + case ECOND_MATCH: case ECOND_NUM_L: /* Numerical comparisons */ case ECOND_NUM_LE: @@ -1989,6 +2322,13 @@ switch(cond_type) for (i = 0; i < 2; i++) { + /* 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; + while (isspace(*s)) s++; if (*s != '{') { @@ -1997,7 +2337,8 @@ switch(cond_type) "after \"%s\"", name); return NULL; } - sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL); + sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL, + honour_dollar, resetok); if (sub[i] == NULL) return NULL; if (*s++ != '}') goto COND_FAILED_CURLY_END; @@ -2031,63 +2372,63 @@ switch(cond_type) { case ECOND_NUM_E: case ECOND_NUM_EE: - *yield = (num[0] == num[1]) == testfor; + tempcond = (num[0] == num[1]); break; case ECOND_NUM_G: - *yield = (num[0] > num[1]) == testfor; + tempcond = (num[0] > num[1]); break; case ECOND_NUM_GE: - *yield = (num[0] >= num[1]) == testfor; + tempcond = (num[0] >= num[1]); break; case ECOND_NUM_L: - *yield = (num[0] < num[1]) == testfor; + tempcond = (num[0] < num[1]); break; case ECOND_NUM_LE: - *yield = (num[0] <= num[1]) == testfor; + tempcond = (num[0] <= num[1]); break; case ECOND_STR_LT: - *yield = (Ustrcmp(sub[0], sub[1]) < 0) == testfor; + tempcond = (Ustrcmp(sub[0], sub[1]) < 0); break; case ECOND_STR_LTI: - *yield = (strcmpic(sub[0], sub[1]) < 0) == testfor; + tempcond = (strcmpic(sub[0], sub[1]) < 0); break; case ECOND_STR_LE: - *yield = (Ustrcmp(sub[0], sub[1]) <= 0) == testfor; + tempcond = (Ustrcmp(sub[0], sub[1]) <= 0); break; case ECOND_STR_LEI: - *yield = (strcmpic(sub[0], sub[1]) <= 0) == testfor; + tempcond = (strcmpic(sub[0], sub[1]) <= 0); break; case ECOND_STR_EQ: - *yield = (Ustrcmp(sub[0], sub[1]) == 0) == testfor; + tempcond = (Ustrcmp(sub[0], sub[1]) == 0); break; case ECOND_STR_EQI: - *yield = (strcmpic(sub[0], sub[1]) == 0) == testfor; + tempcond = (strcmpic(sub[0], sub[1]) == 0); break; case ECOND_STR_GT: - *yield = (Ustrcmp(sub[0], sub[1]) > 0) == testfor; + tempcond = (Ustrcmp(sub[0], sub[1]) > 0); break; case ECOND_STR_GTI: - *yield = (strcmpic(sub[0], sub[1]) > 0) == testfor; + tempcond = (strcmpic(sub[0], sub[1]) > 0); break; case ECOND_STR_GE: - *yield = (Ustrcmp(sub[0], sub[1]) >= 0) == testfor; + tempcond = (Ustrcmp(sub[0], sub[1]) >= 0); break; case ECOND_STR_GEI: - *yield = (strcmpic(sub[0], sub[1]) >= 0) == testfor; + tempcond = (strcmpic(sub[0], sub[1]) >= 0); break; case ECOND_MATCH: /* Regular expression match */ @@ -2099,7 +2440,7 @@ switch(cond_type) "\"%s\": %s at offset %d", sub[1], rerror, roffset); return NULL; } - *yield = regex_match_and_setup(re, sub[0], 0, -1) == testfor; + tempcond = regex_match_and_setup(re, sub[0], 0, -1); break; case ECOND_MATCH_ADDRESS: /* Match in an address list */ @@ -2155,11 +2496,11 @@ switch(cond_type) switch(rc) { case OK: - *yield = testfor; + tempcond = TRUE; break; case FAIL: - *yield = !testfor; + tempcond = FALSE; break; case DEFER: @@ -2173,6 +2514,7 @@ switch(cond_type) /* Various "encrypted" comparisons. If the second string starts with "{" then an encryption type is given. Default to crypt() or crypt16() (build-time choice). */ + /* }-for-text-editors */ case ECOND_CRYPTEQ: #ifndef SUPPORT_CRYPTEQ @@ -2197,7 +2539,7 @@ switch(cond_type) uschar *coded = auth_b64encode((uschar *)digest, 16); DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n" " subject=%s\n crypted=%s\n", coded, sub[1]+5); - *yield = (Ustrcmp(coded, sub[1]+5) == 0) == testfor; + tempcond = (Ustrcmp(coded, sub[1]+5) == 0); } else if (sublen == 32) { @@ -2207,13 +2549,13 @@ switch(cond_type) coded[32] = 0; DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n" " subject=%s\n crypted=%s\n", coded, sub[1]+5); - *yield = (strcmpic(coded, sub[1]+5) == 0) == testfor; + 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); - *yield = !testfor; + tempcond = FALSE; } } @@ -2235,7 +2577,7 @@ switch(cond_type) uschar *coded = auth_b64encode((uschar *)digest, 20); DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n" " subject=%s\n crypted=%s\n", coded, sub[1]+6); - *yield = (Ustrcmp(coded, sub[1]+6) == 0) == testfor; + tempcond = (Ustrcmp(coded, sub[1]+6) == 0); } else if (sublen == 40) { @@ -2245,17 +2587,18 @@ switch(cond_type) coded[40] = 0; DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n" " subject=%s\n crypted=%s\n", coded, sub[1]+6); - *yield = (strcmpic(coded, sub[1]+6) == 0) == testfor; + 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); - *yield = !testfor; + tempcond = FALSE; } } else /* {crypt} or {crypt16} and non-{ at start */ + /* }-for-text-editors */ { int which = 0; uschar *coded; @@ -2270,7 +2613,7 @@ switch(cond_type) sub[1] += 9; which = 2; } - else if (sub[1][0] == '{') + else if (sub[1][0] == '{') /* }-for-text-editors */ { expand_string_message = string_sprintf("unknown encryption mechanism " "in \"%s\"", sub[1]); @@ -2297,13 +2640,37 @@ switch(cond_type) salt), force failure. Otherwise we get false positives: with an empty string the yield of crypt() is an empty string! */ - *yield = (Ustrlen(sub[1]) < 2)? !testfor : - (Ustrcmp(coded, sub[1]) == 0) == testfor; + tempcond = (Ustrlen(sub[1]) < 2)? FALSE : + (Ustrcmp(coded, sub[1]) == 0); } break; #endif /* SUPPORT_CRYPTEQ */ + + case ECOND_INLIST: + case ECOND_INLISTI: + { + int sep = 0; + uschar *save_iterate_item = iterate_item; + int (*compare)(const uschar *, const uschar *); + + tempcond = FALSE; + if (cond_type == ECOND_INLISTI) + compare = strcmpic; + else + compare = (int (*)(const uschar *, const uschar *)) strcmp; + + while ((iterate_item = string_nextinlist(&sub[1], &sep, NULL, 0)) != NULL) + if (compare(sub[0], iterate_item) == 0) + { + tempcond = TRUE; + break; + } + iterate_item = save_iterate_item; + } + } /* Switch for comparison conditions */ + *yield = tempcond == testfor; return s; /* End of comparison conditions */ @@ -2315,21 +2682,21 @@ switch(cond_type) combined_cond = (cond_type == ECOND_AND); while (isspace(*s)) s++; - if (*s++ != '{') goto COND_FAILED_CURLY_START; + if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ for (;;) { while (isspace(*s)) s++; + /* {-for-text-editors */ if (*s == '}') break; - if (*s != '{') + if (*s != '{') /* }-for-text-editors */ { expand_string_message = string_sprintf("each subcondition " "inside an \"%s{...}\" condition must be in its own {}", name); return NULL; } - s = eval_condition(s+1, subcondptr); - if (s == NULL) + if (!(s = eval_condition(s+1, resetok, subcondptr))) { expand_string_message = string_sprintf("%s inside \"%s{...}\" condition", expand_string_message, name); @@ -2337,8 +2704,10 @@ switch(cond_type) } while (isspace(*s)) s++; + /* {-for-text-editors */ if (*s++ != '}') { + /* {-for-text-editors */ expand_string_message = string_sprintf("missing } at end of condition " "inside \"%s\" group", name); return NULL; @@ -2372,13 +2741,14 @@ switch(cond_type) uschar *save_iterate_item = iterate_item; while (isspace(*s)) s++; - if (*s++ != '{') goto COND_FAILED_CURLY_START; - sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL)); + if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ + sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL), TRUE, resetok); if (sub[0] == NULL) return NULL; + /* {-for-text-editors */ if (*s++ != '}') goto COND_FAILED_CURLY_END; while (isspace(*s)) s++; - if (*s++ != '{') goto COND_FAILED_CURLY_START; + if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ sub[1] = s; @@ -2386,8 +2756,7 @@ switch(cond_type) "false" part). This allows us to find the end of the condition, because if the list it empty, we won't actually evaluate the condition for real. */ - s = eval_condition(sub[1], NULL); - if (s == NULL) + if (!(s = eval_condition(sub[1], resetok, NULL))) { expand_string_message = string_sprintf("%s inside \"%s\" condition", expand_string_message, name); @@ -2395,8 +2764,10 @@ switch(cond_type) } while (isspace(*s)) s++; + /* {-for-text-editors */ if (*s++ != '}') { + /* {-for-text-editors */ expand_string_message = string_sprintf("missing } at end of condition " "inside \"%s\"", name); return NULL; @@ -2406,7 +2777,7 @@ switch(cond_type) while ((iterate_item = string_nextinlist(&sub[0], &sep, NULL, 0)) != NULL) { DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, iterate_item); - if (eval_condition(sub[1], &tempcond) == NULL) + if (!eval_condition(sub[1], resetok, &tempcond)) { expand_string_message = string_sprintf("%s inside \"%s\" condition", expand_string_message, name); @@ -2425,6 +2796,80 @@ switch(cond_type) } + /* The bool{} expansion condition maps a string to boolean. + The values supported should match those supported by the ACL condition + (acl.c, ACLC_CONDITION) so that we keep to a minimum the different ideas + of true/false. Note that Router "condition" rules have a different + interpretation, where general data can be used and only a few values + map to FALSE. + Note that readconf.c boolean matching, for boolean configuration options, + only matches true/yes/false/no. + The bool_lax{} condition matches the Router logic, which is much more + liberal. */ + case ECOND_BOOL: + case ECOND_BOOL_LAX: + { + uschar *sub_arg[1]; + uschar *t, *t2; + uschar *ourname; + size_t len; + BOOL boolvalue = FALSE; + while (isspace(*s)) s++; + if (*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)) + { + case 1: expand_string_message = string_sprintf( + "too few arguments or bracketing error for %s", + ourname); + /*FALLTHROUGH*/ + case 2: + case 3: return NULL; + } + t = sub_arg[0]; + while (isspace(*t)) t++; + len = Ustrlen(t); + if (len) + { + /* trailing whitespace: seems like a good idea to ignore it too */ + t2 = t + len - 1; + while (isspace(*t2)) t2--; + if (t2 != (t + len)) + { + *++t2 = '\0'; + len = t2 - t; + } + } + DEBUG(D_expand) + debug_printf("considering %s: %s\n", ourname, len ? t : US""); + /* logic for the lax case from expand_check_condition(), which also does + expands, and the logic is both short and stable enough that there should + be no maintenance burden from replicating it. */ + if (len == 0) + boolvalue = FALSE; + else if (Ustrspn(t, "0123456789") == len) + { + boolvalue = (Uatoi(t) == 0) ? FALSE : TRUE; + /* expand_check_condition only does a literal string "0" check */ + if ((cond_type == ECOND_BOOL_LAX) && (len > 1)) + boolvalue = TRUE; + } + else if (strcmpic(t, US"true") == 0 || strcmpic(t, US"yes") == 0) + boolvalue = TRUE; + else if (strcmpic(t, US"false") == 0 || strcmpic(t, US"no") == 0) + boolvalue = FALSE; + else if (cond_type == ECOND_BOOL_LAX) + boolvalue = TRUE; + else + { + expand_string_message = string_sprintf("unrecognised boolean " + "value \"%s\"", t); + return NULL; + } + if (yield != NULL) *yield = (boolvalue == testfor); + return s; + } + /* Unknown condition */ default: @@ -2536,6 +2981,8 @@ Arguments: sizeptr points to the output string size ptrptr points to the output string pointer type "lookup" or "if" or "extract" or "run", for error message + resetok if not NULL, pointer to flag - write FALSE if unsafe to reset + the store. Returns: 0 OK; lookup_value has been reset to save_lookup 1 expansion failed @@ -2544,7 +2991,7 @@ Returns: 0 OK; lookup_value has been reset to save_lookup static int process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, uschar **sptr, - uschar **yieldptr, int *sizeptr, int *ptrptr, uschar *type) + uschar **yieldptr, int *sizeptr, int *ptrptr, uschar *type, BOOL *resetok) { int rc = 0; uschar *s = *sptr; /* Local value */ @@ -2581,7 +3028,7 @@ if (*s++ != '{') goto FAILED_CURLY; 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); +sub1 = expand_string_internal(s, TRUE, &s, !yes, TRUE, resetok); if (sub1 == NULL && (yes || !expand_string_forcedfail)) goto FAILED; expand_string_forcedfail = FALSE; if (*s++ != '}') goto FAILED_CURLY; @@ -2606,7 +3053,7 @@ already skipping. */ while (isspace(*s)) s++; if (*s == '{') { - sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping); + sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE, resetok); if (sub2 == NULL && (!yes || !expand_string_forcedfail)) goto FAILED; expand_string_forcedfail = FALSE; if (*s++ != '}') goto FAILED_CURLY; @@ -2881,14 +3328,14 @@ Returns: on success: the value of the expression, with *error still NULL on failure: an undefined value, with *error = a message */ -static int eval_op_or(uschar **, BOOL, uschar **); +static int_eximarith_t eval_op_or(uschar **, BOOL, uschar **); -static int +static int_eximarith_t eval_expr(uschar **sptr, BOOL decimal, uschar **error, BOOL endket) { uschar *s = *sptr; -int x = eval_op_or(&s, decimal, error); +int_eximarith_t x = eval_op_or(&s, decimal, error); if (*error == NULL) { if (endket) @@ -2905,21 +3352,26 @@ return x; } -static int +static int_eximarith_t eval_number(uschar **sptr, BOOL decimal, uschar **error) { register int c; -int n; +int_eximarith_t n; uschar *s = *sptr; while (isspace(*s)) s++; c = *s; if (isdigit(c)) { int count; - (void)sscanf(CS s, (decimal? "%d%n" : "%i%n"), &n, &count); + (void)sscanf(CS s, (decimal? SC_EXIM_DEC "%n" : SC_EXIM_ARITH "%n"), &n, &count); s += count; - if (tolower(*s) == 'k') { n *= 1024; s++; } - else if (tolower(*s) == 'm') { n *= 1024*1024; s++; } + switch (tolower(*s)) + { + default: break; + case 'k': n *= 1024; s++; break; + case 'm': n *= 1024*1024; s++; break; + case 'g': n *= 1024*1024*1024; s++; break; + } while (isspace (*s)) s++; } else if (c == '(') @@ -2937,10 +3389,11 @@ return n; } -static int eval_op_unary(uschar **sptr, BOOL decimal, uschar **error) +static int_eximarith_t +eval_op_unary(uschar **sptr, BOOL decimal, uschar **error) { uschar *s = *sptr; -int x; +int_eximarith_t x; while (isspace(*s)) s++; if (*s == '+' || *s == '-' || *s == '~') { @@ -2958,20 +3411,59 @@ return x; } -static int eval_op_mult(uschar **sptr, BOOL decimal, uschar **error) +static int_eximarith_t +eval_op_mult(uschar **sptr, BOOL decimal, uschar **error) { uschar *s = *sptr; -int x = eval_op_unary(&s, decimal, error); +int_eximarith_t x = eval_op_unary(&s, decimal, error); if (*error == NULL) { while (*s == '*' || *s == '/' || *s == '%') { int op = *s++; - int y = eval_op_unary(&s, decimal, error); + int_eximarith_t y = eval_op_unary(&s, decimal, error); if (*error != NULL) break; - if (op == '*') x *= y; - else if (op == '/') x /= y; - else x %= y; + /* SIGFPE both on div/mod by zero and on INT_MIN / -1, which would give + * a value of INT_MAX+1. Note that INT_MIN * -1 gives INT_MIN for me, which + * is a bug somewhere in [gcc 4.2.1, FreeBSD, amd64]. In fact, -N*-M where + * -N*M is INT_MIN will yielf INT_MIN. + * Since we don't support floating point, this is somewhat simpler. + * Ideally, we'd return an error, but since we overflow for all other + * arithmetic, consistency suggests otherwise, but what's the correct value + * to use? There is none. + * The C standard guarantees overflow for unsigned arithmetic but signed + * overflow invokes undefined behaviour; in practice, this is overflow + * except for converting INT_MIN to INT_MAX+1. We also can't guarantee + * that long/longlong larger than int are available, or we could just work + * with larger types. We should consider whether to guarantee 32bit eval + * and 64-bit working variables, with errors returned. For now ... + * So, the only SIGFPEs occur with a non-shrinking div/mod, thus -1; we + * can just let the other invalid results occur otherwise, as they have + * until now. For this one case, we can coerce. + */ + if (y == -1 && x == EXIM_ARITH_MIN && op != '*') + { + DEBUG(D_expand) + debug_printf("Integer exception dodging: " PR_EXIM_ARITH "%c-1 coerced to " PR_EXIM_ARITH "\n", + EXIM_ARITH_MIN, op, EXIM_ARITH_MAX); + x = EXIM_ARITH_MAX; + continue; + } + if (op == '*') + x *= y; + else + { + if (y == 0) + { + *error = (op == '/') ? US"divide by zero" : US"modulo by zero"; + x = 0; + break; + } + if (op == '/') + x /= y; + else + x %= y; + } } } *sptr = s; @@ -2979,16 +3471,17 @@ return x; } -static int eval_op_sum(uschar **sptr, BOOL decimal, uschar **error) +static int_eximarith_t +eval_op_sum(uschar **sptr, BOOL decimal, uschar **error) { uschar *s = *sptr; -int x = eval_op_mult(&s, decimal, error); +int_eximarith_t x = eval_op_mult(&s, decimal, error); if (*error == NULL) { while (*s == '+' || *s == '-') { int op = *s++; - int y = eval_op_mult(&s, decimal, error); + int_eximarith_t y = eval_op_mult(&s, decimal, error); if (*error != NULL) break; if (op == '+') x += y; else x -= y; } @@ -2998,15 +3491,16 @@ return x; } -static int eval_op_shift(uschar **sptr, BOOL decimal, uschar **error) +static int_eximarith_t +eval_op_shift(uschar **sptr, BOOL decimal, uschar **error) { uschar *s = *sptr; -int x = eval_op_sum(&s, decimal, error); +int_eximarith_t x = eval_op_sum(&s, decimal, error); if (*error == NULL) { while ((*s == '<' || *s == '>') && s[1] == s[0]) { - int y; + int_eximarith_t y; int op = *s++; s++; y = eval_op_sum(&s, decimal, error); @@ -3019,15 +3513,16 @@ return x; } -static int eval_op_and(uschar **sptr, BOOL decimal, uschar **error) +static int_eximarith_t +eval_op_and(uschar **sptr, BOOL decimal, uschar **error) { uschar *s = *sptr; -int x = eval_op_shift(&s, decimal, error); +int_eximarith_t x = eval_op_shift(&s, decimal, error); if (*error == NULL) { while (*s == '&') { - int y; + int_eximarith_t y; s++; y = eval_op_shift(&s, decimal, error); if (*error != NULL) break; @@ -3039,15 +3534,16 @@ return x; } -static int eval_op_xor(uschar **sptr, BOOL decimal, uschar **error) +static int_eximarith_t +eval_op_xor(uschar **sptr, BOOL decimal, uschar **error) { uschar *s = *sptr; -int x = eval_op_and(&s, decimal, error); +int_eximarith_t x = eval_op_and(&s, decimal, error); if (*error == NULL) { while (*s == '^') { - int y; + int_eximarith_t y; s++; y = eval_op_and(&s, decimal, error); if (*error != NULL) break; @@ -3059,15 +3555,16 @@ return x; } -static int eval_op_or(uschar **sptr, BOOL decimal, uschar **error) +static int_eximarith_t +eval_op_or(uschar **sptr, BOOL decimal, uschar **error) { uschar *s = *sptr; -int x = eval_op_xor(&s, decimal, error); +int_eximarith_t x = eval_op_xor(&s, decimal, error); if (*error == NULL) { while (*s == '|') { - int y; + int_eximarith_t y; s++; y = eval_op_xor(&s, decimal, error); if (*error != NULL) break; @@ -3118,6 +3615,13 @@ we reset the store before processing it; if the result is in fresh store, we use that without copying. This is helpful for expanding strings like $message_headers which can get very long. +There's a problem if a ${dlfunc item has side-effects that cause allocation, +since resetting the store at the end of the expansion will free store that was +allocated by the plugin code as well as the slop after the expanded string. So +we skip any resets if ${dlfunc } has been used. The same applies for ${acl } +and, given the acl condition, ${if }. This is an unfortunate consequence of +string expansion becoming too powerful. + Arguments: string the string to be expanded ket_ends true if expansion is to stop at } @@ -3125,6 +3629,10 @@ Arguments: 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 + resetok_p if not NULL, pointer to flag - write FALSE if unsafe to reset + the store. Returns: NULL if expansion fails: expand_string_forcedfail is set TRUE if failure was forced @@ -3134,7 +3642,7 @@ Returns: NULL if expansion fails: static uschar * expand_string_internal(uschar *string, BOOL ket_ends, uschar **left, - BOOL skipping) + BOOL skipping, BOOL honour_dollar, BOOL *resetok_p) { int ptr = 0; int size = Ustrlen(string)+ 64; @@ -3143,6 +3651,7 @@ uschar *yield = store_get(size); uschar *s = string; uschar *save_expand_nstring[EXPAND_MAXN+1]; int save_expand_nlength[EXPAND_MAXN+1]; +BOOL resetok = TRUE; expand_string_forcedfail = FALSE; expand_string_message = US""; @@ -3184,12 +3693,14 @@ while (*s != 0) continue; } + /*{*/ /* Anything other than $ is just copied verbatim, unless we are looking for a terminating } character. */ + /*{*/ if (ket_ends && *s == '}') break; - if (*s != '$') + if (*s != '$' || !honour_dollar) { yield = string_cat(yield, &size, &ptr, s++, 1); continue; @@ -3201,7 +3712,7 @@ while (*s != 0) names can contain any printing characters except space and colon. For those that don't like typing this much, "$h_" is a synonym for "$header_". A non-existent header yields a NULL value; nothing is - inserted. */ + inserted. */ /*}*/ if (isalpha((*(++s)))) { @@ -3215,7 +3726,7 @@ while (*s != 0) if (ptr == 0 && yield != NULL) { - store_reset(yield); + if (resetok) store_reset(yield); yield = NULL; size = 0; } @@ -3288,11 +3799,11 @@ while (*s != 0) continue; } - /* Otherwise, if there's no '{' after $ it's an error. */ + /* Otherwise, if there's no '{' after $ it's an error. */ /*}*/ - if (*s != '{') + if (*s != '{') /*}*/ { - expand_string_message = US"$ not followed by letter, digit, or {"; + expand_string_message = US"$ not followed by letter, digit, or {"; /*}*/ goto EXPAND_FAILED; } @@ -3302,9 +3813,9 @@ while (*s != 0) if (isdigit((*(++s)))) { int n; - s = read_number(&n, s); + s = read_number(&n, s); /*{*/ if (*s++ != '}') - { + { /*{*/ expand_string_message = US"} expected after number"; goto EXPAND_FAILED; } @@ -3316,7 +3827,7 @@ while (*s != 0) if (!isalpha(*s)) { - expand_string_message = US"letter or digit expected after ${"; + expand_string_message = US"letter or digit expected after ${"; /*}*/ goto EXPAND_FAILED; } @@ -3329,6 +3840,46 @@ while (*s != 0) switch(item_type) { + /* Call an ACL from an expansion. We feed data in via $acl_arg1 - $acl_arg9. + If the ACL returns accept or reject we return content set by "message =" + There is currently no limit on recursion; this would have us call + acl_check_internal() directly and get a current level from somewhere. + See also the acl expansion condition ECOND_ACL and the traditional + acl modifier ACLC_ACL. + Assume that the function has side-effects on the store that must be preserved. + */ + + case EITEM_ACL: + /* ${acl {name} {arg1}{arg2}...} */ + { + uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */ + uschar *user_msg; + + switch(read_subs(sub, 10, 1, &s, skipping, TRUE, US"acl", &resetok)) + { + case 1: goto EXPAND_FAILED_CURLY; + case 2: + case 3: goto EXPAND_FAILED; + } + if (skipping) continue; + + resetok = FALSE; + switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg)) + { + case OK: + case FAIL: + if (user_msg) + yield = string_cat(yield, &size, &ptr, user_msg, Ustrlen(user_msg)); + continue; + + case DEFER: + expand_string_forcedfail = TRUE; + default: + expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]); + goto EXPAND_FAILED; + } + } + /* Handle conditionals - preserve the values of the numerical expansion variables in case they get changed by a regular expression match in the condition. If not, they retain their external settings. At the end @@ -3342,7 +3893,7 @@ while (*s != 0) save_expand_strings(save_expand_nstring, save_expand_nlength); while (isspace(*s)) s++; - next_s = eval_condition(s, skipping? NULL : &cond); + next_s = eval_condition(s, &resetok, skipping? NULL : &cond); if (next_s == NULL) goto EXPAND_FAILED; /* message already set */ DEBUG(D_expand) @@ -3362,7 +3913,8 @@ while (*s != 0) &yield, /* output pointer */ &size, /* output size */ &ptr, /* output current point */ - US"if")) /* condition type */ + US"if", /* condition type */ + &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ @@ -3403,10 +3955,10 @@ while (*s != 0) Otherwise set the key NULL pro-tem. */ while (isspace(*s)) s++; - if (*s == '{') + if (*s == '{') /*}*/ { - key = expand_string_internal(s+1, TRUE, &s, skipping); - if (key == NULL) goto EXPAND_FAILED; + key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + if (key == NULL) goto EXPAND_FAILED; /*{*/ if (*s++ != '}') goto EXPAND_FAILED_CURLY; while (isspace(*s)) s++; } @@ -3422,9 +3974,9 @@ while (*s != 0) /* The type is a string that may contain special characters of various kinds. Allow everything except space or { to appear; the actual content - is checked by search_findtype_partial. */ + is checked by search_findtype_partial. */ /*}*/ - while (*s != 0 && *s != '{' && !isspace(*s)) + while (*s != 0 && *s != '{' && !isspace(*s)) /*}*/ { if (nameptr < sizeof(name) - 1) name[nameptr++] = *s; s++; @@ -3471,7 +4023,7 @@ while (*s != 0) first. */ if (*s != '{') goto EXPAND_FAILED_CURLY; - filename = expand_string_internal(s+1, TRUE, &s, skipping); + filename = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); if (filename == NULL) goto EXPAND_FAILED; if (*s++ != '}') goto EXPAND_FAILED_CURLY; while (isspace(*s)) s++; @@ -3531,8 +4083,8 @@ while (*s != 0) if (search_find_defer) { expand_string_message = - string_sprintf("lookup of \"%s\" gave DEFER: %s", key, - search_error_message); + string_sprintf("lookup of \"%s\" gave DEFER: %s", + string_printing2(key, FALSE), search_error_message); goto EXPAND_FAILED; } if (expand_setup > 0) expand_nmax = expand_setup; @@ -3549,7 +4101,8 @@ while (*s != 0) &yield, /* output pointer */ &size, /* output size */ &ptr, /* output current point */ - US"lookup")) /* condition type */ + US"lookup", /* condition type */ + &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ @@ -3572,7 +4125,7 @@ while (*s != 0) case EITEM_PERL: #ifndef EXIM_PERL - expand_string_message = US"\"${perl\" encountered, but this facility " + expand_string_message = US"\"${perl\" encountered, but this facility " /*}*/ "is not included in this binary"; goto EXPAND_FAILED; @@ -3588,7 +4141,7 @@ while (*s != 0) } switch(read_subs(sub_arg, EXIM_PERL_MAX_ARGS + 1, 1, &s, skipping, TRUE, - US"perl")) + US"perl", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -3660,7 +4213,7 @@ while (*s != 0) uschar *sub_arg[3]; uschar *p,*domain; - switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, US"prvs")) + switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, US"prvs", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -3699,11 +4252,11 @@ while (*s != 0) *domain++ = '\0'; yield = string_cat(yield,&size,&ptr,US"prvs=",5); - string_cat(yield,&size,&ptr,sub_arg[0],Ustrlen(sub_arg[0])); - string_cat(yield,&size,&ptr,US"/",1); string_cat(yield,&size,&ptr,(sub_arg[2] != NULL) ? sub_arg[2] : US"0", 1); string_cat(yield,&size,&ptr,prvs_daystamp(7),3); string_cat(yield,&size,&ptr,p,6); + string_cat(yield,&size,&ptr,US"=",1); + string_cat(yield,&size,&ptr,sub_arg[0],Ustrlen(sub_arg[0])); string_cat(yield,&size,&ptr,US"@",1); string_cat(yield,&size,&ptr,domain,Ustrlen(domain)); @@ -3734,22 +4287,22 @@ while (*s != 0) prvscheck_address = NULL; prvscheck_keynum = NULL; - switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs")) + switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs", &resetok)) { 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})\\@(.+)$", + re = regex_must_compile(US"^prvs\\=([0-9])([0-9]{3})([A-F0-9]{6})\\=(.+)\\@(.+)$", TRUE,FALSE); if (regex_match_and_setup(re,sub_arg[0],0,-1)) { - uschar *local_part = string_copyn(expand_nstring[1],expand_nlength[1]); - uschar *key_num = string_copyn(expand_nstring[2],expand_nlength[2]); - uschar *daystamp = string_copyn(expand_nstring[3],expand_nlength[3]); - uschar *hash = string_copyn(expand_nstring[4],expand_nlength[4]); + 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("prvscheck localpart: %s\n", local_part); @@ -3766,7 +4319,7 @@ while (*s != 0) prvscheck_keynum = string_copy(key_num); /* Now expand the second argument */ - switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs")) + switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -3800,7 +4353,7 @@ while (*s != 0) Adjust "inow" accordingly. */ if ( (iexpire < 7) && (inow >= 993) ) inow = 0; - if (iexpire > inow) + if (iexpire >= inow) { prvscheck_result = US"1"; DEBUG(D_expand) debug_printf("prvscheck: success, $pvrs_result set to 1\n"); @@ -3820,7 +4373,7 @@ while (*s != 0) /* 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, US"prvs")) + switch(read_subs(sub_arg, 1, 0, &s, skipping, TRUE, US"prvs", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -3844,7 +4397,7 @@ while (*s != 0) 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, US"prvs")) + switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"prvs", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -3868,7 +4421,7 @@ while (*s != 0) goto EXPAND_FAILED; } - switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"readfile")) + switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"readfile", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -3914,7 +4467,7 @@ while (*s != 0) /* 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, US"readsocket")) + switch(read_subs(sub_arg, 4, 2, &s, skipping, FALSE, US"readsocket", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: /* Won't occur: no end check */ @@ -4140,7 +4693,7 @@ while (*s != 0) if (*s == '{') { - if (expand_string_internal(s+1, TRUE, &s, TRUE) == NULL) + if (expand_string_internal(s+1, TRUE, &s, TRUE, TRUE, &resetok) == NULL) goto EXPAND_FAILED; if (*s++ != '}') goto EXPAND_FAILED_CURLY; while (isspace(*s)) s++; @@ -4155,7 +4708,7 @@ while (*s != 0) SOCK_FAIL: if (*s != '{') goto EXPAND_FAILED; DEBUG(D_any) debug_printf("%s\n", expand_string_message); - arg = expand_string_internal(s+1, TRUE, &s, FALSE); + arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE, &resetok); if (arg == NULL) goto EXPAND_FAILED; yield = string_cat(yield, &size, &ptr, arg, Ustrlen(arg)); if (*s++ != '}') goto EXPAND_FAILED_CURLY; @@ -4184,7 +4737,7 @@ while (*s != 0) while (isspace(*s)) s++; if (*s != '{') goto EXPAND_FAILED_CURLY; - arg = expand_string_internal(s+1, TRUE, &s, skipping); + arg = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); if (arg == NULL) goto EXPAND_FAILED; while (isspace(*s)) s++; if (*s++ != '}') goto EXPAND_FAILED_CURLY; @@ -4221,13 +4774,24 @@ while (*s != 0) (void)close(fd_in); + /* Read the pipe to get the command's output into $value (which is kept + in lookup_value). Read during execution, so that if the output exceeds + the OS pipe buffer limit, we don't block forever. */ + + f = fdopen(fd_out, "rb"); + sigalrm_seen = FALSE; + alarm(60); + lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL); + alarm(0); + (void)fclose(f); + /* Wait for the process to finish, applying the timeout, and inspect its return code for serious disasters. Simple non-zero returns are passed on. */ - if ((runrc = child_close(pid, 60)) < 0) + if (sigalrm_seen == TRUE || (runrc = child_close(pid, 30)) < 0) { - if (runrc == -256) + if (sigalrm_seen == TRUE || runrc == -256) { expand_string_message = string_sprintf("command timed out"); killpg(pid, SIGKILL); /* Kill the whole process group */ @@ -4243,14 +4807,6 @@ while (*s != 0) goto EXPAND_FAILED; } - - /* Read the pipe to get the command's output into $value (which is kept - in lookup_value). */ - - f = fdopen(fd_out, "rb"); - lookup_value = NULL; - lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL); - (void)fclose(f); } /* Process the yes/no strings; $value may be useful in both cases */ @@ -4263,7 +4819,8 @@ while (*s != 0) &yield, /* output pointer */ &size, /* output size */ &ptr, /* output current point */ - US"run")) /* condition type */ + US"run", /* condition type */ + &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ @@ -4280,7 +4837,7 @@ while (*s != 0) int o2m; uschar *sub[3]; - switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"tr")) + switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"tr", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4318,11 +4875,11 @@ while (*s != 0) 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. */ + 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)) + TRUE, name, &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4397,7 +4954,7 @@ while (*s != 0) uschar innerkey[MAX_HASHBLOCKLEN]; uschar outerkey[MAX_HASHBLOCKLEN]; - switch (read_subs(sub, 3, 3, &s, skipping, TRUE, name)) + switch (read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4492,7 +5049,7 @@ while (*s != 0) int save_expand_nmax = save_expand_strings(save_expand_nstring, save_expand_nlength); - switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"sg")) + switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"sg", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4610,10 +5167,10 @@ while (*s != 0) for (i = 0; i < j; i++) { while (isspace(*s)) s++; - if (*s == '{') + if (*s == '{') /*}*/ { - sub[i] = expand_string_internal(s+1, TRUE, &s, skipping); - if (sub[i] == NULL) goto EXPAND_FAILED; + sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + if (sub[i] == NULL) goto EXPAND_FAILED; /*{*/ if (*s++ != '}') goto EXPAND_FAILED_CURLY; /* After removal of leading and trailing white space, the first @@ -4634,7 +5191,7 @@ while (*s != 0) while (len > 0 && isspace(p[len-1])) len--; p[len] = 0; - if (*p == 0) + if (*p == 0 && !skipping) { expand_string_message = US"first argument of \"extract\" must " "not be empty"; @@ -4676,7 +5233,8 @@ while (*s != 0) &yield, /* output pointer */ &size, /* output size */ &ptr, /* output current point */ - US"extract")) /* condition type */ + US"extract", /* condition type */ + &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ @@ -4690,6 +5248,98 @@ while (*s != 0) continue; } + /* return the Nth item from a list */ + + case EITEM_LISTEXTRACT: + { + int i; + int field_number = 1; + uschar *save_lookup_value = lookup_value; + uschar *sub[2]; + int save_expand_nmax = + save_expand_strings(save_expand_nstring, save_expand_nlength); + + /* Read the field & list arguments */ + + for (i = 0; i < 2; i++) + { + while (isspace(*s)) s++; + if (*s != '{') /*}*/ + goto EXPAND_FAILED_CURLY; + + sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + if (!sub[i]) goto EXPAND_FAILED; /*{*/ + if (*s++ != '}') goto EXPAND_FAILED_CURLY; + + /* After removal of leading and trailing white space, the first + argument must be numeric and nonempty. */ + + if (i == 0) + { + int len; + int x = 0; + uschar *p = sub[0]; + + while (isspace(*p)) p++; + sub[0] = p; + + len = Ustrlen(p); + while (len > 0 && isspace(p[len-1])) len--; + p[len] = 0; + + if (!*p && !skipping) + { + expand_string_message = US"first argument of \"listextract\" must " + "not be empty"; + goto EXPAND_FAILED; + } + + if (*p == '-') + { + field_number = -1; + p++; + } + while (*p && isdigit(*p)) x = x * 10 + *p++ - '0'; + if (*p) + { + expand_string_message = US"first argument of \"listextract\" must " + "be numeric"; + goto EXPAND_FAILED; + } + field_number *= x; + } + } + + /* Extract the numbered element into $value. If + skipping, just pretend the extraction failed. */ + + lookup_value = 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 */ + &size, /* output size */ + &ptr, /* output current point */ + US"extract", /* condition type */ + &resetok)) + { + case 1: goto EXPAND_FAILED; /* when all is well, the */ + case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ + } + + /* All done - restore numerical variables. */ + + restore_expand_strings(save_expand_nmax, save_expand_nstring, + save_expand_nlength); + + continue; + } /* Handle list operations */ @@ -4703,11 +5353,12 @@ while (*s != 0) uschar *list, *expr, *temp; uschar *save_iterate_item = iterate_item; uschar *save_lookup_value = lookup_value; + BOOL dummy; while (isspace(*s)) s++; if (*s++ != '{') goto EXPAND_FAILED_CURLY; - list = expand_string_internal(s, TRUE, &s, skipping); + list = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok); if (list == NULL) goto EXPAND_FAILED; if (*s++ != '}') goto EXPAND_FAILED_CURLY; @@ -4715,7 +5366,7 @@ while (*s != 0) { while (isspace(*s)) s++; if (*s++ != '{') goto EXPAND_FAILED_CURLY; - temp = expand_string_internal(s, TRUE, &s, skipping); + temp = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok); if (temp == NULL) goto EXPAND_FAILED; lookup_value = temp; if (*s++ != '}') goto EXPAND_FAILED_CURLY; @@ -4734,12 +5385,12 @@ while (*s != 0) if (item_type == EITEM_FILTER) { - temp = eval_condition(expr, NULL); + temp = eval_condition(expr, &resetok, NULL); if (temp != NULL) s = temp; } else { - temp = expand_string_internal(s, TRUE, &s, TRUE); + temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok); } if (temp == NULL) @@ -4751,15 +5402,15 @@ while (*s != 0) while (isspace(*s)) s++; if (*s++ != '}') - { + { /*{*/ expand_string_message = string_sprintf("missing } at end of condition " "or expression inside \"%s\"", name); goto EXPAND_FAILED; } - while (isspace(*s)) s++; + while (isspace(*s)) s++; /*{*/ if (*s++ != '}') - { + { /*{*/ expand_string_message = string_sprintf("missing } at end of \"%s\"", name); goto EXPAND_FAILED; @@ -4778,7 +5429,7 @@ while (*s != 0) if (item_type == EITEM_FILTER) { BOOL condresult; - if (eval_condition(expr, &condresult) == NULL) + if (eval_condition(expr, &resetok, &condresult) == NULL) { iterate_item = save_iterate_item; lookup_value = save_lookup_value; @@ -4798,7 +5449,7 @@ while (*s != 0) else { - temp = expand_string_internal(expr, TRUE, NULL, skipping); + temp = expand_string_internal(expr, TRUE, NULL, skipping, TRUE, &resetok); if (temp == NULL) { iterate_item = save_iterate_item; @@ -4869,7 +5520,7 @@ while (*s != 0) } - /* If ${dlfunc support is configured, handle calling dynamically-loaded + /* If ${dlfunc } support is configured, handle calling dynamically-loaded functions, unless locked out at this time. Syntax is ${dlfunc{file}{func}} or ${dlfunc{file}{func}{arg}} or ${dlfunc{file}{func}{arg1}{arg2}} or up to a maximum of EXPAND_DLFUNC_MAX_ARGS arguments (defined below). */ @@ -4878,7 +5529,7 @@ while (*s != 0) case EITEM_DLFUNC: #ifndef EXPAND_DLFUNC - expand_string_message = US"\"${dlfunc\" encountered, but this facility " + expand_string_message = US"\"${dlfunc\" encountered, but this facility " /*}*/ "is not included in this binary"; goto EXPAND_FAILED; @@ -4898,7 +5549,7 @@ while (*s != 0) } switch(read_subs(argv, EXPAND_DLFUNC_MAX_ARGS + 2, 2, &s, skipping, - TRUE, US"dlfunc")) + TRUE, US"dlfunc", &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -4945,8 +5596,10 @@ while (*s != 0) returns OK, we have a replacement string; if it returns DEFER then expansion has failed in a non-forced manner; if it returns FAIL then failure was forced; if it returns ERROR or any other value there's a - problem, so panic slightly. */ + problem, so panic slightly. In any case, assume that the function has + side-effects on the store that must be preserved. */ + resetok = FALSE; result = NULL; for (argc = 0; argv[argc] != NULL; argc++); status = func(&result, argc - 2, &argv[2]); @@ -4967,7 +5620,7 @@ while (*s != 0) } } #endif /* EXPAND_DLFUNC */ - } + } /* EITEM_* switch */ /* Control reaches here if the name is not recognized as one of the more complicated expansion items. Check for the "operator" syntax (name terminated @@ -4978,7 +5631,7 @@ while (*s != 0) { int c; uschar *arg = NULL; - uschar *sub = expand_string_internal(s+1, TRUE, &s, skipping); + uschar *sub = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); if (sub == NULL) goto EXPAND_FAILED; s++; @@ -5053,7 +5706,7 @@ while (*s != 0) case EOP_EXPAND: { - uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping); + uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE, &resetok); if (expanded == NULL) { expand_string_message = @@ -5157,6 +5810,122 @@ while (*s != 0) continue; } + /* Convert octets outside 0x21..0x7E to \xXX form */ + + case EOP_HEXQUOTE: + { + uschar *t = sub - 1; + while (*(++t) != 0) + { + if (*t < 0x21 || 0x7E < *t) + yield = string_cat(yield, &size, &ptr, + string_sprintf("\\x%02x", *t), 4); + else + yield = string_cat(yield, &size, &ptr, t, 1); + } + continue; + } + + /* count the number of list elements */ + + case EOP_LISTCOUNT: + { + int cnt = 0; + int sep = 0; + uschar * cp; + uschar buffer[256]; + + while (string_nextinlist(&sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++; + cp = string_sprintf("%d", cnt); + yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp)); + continue; + } + + /* expand a named list given the name */ + /* handles nested named lists; requotes as colon-sep list */ + + case EOP_LISTNAMED: + { + tree_node *t = NULL; + uschar * list; + int sep = 0; + uschar * item; + uschar * suffix = US""; + BOOL needsep = FALSE; + uschar buffer[256]; + + if (*sub == '+') sub++; + if (arg == NULL) /* no-argument version */ + { + if (!(t = tree_search(addresslist_anchor, sub)) && + !(t = tree_search(domainlist_anchor, sub)) && + !(t = tree_search(hostlist_anchor, sub))) + t = tree_search(localpartlist_anchor, sub); + } + else switch(*arg) /* specific list-type version */ + { + case 'a': t = tree_search(addresslist_anchor, sub); suffix = US"_a"; break; + case 'd': t = tree_search(domainlist_anchor, sub); suffix = US"_d"; break; + case 'h': t = tree_search(hostlist_anchor, sub); suffix = US"_h"; break; + case 'l': t = tree_search(localpartlist_anchor, sub); suffix = US"_l"; break; + default: + expand_string_message = string_sprintf("bad suffix on \"list\" operator"); + goto EXPAND_FAILED; + } + + if(!t) + { + expand_string_message = string_sprintf("\"%s\" is not a %snamed list", + sub, !arg?"" + : *arg=='a'?"address " + : *arg=='d'?"domain " + : *arg=='h'?"host " + : *arg=='l'?"localpart " + : 0); + goto EXPAND_FAILED; + } + + list = ((namedlist_block *)(t->data.ptr))->string; + + while ((item = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL) + { + uschar * buf = US" : "; + if (needsep) + yield = string_cat(yield, &size, &ptr, buf, 3); + else + needsep = TRUE; + + if (*item == '+') /* list item is itself a named list */ + { + uschar * sub = string_sprintf("${listnamed%s:%s}", suffix, item); + item = expand_string_internal(sub, FALSE, NULL, FALSE, TRUE, &resetok); + } + else if (sep != ':') /* item from non-colon-sep list, re-quote for colon list-separator */ + { + char * cp; + char tok[3]; + tok[0] = sep; tok[1] = ':'; tok[2] = 0; + while ((cp= strpbrk((const char *)item, tok))) + { + yield = string_cat(yield, &size, &ptr, item, cp-(char *)item); + if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */ + { + yield = string_cat(yield, &size, &ptr, US"::", 2); + item = (uschar *)cp; + } + else /* sep in item; should already be doubled; emit once */ + { + yield = string_cat(yield, &size, &ptr, (uschar *)tok, 1); + if (*cp == sep) cp++; + item = (uschar *)cp; + } + } + } + yield = string_cat(yield, &size, &ptr, item, Ustrlen(item)); + } + continue; + } + /* mask applies a mask to an IP address; for example the result of ${mask:131.111.10.206/28} is 131.111.10.192/28. */ @@ -5360,8 +6129,8 @@ while (*s != 0) goto EXPAND_FAILED; } - if (lookup_list[n].quote != NULL) - sub = (lookup_list[n].quote)(sub, opt); + if (lookup_list[n]->quote != NULL) + sub = (lookup_list[n]->quote)(sub, opt); else if (opt != NULL) sub = NULL; if (sub == NULL) @@ -5453,7 +6222,7 @@ while (*s != 0) { uschar *save_sub = sub; uschar *error = NULL; - int n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE); + int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE); if (error != NULL) { expand_string_message = string_sprintf("error in expression " @@ -5461,7 +6230,7 @@ while (*s != 0) save_sub); goto EXPAND_FAILED; } - sprintf(CS var_buffer, "%d", n); + sprintf(CS var_buffer, PR_EXIM_ARITH, n); yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer)); continue; } @@ -5662,6 +6431,40 @@ while (*s != 0) continue; } + /* vaguely random number less than N */ + + case EOP_RANDINT: + { + int_eximarith_t max; + uschar *s; + + max = expand_string_integer(sub, TRUE); + if (expand_string_message != NULL) + goto EXPAND_FAILED; + s = string_sprintf("%d", vaguely_random_number((int)max)); + yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); + continue; + } + + /* Reverse IP, including IPv6 to dotted-nibble */ + + case EOP_REVERSE_IP: + { + 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, &size, &ptr, reversed, Ustrlen(reversed)); + continue; + } + /* Unknown operator */ default: @@ -5677,14 +6480,14 @@ while (*s != 0) store instead of copying. Many expansion strings contain just one reference, so this is a useful optimization, especially for humungous headers ($message_headers). */ - + /*{*/ if (*s++ == '}') { int len; int newsize = 0; if (ptr == 0) { - store_reset(yield); + if (resetok) store_reset(yield); yield = NULL; size = 0; } @@ -5739,7 +6542,9 @@ if (left != NULL) *left = s; In many cases the final string will be the first one that was got and so there will be optimal store usage. */ -store_reset(yield + ptr + 1); +if (resetok) store_reset(yield + ptr + 1); +else if (resetok_p) *resetok_p = FALSE; + DEBUG(D_expand) { debug_printf("expanding: %.*s\n result: %s\n", (int)(s - string), string, @@ -5769,6 +6574,7 @@ DEBUG(D_expand) debug_printf(" error message: %s\n", expand_string_message); if (expand_string_forcedfail) debug_printf("failure was forced\n"); } +if (resetok_p) *resetok_p = resetok; return NULL; } @@ -5787,7 +6593,7 @@ expand_string(uschar *string) search_find_defer = FALSE; malformed_header = FALSE; return (Ustrpbrk(string, "$\\") == NULL)? string : - expand_string_internal(string, FALSE, NULL, FALSE); + expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL); } @@ -5829,10 +6635,10 @@ Returns: the integer value, or expand_string_message is set NULL for an OK integer */ -int +int_eximarith_t expand_string_integer(uschar *string, BOOL isplus) { -long int value; +int_eximarith_t value; uschar *s = expand_string(string); uschar *msg = US"invalid integer \"%s\""; uschar *endptr; @@ -5847,7 +6653,24 @@ systems, so we set it zero ourselves. */ errno = 0; expand_string_message = NULL; /* Indicates no error */ -value = strtol(CS s, CSS &endptr, 10); + +/* Before Exim 4.64, strings consisting entirely of whitespace compared +equal to 0. Unfortunately, people actually relied upon that, so preserve +the behaviour explicitly. Stripping leading whitespace is a harmless +noop change since strtol skips it anyway (provided that there is a number +to find at all). */ +if (isspace(*s)) + { + while (isspace(*s)) ++s; + if (*s == '\0') + { + DEBUG(D_expand) + debug_printf("treating blank string as number 0\n"); + return 0; + } + } + +value = strtoll(CS s, CSS &endptr, 10); if (endptr == s) { @@ -5859,31 +6682,32 @@ else if (value < 0 && isplus) } else { - /* Ensure we can cast this down to an int */ - if (value > INT_MAX || value < INT_MIN) errno = ERANGE; - - if (errno != ERANGE) + switch (tolower(*endptr)) { - if (tolower(*endptr) == 'k') - { - if (value > INT_MAX/1024 || value < INT_MIN/1024) errno = ERANGE; - else value *= 1024; + default: + break; + case 'k': + if (value > EXIM_ARITH_MAX/1024 || value < EXIM_ARITH_MIN/1024) errno = ERANGE; + else value *= 1024; endptr++; - } - else if (tolower(*endptr) == 'm') - { - if (value > INT_MAX/(1024*1024) || value < INT_MIN/(1024*1024)) - errno = ERANGE; + break; + case 'm': + if (value > EXIM_ARITH_MAX/(1024*1024) || value < EXIM_ARITH_MIN/(1024*1024)) errno = ERANGE; else value *= 1024*1024; endptr++; - } + break; + case 'g': + if (value > EXIM_ARITH_MAX/(1024*1024*1024) || value < EXIM_ARITH_MIN/(1024*1024*1024)) errno = ERANGE; + else value *= 1024*1024*1024; + endptr++; + break; } if (errno == ERANGE) msg = US"absolute value of integer \"%s\" is too large (overflow)"; else { while (isspace(*endptr)) endptr++; - if (*endptr == 0) return (int)value; + if (*endptr == 0) return value; } } @@ -5956,6 +6780,9 @@ for (i = 1; i < argc; i++) #ifdef LOOKUP_PGSQL pgsql_servers = argv[i]; #endif + #ifdef EXPERIMENTAL_REDIS + redis_servers = argv[i]; + #endif } #ifdef EXIM_PERL else opt_perl_startup = argv[i];