X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/93a6fce2ebf117f490d7ee11f066f75280d32386..20fcb1e7be45177beca2d433f54260843cc7c2f6:/src/src/expand.c diff --git a/src/src/expand.c b/src/src/expand.c index 145b827dc..c13284d8e 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2014 */ +/* Copyright (c) University of Cambridge 1995 - 2016 */ /* See the file NOTICE for conditions of use and distribution. */ @@ -94,10 +94,6 @@ bcrypt ({CRYPT}$2a$). -#ifndef nelements -# define nelements(arr) (sizeof(arr) / sizeof(*arr)) -#endif - /************************************************* * Local statics and tables * *************************************************/ @@ -109,11 +105,15 @@ static uschar *item_table[] = { US"acl", US"certextract", US"dlfunc", + US"env", US"extract", US"filter", US"hash", US"hmac", US"if", +#ifdef SUPPORT_I18N + US"imapfolder", +#endif US"length", US"listextract", US"lookup", @@ -135,11 +135,15 @@ enum { EITEM_ACL, EITEM_CERTEXTRACT, EITEM_DLFUNC, + EITEM_ENV, EITEM_EXTRACT, EITEM_FILTER, EITEM_HASH, EITEM_HMAC, EITEM_IF, +#ifdef SUPPORT_I18N + EITEM_IMAPFOLDER, +#endif EITEM_LENGTH, EITEM_LISTEXTRACT, EITEM_LOOKUP, @@ -168,7 +172,14 @@ static uschar *op_table_underscore[] = { US"quote_local_part", US"reverse_ip", US"time_eval", - US"time_interval"}; + US"time_interval" +#ifdef SUPPORT_I18N + ,US"utf8_domain_from_alabel", + US"utf8_domain_to_alabel", + US"utf8_localpart_from_alabel", + US"utf8_localpart_to_alabel" +#endif + }; enum { EOP_FROM_UTF8, @@ -176,13 +187,24 @@ enum { EOP_QUOTE_LOCAL_PART, EOP_REVERSE_IP, EOP_TIME_EVAL, - EOP_TIME_INTERVAL }; + EOP_TIME_INTERVAL +#ifdef SUPPORT_I18N + ,EOP_UTF8_DOMAIN_FROM_ALABEL, + EOP_UTF8_DOMAIN_TO_ALABEL, + EOP_UTF8_LOCALPART_FROM_ALABEL, + EOP_UTF8_LOCALPART_TO_ALABEL +#endif + }; static uschar *op_table_main[] = { US"address", US"addresses", + US"base32", + US"base32d", US"base62", US"base62d", + US"base64", + US"base64d", US"domain", US"escape", US"eval", @@ -192,6 +214,8 @@ static uschar *op_table_main[] = { US"hash", US"hex2b64", US"hexquote", + US"ipv6denorm", + US"ipv6norm", US"l", US"lc", US"length", @@ -209,6 +233,7 @@ static uschar *op_table_main[] = { US"s", US"sha1", US"sha256", + US"sha3", US"stat", US"str2b64", US"strlen", @@ -217,10 +242,14 @@ static uschar *op_table_main[] = { US"utf8clean" }; enum { - EOP_ADDRESS = sizeof(op_table_underscore)/sizeof(uschar *), + EOP_ADDRESS = nelem(op_table_underscore), EOP_ADDRESSES, + EOP_BASE32, + EOP_BASE32D, EOP_BASE62, EOP_BASE62D, + EOP_BASE64, + EOP_BASE64D, EOP_DOMAIN, EOP_ESCAPE, EOP_EVAL, @@ -230,6 +259,8 @@ enum { EOP_HASH, EOP_HEX2B64, EOP_HEXQUOTE, + EOP_IPV6DENORM, + EOP_IPV6NORM, EOP_L, EOP_LC, EOP_LENGTH, @@ -247,6 +278,7 @@ enum { EOP_S, EOP_SHA1, EOP_SHA256, + EOP_SHA3, EOP_STAT, EOP_STR2B64, EOP_STRLEN, @@ -444,6 +476,7 @@ static var_entry var_table[] = { { "bounce_return_size_limit", vtype_int, &bounce_return_size_limit }, { "caller_gid", vtype_gid, &real_gid }, { "caller_uid", vtype_uid, &real_uid }, + { "callout_address", vtype_stringptr, &callout_address }, { "compile_date", vtype_stringptr, &version_date }, { "compile_number", vtype_stringptr, &version_cnumber }, { "config_dir", vtype_stringptr, &config_main_directory }, @@ -453,10 +486,6 @@ static var_entry var_table[] = { { "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 #ifndef DISABLE_DKIM { "dkim_algo", vtype_dkim, (void *)DKIM_ALGO }, { "dkim_bodylength", vtype_dkim, (void *)DKIM_BODYLENGTH }, @@ -470,6 +499,7 @@ static var_entry var_table[] = { { "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 }, @@ -492,7 +522,7 @@ static var_entry var_table[] = { { "dnslist_value", vtype_stringptr, &dnslist_value }, { "domain", vtype_stringptr, &deliver_domain }, { "domain_data", vtype_stringptr, &deliver_domain_data }, -#ifdef EXPERIMENTAL_EVENT +#ifndef DISABLE_EVENT { "event_data", vtype_stringptr, &event_data }, /*XXX want to use generic vars for as many of these as possible*/ @@ -504,9 +534,6 @@ static var_entry var_table[] = { { "exim_path", vtype_stringptr, &exim_path }, { "exim_uid", vtype_uid, &exim_uid }, { "exim_version", vtype_stringptr, &version_string }, -#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 }, @@ -515,6 +542,7 @@ static var_entry var_table[] = { { "host_lookup_deferred",vtype_int, &host_lookup_deferred }, { "host_lookup_failed", vtype_int, &host_lookup_failed }, { "host_port", vtype_int, &deliver_host_port }, + { "initial_cwd", vtype_stringptr, &initial_cwd }, { "inode", vtype_ino, &deliver_inode }, { "interface_address", vtype_stringptr, &interface_address }, { "interface_port", vtype_int, &interface_port }, @@ -549,6 +577,9 @@ static var_entry var_table[] = { { "message_id", vtype_stringptr, &message_id }, { "message_linecount", vtype_int, &message_linecount }, { "message_size", vtype_int, &message_size }, +#ifdef SUPPORT_I18N + { "message_smtputf8", vtype_bool, &message_smtputf8 }, +#endif #ifdef WITH_CONTENT_SCAN { "mime_anomaly_level", vtype_int, &mime_anomaly_level }, { "mime_anomaly_text", vtype_stringptr, &mime_anomaly_text }, @@ -584,19 +615,23 @@ static var_entry var_table[] = { { "parent_domain", vtype_stringptr, &deliver_domain_parent }, { "parent_local_part", vtype_stringptr, &deliver_localpart_parent }, { "pid", vtype_pid, NULL }, +#ifndef DISABLE_PRDR + { "prdr_requested", vtype_bool, &prdr_requested }, +#endif { "primary_hostname", vtype_stringptr, &primary_hostname }, -#ifdef EXPERIMENTAL_PROXY - { "proxy_host_address", vtype_stringptr, &proxy_host_address }, - { "proxy_host_port", vtype_int, &proxy_host_port }, +#if defined(SUPPORT_PROXY) || defined(SUPPORT_SOCKS) + { "proxy_external_address",vtype_stringptr, &proxy_external_address }, + { "proxy_external_port", vtype_int, &proxy_external_port }, + { "proxy_local_address", vtype_stringptr, &proxy_local_address }, + { "proxy_local_port", vtype_int, &proxy_local_port }, { "proxy_session", vtype_bool, &proxy_session }, - { "proxy_target_address",vtype_stringptr, &proxy_target_address }, - { "proxy_target_port", vtype_int, &proxy_target_port }, #endif { "prvscheck_address", vtype_stringptr, &prvscheck_address }, { "prvscheck_keynum", vtype_stringptr, &prvscheck_keynum }, { "prvscheck_result", vtype_stringptr, &prvscheck_result }, { "qualify_domain", vtype_stringptr, &qualify_domain_sender }, { "qualify_recipient", vtype_stringptr, &qualify_domain_recipient }, + { "queue_name", vtype_stringptr, &queue_name }, { "rcpt_count", vtype_int, &rcpt_count }, { "rcpt_defer_count", vtype_int, &rcpt_defer_count }, { "rcpt_fail_count", vtype_int, &rcpt_fail_count }, @@ -625,6 +660,7 @@ static var_entry var_table[] = { { "sender_address_local_part", vtype_localpart, &sender_address }, { "sender_data", vtype_stringptr, &sender_data }, { "sender_fullhost", vtype_stringptr, &sender_fullhost }, + { "sender_helo_dnssec", vtype_bool, &sender_helo_dnssec }, { "sender_helo_name", vtype_stringptr, &sender_helo_name }, { "sender_host_address", vtype_stringptr, &sender_host_address }, { "sender_host_authenticated",vtype_stringptr, &sender_host_authenticated }, @@ -738,7 +774,7 @@ static var_entry var_table[] = { { "warnmsg_recipients", vtype_stringptr, &warnmsg_recipients } }; -static int var_table_size = sizeof(var_table)/sizeof(var_entry); +static int var_table_size = nelem(var_table); static uschar var_buffer[256]; static BOOL malformed_header; @@ -806,6 +842,9 @@ static int utf8_table2[] = { 0xff, 0x1f, 0x0f, 0x07, 0x03, 0x01}; } + +static uschar * base32_chars = US"abcdefghijklmnopqrstuvwxyz234567"; + /************************************************* * Binary chop search on a table * *************************************************/ @@ -1038,6 +1077,8 @@ return s; Returns: a pointer to the character after the last digit */ +/*XXX consider expanding to int_eximarith_t. But the test for +"overbig numbers" in 0002 still needs to overflow it. */ static uschar * read_number(int *n, uschar *s) @@ -1244,7 +1285,7 @@ certfield * cp; if (!(vp = find_var_ent(certvar))) { - expand_string_message = + expand_string_message = string_sprintf("no variable named \"%s\"", certvar); return NULL; /* Unknown variable name */ } @@ -1252,7 +1293,7 @@ if (!(vp = find_var_ent(certvar))) want to do that in future */ if (vp->type != vtype_cert) { - expand_string_message = + expand_string_message = string_sprintf("\"%s\" is not a certificate", certvar); return NULL; /* Unknown variable name */ } @@ -1263,7 +1304,7 @@ if (*field >= '0' && *field <= '9') return tls_cert_ext_by_oid(*(void **)vp->value, field, 0); for(cp = certfields; - cp < certfields + nelements(certfields); + cp < certfields + nelem(certfields); cp++) if (Ustrncmp(cp->name, field, cp->namelen) == 0) { @@ -1272,7 +1313,7 @@ for(cp = certfields; return (*cp->getfn)( *(void **)vp->value, modifier ); } -expand_string_message = +expand_string_message = string_sprintf("bad field selector \"%s\" for certextract", field); return NULL; } @@ -1433,7 +1474,7 @@ unsigned long int total = 0; /* no overflow */ while (*s != 0) { - if (i == 0) i = sizeof(prime)/sizeof(int) - 1; + if (i == 0) i = nelem(prime) - 1; total += prime[i--] * (unsigned int)(*s++); } @@ -1638,9 +1679,8 @@ if (!enable_dollar_recipients) return NULL; else 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)); + if (i != 0) s = string_catn(s, &size, &ptr, US", ", 2); + s = string_cat(s, &size, &ptr, recipients_list[i].address); } s[ptr] = 0; /* string_cat() leaves room */ return s; @@ -1694,7 +1734,7 @@ if ((Ustrncmp(name, "acl_c", 5) == 0 || Ustrncmp(name, "acl_m", 5) == 0) && { tree_node *node = tree_search((name[4] == 'c')? acl_var_c : acl_var_m, name + 4); - return (node == NULL)? (strict_acl_vars? NULL : US"") : node->data.ptr; + return node ? node->data.ptr : strict_acl_vars ? NULL : US""; } /* Handle $auth variables. */ @@ -1704,7 +1744,14 @@ if (Ustrncmp(name, "auth", 4) == 0) uschar *endptr; int n = Ustrtoul(name + 4, &endptr, 10); if (*endptr == 0 && n != 0 && n <= AUTH_VARS) - return (auth_vars[n-1] == NULL)? US"" : auth_vars[n-1]; + return !auth_vars[n-1] ? US"" : auth_vars[n-1]; + } +else if (Ustrncmp(name, "regex", 5) == 0) + { + uschar *endptr; + int n = Ustrtoul(name + 5, &endptr, 10); + if (*endptr == 0 && n != 0 && n <= REGEX_VARS) + return !regex_vars[n-1] ? US"" : regex_vars[n-1]; } /* For all other variables, search the table */ @@ -1722,153 +1769,150 @@ val = vp->value; switch (vp->type) { case vtype_filter_int: - if (!filter_running) return NULL; - /* Fall through */ - /* VVVVVVVVVVVV */ + if (!filter_running) return NULL; + /* Fall through */ + /* VVVVVVVVVVVV */ case vtype_int: - sprintf(CS var_buffer, "%d", *(int *)(val)); /* Integer */ - return var_buffer; + sprintf(CS var_buffer, "%d", *(int *)(val)); /* Integer */ + return var_buffer; case vtype_ino: - sprintf(CS var_buffer, "%ld", (long int)(*(ino_t *)(val))); /* Inode */ - return var_buffer; + sprintf(CS var_buffer, "%ld", (long int)(*(ino_t *)(val))); /* Inode */ + return var_buffer; case vtype_gid: - sprintf(CS var_buffer, "%ld", (long int)(*(gid_t *)(val))); /* gid */ - return var_buffer; + sprintf(CS var_buffer, "%ld", (long int)(*(gid_t *)(val))); /* gid */ + return var_buffer; case vtype_uid: - sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(val))); /* uid */ - return var_buffer; + sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(val))); /* uid */ + return var_buffer; case vtype_bool: - sprintf(CS var_buffer, "%s", *(BOOL *)(val) ? "yes" : "no"); /* bool */ - return var_buffer; + sprintf(CS var_buffer, "%s", *(BOOL *)(val) ? "yes" : "no"); /* bool */ + return var_buffer; case vtype_stringptr: /* Pointer to string */ - s = *((uschar **)(val)); - return (s == NULL)? US"" : s; + return (s = *((uschar **)(val))) ? s : US""; case vtype_pid: - sprintf(CS var_buffer, "%d", (int)getpid()); /* pid */ - return var_buffer; + sprintf(CS var_buffer, "%d", (int)getpid()); /* pid */ + return var_buffer; case vtype_load_avg: - sprintf(CS var_buffer, "%d", OS_GETLOADAVG()); /* load_average */ - return var_buffer; + sprintf(CS var_buffer, "%d", OS_GETLOADAVG()); /* load_average */ + return var_buffer; case vtype_host_lookup: /* Lookup if not done so */ - if (sender_host_name == NULL && sender_host_address != NULL && - !host_lookup_failed && host_name_lookup() == OK) - host_build_sender_fullhost(); - return (sender_host_name == NULL)? US"" : sender_host_name; + if (sender_host_name == NULL && sender_host_address != NULL && + !host_lookup_failed && host_name_lookup() == OK) + host_build_sender_fullhost(); + return (sender_host_name == NULL)? US"" : sender_host_name; case vtype_localpart: /* Get local part from address */ - s = *((uschar **)(val)); - if (s == NULL) return US""; - 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 " SIZE_T_FMT - " in string expansion", sizeof(var_buffer)); - Ustrncpy(var_buffer, s, domain - s); - var_buffer[domain - s] = 0; - return var_buffer; + s = *((uschar **)(val)); + if (s == NULL) return US""; + 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 " SIZE_T_FMT + " in string expansion", sizeof(var_buffer)); + Ustrncpy(var_buffer, s, domain - s); + var_buffer[domain - s] = 0; + return var_buffer; case vtype_domain: /* Get domain from address */ - s = *((uschar **)(val)); - if (s == NULL) return US""; - domain = Ustrrchr(s, '@'); - return (domain == NULL)? US"" : domain + 1; + s = *((uschar **)(val)); + if (s == NULL) return US""; + domain = Ustrrchr(s, '@'); + return (domain == NULL)? US"" : domain + 1; case vtype_msgheaders: - return find_header(NULL, exists_only, newsize, FALSE, NULL); + return find_header(NULL, exists_only, newsize, FALSE, NULL); case vtype_msgheaders_raw: - return find_header(NULL, exists_only, newsize, TRUE, NULL); + return find_header(NULL, exists_only, newsize, TRUE, NULL); case vtype_msgbody: /* Pointer to msgbody string */ case vtype_msgbody_end: /* Ditto, the end of the msg */ - ss = (uschar **)(val); - if (*ss == NULL && deliver_datafile >= 0) /* Read body when needed */ - { - uschar *body; - off_t start_offset = SPOOL_DATA_START_OFFSET; - int len = message_body_visible; - if (len > message_size) len = message_size; - *ss = body = store_malloc(len+1); - body[0] = 0; - if (vp->type == vtype_msgbody_end) - { - struct stat statbuf; - if (fstat(deliver_datafile, &statbuf) == 0) - { - start_offset = statbuf.st_size - len; - if (start_offset < SPOOL_DATA_START_OFFSET) - start_offset = SPOOL_DATA_START_OFFSET; - } - } - lseek(deliver_datafile, start_offset, SEEK_SET); - len = read(deliver_datafile, body, len); - if (len > 0) + ss = (uschar **)(val); + if (*ss == NULL && deliver_datafile >= 0) /* Read body when needed */ { - body[len] = 0; - if (message_body_newlines) /* Separate loops for efficiency */ + uschar *body; + off_t start_offset = SPOOL_DATA_START_OFFSET; + int len = message_body_visible; + if (len > message_size) len = message_size; + *ss = body = store_malloc(len+1); + body[0] = 0; + if (vp->type == vtype_msgbody_end) { - while (len > 0) - { if (body[--len] == 0) body[len] = ' '; } + struct stat statbuf; + if (fstat(deliver_datafile, &statbuf) == 0) + { + start_offset = statbuf.st_size - len; + if (start_offset < SPOOL_DATA_START_OFFSET) + start_offset = SPOOL_DATA_START_OFFSET; + } } - else + if (lseek(deliver_datafile, start_offset, SEEK_SET) < 0) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "deliver_datafile lseek: %s", + strerror(errno)); + len = read(deliver_datafile, body, len); + if (len > 0) { - while (len > 0) - { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; } + body[len] = 0; + if (message_body_newlines) /* Separate loops for efficiency */ + while (len > 0) + { if (body[--len] == 0) body[len] = ' '; } + else + while (len > 0) + { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; } } } - } - return (*ss == NULL)? US"" : *ss; + return (*ss == NULL)? US"" : *ss; case vtype_todbsdin: /* BSD inbox time of day */ - return tod_stamp(tod_bsdin); + return tod_stamp(tod_bsdin); case vtype_tode: /* Unix epoch time of day */ - return tod_stamp(tod_epoch); + return tod_stamp(tod_epoch); case vtype_todel: /* Unix epoch/usec time of day */ - return tod_stamp(tod_epoch_l); + return tod_stamp(tod_epoch_l); case vtype_todf: /* Full time of day */ - return tod_stamp(tod_full); + return tod_stamp(tod_full); case vtype_todl: /* Log format time of day */ - return tod_stamp(tod_log_bare); /* (without timezone) */ + return tod_stamp(tod_log_bare); /* (without timezone) */ case vtype_todzone: /* Time zone offset only */ - return tod_stamp(tod_zone); + return tod_stamp(tod_zone); case vtype_todzulu: /* Zulu time */ - return tod_stamp(tod_zulu); + return tod_stamp(tod_zulu); case vtype_todlf: /* Log file datestamp tod */ - return tod_stamp(tod_log_datestamp_daily); + return tod_stamp(tod_log_datestamp_daily); case vtype_reply: /* Get reply address */ - s = find_header(US"reply-to:", exists_only, newsize, TRUE, - headers_charset); - if (s != NULL) while (isspace(*s)) s++; - if (s == NULL || *s == 0) - { - *newsize = 0; /* For the *s==0 case */ - s = find_header(US"from:", exists_only, newsize, TRUE, headers_charset); - } - if (s != NULL) - { - uschar *t; - while (isspace(*s)) s++; - for (t = s; *t != 0; t++) if (*t == '\n') *t = ' '; - while (t > s && isspace(t[-1])) t--; - *t = 0; - } - return (s == NULL)? US"" : s; + s = find_header(US"reply-to:", exists_only, newsize, TRUE, + headers_charset); + if (s != NULL) while (isspace(*s)) s++; + if (s == NULL || *s == 0) + { + *newsize = 0; /* For the *s==0 case */ + s = find_header(US"from:", exists_only, newsize, TRUE, headers_charset); + } + if (s != NULL) + { + uschar *t; + while (isspace(*s)) s++; + for (t = s; *t != 0; t++) if (*t == '\n') *t = ' '; + while (t > s && isspace(t[-1])) t--; + *t = 0; + } + return (s == NULL)? US"" : s; case vtype_string_func: { @@ -1893,12 +1937,12 @@ switch (vp->type) return var_buffer; case vtype_cert: - return *(void **)val ? US"" : US""; + return *(void **)val ? US"" : US""; - #ifndef DISABLE_DKIM +#ifndef DISABLE_DKIM case vtype_dkim: - return dkim_exim_expand_query((int)(long)val); - #endif + return dkim_exim_expand_query((int)(long)val); +#endif } @@ -1957,12 +2001,17 @@ for (i = 0; i < n; i++) { if (*s != '{') { - if (i < m) return 1; + if (i < m) + { + expand_string_message = string_sprintf("Not enough arguments for '%s' " + "(min is %d)", name, m); + return 1; + } sub[i] = NULL; break; } - sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok); - if (sub[i] == NULL) return 3; + if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok))) + return 3; if (*s++ != '}') return 1; while (isspace(*s)) s++; } @@ -1970,10 +2019,11 @@ if (check_end && *s++ != '}') { if (s[-1] == '{') { - expand_string_message = string_sprintf("Too many arguments for \"%s\" " + expand_string_message = string_sprintf("Too many arguments for '%s' " "(max is %d)", name, n); return 2; } + expand_string_message = string_sprintf("missing '}' after '%s'", name); return 1; } @@ -2016,7 +2066,7 @@ 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 + DISCARD access is (apparently) granted by a DISCARD verb FAIL access is denied FAIL_DROP access is denied; drop the connection DEFER can't tell at the moment @@ -2031,7 +2081,7 @@ int ret; uschar * dummy_logmsg; extern int acl_where; -if(--nsub > sizeof(acl_arg)/sizeof(*acl_arg)) nsub = sizeof(acl_arg)/sizeof(*acl_arg); +if(--nsub > nelem(acl_arg)) nsub = nelem(acl_arg); for (i = 0; i < nsub && sub[i+1]; i++) { uschar * tmp = acl_arg[i]; @@ -2133,7 +2183,7 @@ if (name[0] == 0) /* Find which condition we are dealing with, and switch on it */ -cond_type = chop_match(name, cond_table, sizeof(cond_table)/sizeof(uschar *)); +cond_type = chop_match(name, cond_table, nelem(cond_table)); switch(cond_type) { /* def: tests for a non-empty variable, or for the existence of a header. If @@ -2322,7 +2372,7 @@ switch(cond_type) while (isspace(*s)) s++; if (*s++ != '{') goto COND_FAILED_CURLY_START; /*}*/ - switch(read_subs(sub, sizeof(sub)/sizeof(*sub), 1, + switch(read_subs(sub, nelem(sub), 1, &s, yield == NULL, TRUE, US"acl", resetok)) { case 1: expand_string_message = US"too few arguments or bracketing " @@ -2331,8 +2381,8 @@ switch(cond_type) case 3: return NULL; } - *resetok = FALSE; - if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg)) + *resetok = FALSE; /* eval_acl() might allocate; do not reclaim */ + if (yield != NULL) switch(eval_acl(sub, nelem(sub), &user_msg)) { case OK: cond = TRUE; @@ -2340,7 +2390,7 @@ switch(cond_type) lookup_value = NULL; if (user_msg) { - lookup_value = string_cat(NULL, &size, &ptr, user_msg, Ustrlen(user_msg)); + lookup_value = string_cat(NULL, &size, &ptr, user_msg); lookup_value[ptr] = '\0'; } *yield = cond == testfor; @@ -2348,6 +2398,7 @@ switch(cond_type) case DEFER: expand_string_forcedfail = TRUE; + /*FALLTHROUGH*/ default: expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]); return NULL; @@ -2364,29 +2415,32 @@ switch(cond_type) 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; /* }-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"; - case 2: - case 3: return NULL; - } - if (sub[2] == NULL) sub[3] = NULL; /* realm if no service */ - if (yield != NULL) +#ifndef CYRUS_SASLAUTHD_SOCKET + goto COND_FAILED_NOT_COMPILED; +#else { - int rc; - rc = auth_call_saslauthd(sub[0], sub[1], sub[2], sub[3], - &expand_string_message); - if (rc == ERROR || rc == DEFER) return NULL; - *yield = (rc == OK) == testfor; + uschar *sub[4]; + while (isspace(*s)) s++; + if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */ + switch(read_subs(sub, nelem(sub), 2, &s, yield == NULL, TRUE, US"saslauthd", + resetok)) + { + case 1: expand_string_message = US"too few arguments or bracketing " + "error for saslauthd"; + case 2: + case 3: return NULL; + } + if (sub[2] == NULL) sub[3] = NULL; /* realm if no service */ + if (yield != NULL) + { + int rc = auth_call_saslauthd(sub[0], sub[1], sub[2], sub[3], + &expand_string_message); + if (rc == ERROR || rc == DEFER) return NULL; + *yield = (rc == OK) == testfor; + } + return s; } - return s; - #endif /* CYRUS_SASLAUTHD_SOCKET */ +#endif /* CYRUS_SASLAUTHD_SOCKET */ /* symbolic operators for numeric and string comparison, and a number of @@ -2462,7 +2516,6 @@ switch(cond_type) checking for them individually. */ if (!isalpha(name[0]) && yield != NULL) - { if (sub[i][0] == 0) { num[i] = 0; @@ -2474,7 +2527,6 @@ switch(cond_type) num[i] = expanded_string_integer(sub[i], FALSE); if (expand_string_message != NULL) return NULL; } - } } /* Result not required */ @@ -2642,7 +2694,7 @@ switch(cond_type) uschar digest[16]; md5_start(&base); - md5_end(&base, (uschar *)sub[0], Ustrlen(sub[0]), digest); + md5_end(&base, sub[0], Ustrlen(sub[0]), digest); /* If the length that we are comparing against is 24, the MD5 digest is expressed as a base64 string. This is the way LDAP does it. However, @@ -2651,7 +2703,7 @@ switch(cond_type) if (sublen == 24) { - uschar *coded = auth_b64encode((uschar *)digest, 16); + uschar *coded = b64encode(digest, 16); DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n" " subject=%s\n crypted=%s\n", coded, sub[1]+5); tempcond = (Ustrcmp(coded, sub[1]+5) == 0); @@ -2677,11 +2729,11 @@ switch(cond_type) else if (strncmpic(sub[1], US"{sha1}", 6) == 0) { int sublen = Ustrlen(sub[1]+6); - sha1 base; + hctx h; uschar digest[20]; - sha1_start(&base); - sha1_end(&base, (uschar *)sub[0], Ustrlen(sub[0]), digest); + sha1_start(&h); + sha1_end(&h, sub[0], Ustrlen(sub[0]), digest); /* If the length that we are comparing against is 28, assume the SHA1 digest is expressed as a base64 string. If the length is 40, assume a @@ -2689,7 +2741,7 @@ switch(cond_type) if (sublen == 28) { - uschar *coded = auth_b64encode((uschar *)digest, 20); + uschar *coded = b64encode(digest, 20); DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n" " subject=%s\n crypted=%s\n", coded, sub[1]+6); tempcond = (Ustrcmp(coded, sub[1]+6) == 0); @@ -2746,7 +2798,7 @@ switch(cond_type) #define XSTR(s) STR(s) DEBUG(D_auth) debug_printf("crypteq: using %s()\n" " subject=%s\n crypted=%s\n", - (which == 0)? XSTR(DEFAULT_CRYPT) : (which == 1)? "crypt" : "crypt16", + which == 0 ? XSTR(DEFAULT_CRYPT) : which == 1 ? "crypt" : "crypt16", coded, sub[1]); #undef STR #undef XSTR @@ -2755,8 +2807,16 @@ switch(cond_type) salt), force failure. Otherwise we get false positives: with an empty string the yield of crypt() is an empty string! */ - tempcond = (Ustrlen(sub[1]) < 2)? FALSE : - (Ustrcmp(coded, sub[1]) == 0); + if (coded) + tempcond = Ustrlen(sub[1]) < 2 ? FALSE : Ustrcmp(coded, sub[1]) == 0; + else if (errno == EINVAL) + tempcond = FALSE; + else + { + expand_string_message = string_sprintf("crypt error: %s\n", + US strerror(errno)); + return NULL; + } } break; #endif /* SUPPORT_CRYPTEQ */ @@ -3102,7 +3162,8 @@ Arguments: yieldptr points to the output string pointer 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 + type "lookup", "if", "extract", "run", "env", "listextract" or + "certextract" for error message resetok if not NULL, pointer to flag - write FALSE if unsafe to reset the store. @@ -3118,6 +3179,7 @@ process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, const uschar **sptr, int rc = 0; const uschar *s = *sptr; /* Local value */ uschar *sub1, *sub2; +const uschar * errwhere; /* If there are no following strings, we substitute the contents of $value for lookups and for extractions in the success case. For the ${if item, the string @@ -3127,24 +3189,28 @@ items. */ while (isspace(*s)) s++; if (*s == '}') { - if (type[0] == 'i') - { - if (yes) *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, US"true", 4); - } - else - { - if (yes && lookup_value != NULL) - *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value, - Ustrlen(lookup_value)); - lookup_value = save_lookup; - } + if (!skipping) + if (type[0] == 'i') + { + if (yes) *yieldptr = string_catn(*yieldptr, sizeptr, ptrptr, US"true", 4); + } + else + { + if (yes && lookup_value) + *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value); + lookup_value = save_lookup; + } s++; goto RETURN; } /* The first following string must be braced. */ -if (*s++ != '{') goto FAILED_CURLY; +if (*s++ != '{') + { + errwhere = US"'yes' part did not start with '{'"; + goto FAILED_CURLY; + } /* Expand the first substring. Forced failures are noticed only if we actually want this string. Set skipping in the call in the fail case (this will always @@ -3153,17 +3219,22 @@ be the case if we were already skipping). */ 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; +if (*s++ != '}') + { + errwhere = US"'yes' part did not end with '}'"; + goto FAILED_CURLY; + } /* If we want the first string, add it to the output */ if (yes) - *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub1, Ustrlen(sub1)); + *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub1); -/* If this is called from a lookup or an extract, we want to restore $value to -what it was at the start of the item, so that it has this value during the -second string expansion. For the call from "if" or "run" to this function, -save_lookup is set to lookup_value, so that this statement does nothing. */ +/* If this is called from a lookup/env or a (cert)extract, we want to restore +$value to what it was at the start of the item, so that it has this value +during the second string expansion. For the call from "if" or "run" to this +function, save_lookup is set to lookup_value, so that this statement does +nothing. */ lookup_value = save_lookup; @@ -3178,12 +3249,16 @@ if (*s == '{') 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; + if (*s++ != '}') + { + errwhere = US"'no' part did not start with '{'"; + goto FAILED_CURLY; + } /* If we want the second string, add it to the output */ if (!yes) - *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub2, Ustrlen(sub2)); + *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, sub2); } /* If there is no second string, but the word "fail" is present when the use of @@ -3201,7 +3276,11 @@ else if (*s != '}') if (!yes && !skipping) { while (isspace(*s)) s++; - if (*s++ != '}') goto FAILED_CURLY; + if (*s++ != '}') + { + errwhere = US"did not close with '}' after forcedfail"; + goto FAILED_CURLY; + } expand_string_message = string_sprintf("\"%s\" failed and \"fail\" requested", type); expand_string_forcedfail = TRUE; @@ -3219,23 +3298,30 @@ else if (*s != '}') /* All we have to do now is to check on the final closing brace. */ while (isspace(*s)) s++; -if (*s++ == '}') goto RETURN; - -/* Get here if there is a bracketing failure */ - -FAILED_CURLY: -rc++; - -/* Get here for other failures */ - -FAILED: -rc++; +if (*s++ != '}') + { + errwhere = US"did not close with '}'"; + goto FAILED_CURLY; + } -/* Update the input pointer value before returning */ RETURN: +/* Update the input pointer value before returning */ *sptr = s; return rc; + +FAILED_CURLY: + /* Get here if there is a bracketing failure */ + expand_string_message = string_sprintf( + "curly-bracket problem in conditional yes/no parsing: %s\n" + " remaining string is '%s'", errwhere, --s); + rc = 2; + goto RETURN; + +FAILED: + /* Get here for other failures */ + rc = 1; + goto RETURN; } @@ -3261,7 +3347,7 @@ chash_start(int type, void *base) if (type == HMAC_MD5) md5_start((md5 *)base); else - sha1_start((sha1 *)base); + sha1_start((hctx *)base); } static void @@ -3270,7 +3356,7 @@ chash_mid(int type, void *base, uschar *string) if (type == HMAC_MD5) md5_mid((md5 *)base, string); else - sha1_mid((sha1 *)base, string); + sha1_mid((hctx *)base, string); } static void @@ -3279,7 +3365,7 @@ chash_end(int type, void *base, uschar *string, int length, uschar *digest) if (type == HMAC_MD5) md5_end((md5 *)base, string, length, digest); else - sha1_end((sha1 *)base, string, length, digest); + sha1_end((hctx *)base, string, length, digest); } @@ -3338,8 +3424,7 @@ prvs_hmac_sha1(uschar *address, uschar *key, uschar *key_num, uschar *daystamp) { uschar *hash_source, *p; int size = 0,offset = 0,i; -sha1 sha1_base; -void *use_base = &sha1_base; +hctx h; uschar innerhash[20]; uschar finalhash[20]; uschar innerkey[64]; @@ -3352,9 +3437,9 @@ if (key_num == NULL) if (Ustrlen(key) > 64) return NULL; -hash_source = string_cat(NULL,&size,&offset,key_num,1); -string_cat(hash_source,&size,&offset,daystamp,3); -string_cat(hash_source,&size,&offset,address,Ustrlen(address)); +hash_source = string_catn(NULL, &size, &offset, key_num, 1); +hash_source = string_catn(hash_source, &size, &offset, daystamp, 3); +hash_source = string_cat(hash_source, &size, &offset, address); hash_source[offset] = '\0'; DEBUG(D_expand) debug_printf("prvs: hash source is '%s'\n", hash_source); @@ -3368,13 +3453,13 @@ for (i = 0; i < Ustrlen(key); i++) outerkey[i] ^= key[i]; } -chash_start(HMAC_SHA1, use_base); -chash_mid(HMAC_SHA1, use_base, innerkey); -chash_end(HMAC_SHA1, use_base, hash_source, offset, innerhash); +chash_start(HMAC_SHA1, &h); +chash_mid(HMAC_SHA1, &h, innerkey); +chash_end(HMAC_SHA1, &h, hash_source, offset, innerhash); -chash_start(HMAC_SHA1, use_base); -chash_mid(HMAC_SHA1, use_base, outerkey); -chash_end(HMAC_SHA1, use_base, innerhash, 20, finalhash); +chash_start(HMAC_SHA1, &h); +chash_mid(HMAC_SHA1, &h, outerkey); +chash_end(HMAC_SHA1, &h, innerhash, 20, finalhash); p = finalhash_hex; for (i = 0; i < 3; i++) @@ -3394,9 +3479,9 @@ return finalhash_hex; * Join a file onto the output string * *************************************************/ -/* This is used for readfile and after a run expansion. It joins the contents -of a file onto the output string, globally replacing newlines with a given -string (optionally). The file is closed at the end. +/* This is used for readfile/readsock and after a run expansion. +It joins the contents of a file onto the output string, globally replacing +newlines with a given string (optionally). Arguments: f the FILE @@ -3411,21 +3496,18 @@ Returns: new value of string pointer static uschar * cat_file(FILE *f, uschar *yield, int *sizep, int *ptrp, uschar *eol) { -int eollen; uschar buffer[1024]; -eollen = (eol == NULL)? 0 : Ustrlen(eol); - -while (Ufgets(buffer, sizeof(buffer), f) != NULL) +while (Ufgets(buffer, sizeof(buffer), f)) { int len = Ustrlen(buffer); - if (eol != NULL && buffer[len-1] == '\n') len--; - yield = string_cat(yield, sizep, ptrp, buffer, len); - if (buffer[len] != 0) - yield = string_cat(yield, sizep, ptrp, eol, eollen); + if (eol && buffer[len-1] == '\n') len--; + yield = string_catn(yield, sizep, ptrp, buffer, len); + if (eol && buffer[len]) + yield = string_cat(yield, sizep, ptrp, eol); } -if (yield != NULL) yield[*ptrp] = 0; +if (yield) yield[*ptrp] = 0; return yield; } @@ -3599,13 +3681,20 @@ eval_op_sum(uschar **sptr, BOOL decimal, uschar **error) { uschar *s = *sptr; int_eximarith_t x = eval_op_mult(&s, decimal, error); -if (*error == NULL) +if (!*error) { while (*s == '+' || *s == '-') { int op = *s++; int_eximarith_t y = eval_op_mult(&s, decimal, error); - if (*error != NULL) break; + if (*error) break; + if ( (x >= EXIM_ARITH_MAX/2 && x >= EXIM_ARITH_MAX/2) + || (x <= -(EXIM_ARITH_MAX/2) && y <= -(EXIM_ARITH_MAX/2))) + { /* over-conservative check */ + *error = op == '+' + ? US"overflow in sum" : US"overflow in difference"; + break; + } if (op == '+') x += y; else x -= y; } } @@ -3769,13 +3858,16 @@ expand_string_internal(const uschar *string, BOOL ket_ends, const uschar **left, { int ptr = 0; int size = Ustrlen(string)+ 64; -int item_type; uschar *yield = store_get(size); +int item_type; const uschar *s = string; uschar *save_expand_nstring[EXPAND_MAXN+1]; int save_expand_nlength[EXPAND_MAXN+1]; BOOL resetok = TRUE; +DEBUG(D_expand) + debug_printf("%s: %s\n", skipping ? " scanning" : "considering", string); + expand_string_forcedfail = FALSE; expand_string_message = US""; @@ -3801,7 +3893,7 @@ while (*s != 0) { const uschar * t = s + 2; for (s = t; *s != 0; s++) if (*s == '\\' && s[1] == 'N') break; - yield = string_cat(yield, &size, &ptr, t, s - t); + yield = string_catn(yield, &size, &ptr, t, s - t); if (*s != 0) s += 2; } @@ -3810,7 +3902,7 @@ while (*s != 0) uschar ch[1]; ch[0] = string_interpret_escape(&s); s++; - yield = string_cat(yield, &size, &ptr, ch, 1); + yield = string_catn(yield, &size, &ptr, ch, 1); } continue; @@ -3825,7 +3917,7 @@ while (*s != 0) if (*s != '$' || !honour_dollar) { - yield = string_cat(yield, &size, &ptr, s++, 1); + yield = string_catn(yield, &size, &ptr, s++, 1); continue; } @@ -3882,16 +3974,12 @@ while (*s != 0) /* Variable */ - else + else if (!(value = find_variable(name, FALSE, skipping, &newsize))) { - value = find_variable(name, FALSE, skipping, &newsize); - if (value == NULL) - { - expand_string_message = - string_sprintf("unknown variable name \"%s\"", name); - check_variable_error_message(name); - goto EXPAND_FAILED; - } + expand_string_message = + string_sprintf("unknown variable name \"%s\"", name); + check_variable_error_message(name); + goto EXPAND_FAILED; } /* If the data is known to be in a new buffer, newsize will be set to the @@ -3907,7 +3995,7 @@ while (*s != 0) size = newsize; ptr = len; } - else yield = string_cat(yield, &size, &ptr, value, len); + else yield = string_catn(yield, &size, &ptr, value, len); continue; } @@ -3917,7 +4005,7 @@ while (*s != 0) int n; s = read_cnumber(&n, s); if (n >= 0 && n <= expand_nmax) - yield = string_cat(yield, &size, &ptr, expand_nstring[n], + yield = string_catn(yield, &size, &ptr, expand_nstring[n], expand_nlength[n]); continue; } @@ -3943,7 +4031,7 @@ while (*s != 0) goto EXPAND_FAILED; } if (n >= 0 && n <= expand_nmax) - yield = string_cat(yield, &size, &ptr, expand_nstring[n], + yield = string_catn(yield, &size, &ptr, expand_nstring[n], expand_nlength[n]); continue; } @@ -3959,7 +4047,7 @@ while (*s != 0) OK. */ s = read_name(name, sizeof(name), s, US"_-"); - item_type = chop_match(name, item_table, sizeof(item_table)/sizeof(uschar *)); + item_type = chop_match(name, item_table, nelem(item_table)); switch(item_type) { @@ -3978,7 +4066,8 @@ while (*s != 0) 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)) + switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, US"acl", + &resetok)) { case 1: goto EXPAND_FAILED_CURLY; case 2: @@ -3987,18 +4076,19 @@ while (*s != 0) if (skipping) continue; resetok = FALSE; - switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg)) + switch(eval_acl(sub, nelem(sub), &user_msg)) { case OK: case FAIL: DEBUG(D_expand) debug_printf("acl expansion yield: %s\n", user_msg); if (user_msg) - yield = string_cat(yield, &size, &ptr, user_msg, Ustrlen(user_msg)); + yield = string_cat(yield, &size, &ptr, user_msg); continue; case DEFER: expand_string_forcedfail = TRUE; + /*FALLTHROUGH*/ default: expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]); goto EXPAND_FAILED; @@ -4022,8 +4112,9 @@ while (*s != 0) if (next_s == NULL) goto EXPAND_FAILED; /* message already set */ DEBUG(D_expand) - debug_printf("condition: %.*s\n result: %s\n", (int)(next_s - s), s, - cond? "true" : "false"); + debug_printf(" condition: %.*s\n result: %s\n", + (int)(next_s - s), s, + cond ? "true" : "false"); s = next_s; @@ -4053,6 +4144,43 @@ while (*s != 0) continue; } +#ifdef SUPPORT_I18N + case EITEM_IMAPFOLDER: + { /* ${imapfolder {name}{sep]{specials}} */ + uschar *sub_arg[3]; + uschar *encoded; + + switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, skipping, TRUE, name, + &resetok)) + { + case 1: goto EXPAND_FAILED_CURLY; + case 2: + case 3: goto EXPAND_FAILED; + } + + if (sub_arg[1] == NULL) /* One argument */ + { + sub_arg[1] = US"/"; /* default separator */ + sub_arg[2] = NULL; + } + else if (Ustrlen(sub_arg[1]) != 1) + { + expand_string_message = + string_sprintf( + "IMAP folder separator must be one character, found \"%s\"", + sub_arg[1]); + goto EXPAND_FAILED; + } + + if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset, + sub_arg[1][0], sub_arg[2], &expand_string_message))) + goto EXPAND_FAILED; + if (!skipping) + yield = string_cat(yield, &size, &ptr, encoded); + continue; + } +#endif + /* Handle database lookups unless locked out. If "skipping" is TRUE, we are expanding an internal string that isn't actually going to be used. All we need to do is check the syntax, so don't do a lookup at all. Preserve the @@ -4084,8 +4212,12 @@ while (*s != 0) if (*s == '{') /*}*/ { key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); - if (key == NULL) goto EXPAND_FAILED; /*{*/ - if (*s++ != '}') goto EXPAND_FAILED_CURLY; + if (!key) goto EXPAND_FAILED; /*{{*/ + if (*s++ != '}') + { + expand_string_message = US"missing '}' after lookup key"; + goto EXPAND_FAILED_CURLY; + } while (isspace(*s)) s++; } else key = NULL; @@ -4148,10 +4280,18 @@ while (*s != 0) queries that also require a file name (e.g. sqlite), the file name comes first. */ - if (*s != '{') goto EXPAND_FAILED_CURLY; + if (*s != '{') + { + expand_string_message = US"missing '{' for lookup file-or-query arg"; + goto EXPAND_FAILED_CURLY; + } filename = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); if (filename == NULL) goto EXPAND_FAILED; - if (*s++ != '}') goto EXPAND_FAILED_CURLY; + if (*s++ != '}') + { + expand_string_message = US"missing '}' closing lookup file-or-query arg"; + goto EXPAND_FAILED_CURLY; + } while (isspace(*s)) s++; /* If this isn't a single-key+file lookup, re-arrange the variables @@ -4159,15 +4299,13 @@ while (*s != 0) there is just a "key", and no file name. For the special query-style + file types, the query (i.e. "key") starts with a file name. */ - if (key == NULL) + if (!key) { while (isspace(*filename)) filename++; key = filename; if (mac_islookup(stype, lookup_querystyle)) - { filename = NULL; - } else { if (*filename != '/') @@ -4377,14 +4515,14 @@ while (*s != 0) /* Now separate the domain from the local part */ *domain++ = '\0'; - yield = string_cat(yield,&size,&ptr,US"prvs=",5); - 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)); + yield = string_catn(yield, &size, &ptr, US"prvs=", 5); + yield = string_catn(yield, &size, &ptr, sub_arg[2] ? sub_arg[2] : US"0", 1); + yield = string_catn(yield, &size, &ptr, prvs_daystamp(7), 3); + yield = string_catn(yield, &size, &ptr, p, 6); + yield = string_catn(yield, &size, &ptr, US"=", 1); + yield = string_cat (yield, &size, &ptr, sub_arg[0]); + yield = string_catn(yield, &size, &ptr, US"@", 1); + yield = string_cat (yield, &size, &ptr, domain); continue; } @@ -4438,9 +4576,9 @@ while (*s != 0) DEBUG(D_expand) debug_printf("prvscheck domain: %s\n", domain); /* Set up expansion variables */ - prvscheck_address = string_cat(NULL, &mysize, &myptr, local_part, Ustrlen(local_part)); - string_cat(prvscheck_address,&mysize,&myptr,US"@",1); - string_cat(prvscheck_address,&mysize,&myptr,domain,Ustrlen(domain)); + prvscheck_address = string_cat (NULL, &mysize, &myptr, local_part); + prvscheck_address = string_catn(prvscheck_address, &mysize, &myptr, US"@", 1); + prvscheck_address = string_cat (prvscheck_address, &mysize, &myptr, domain); prvscheck_address[myptr] = '\0'; prvscheck_keynum = string_copy(key_num); @@ -4506,10 +4644,8 @@ while (*s != 0) case 3: goto EXPAND_FAILED; } - if (sub_arg[0] == NULL || *sub_arg[0] == '\0') - yield = string_cat(yield,&size,&ptr,prvscheck_address,Ustrlen(prvscheck_address)); - else - yield = string_cat(yield,&size,&ptr,sub_arg[0],Ustrlen(sub_arg[0])); + yield = string_cat(yield, &size, &ptr, + !sub_arg[0] || !*sub_arg[0] ? prvscheck_address : sub_arg[0]); /* Reset the "internal" variables afterwards, because they are in dynamic store that will be reclaimed if the expansion succeeded. */ @@ -4756,10 +4892,20 @@ while (*s != 0) { if (expand_string_internal(s+1, TRUE, &s, TRUE, TRUE, &resetok) == NULL) goto EXPAND_FAILED; - if (*s++ != '}') goto EXPAND_FAILED_CURLY; + if (*s++ != '}') + { + expand_string_message = US"missing '}' closing failstring for readsocket"; + goto EXPAND_FAILED_CURLY; + } while (isspace(*s)) s++; } - if (*s++ != '}') goto EXPAND_FAILED_CURLY; + + readsock_done: + if (*s++ != '}') + { + expand_string_message = US"missing '}' closing readsocket"; + goto EXPAND_FAILED_CURLY; + } continue; /* Come here on failure to create socket, connect socket, write to the @@ -4769,13 +4915,16 @@ 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, TRUE, &resetok); - if (arg == NULL) goto EXPAND_FAILED; - yield = string_cat(yield, &size, &ptr, arg, Ustrlen(arg)); - if (*s++ != '}') goto EXPAND_FAILED_CURLY; + if (!(arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE, &resetok))) + goto EXPAND_FAILED; + yield = string_cat(yield, &size, &ptr, arg); + if (*s++ != '}') + { + expand_string_message = US"missing '}' closing failstring for readsocket"; + goto EXPAND_FAILED_CURLY; + } while (isspace(*s)) s++; - if (*s++ != '}') goto EXPAND_FAILED_CURLY; - continue; + goto readsock_done; } /* Handle "run" to execute a program. */ @@ -4787,8 +4936,7 @@ while (*s != 0) const uschar **argv; pid_t pid; int fd_in, fd_out; - int lsize = 0; - int lptr = 0; + int lsize = 0, lptr = 0; if ((expand_forbid & RDO_RUN) != 0) { @@ -4797,16 +4945,25 @@ while (*s != 0) } while (isspace(*s)) s++; - if (*s != '{') goto EXPAND_FAILED_CURLY; + if (*s != '{') + { + expand_string_message = US"missing '{' for command arg of run"; + goto EXPAND_FAILED_CURLY; + } 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; + if (*s++ != '}') + { + expand_string_message = US"missing '}' closing command arg of run"; + goto EXPAND_FAILED_CURLY; + } if (skipping) /* Just pretend it worked when we're skipping */ - { + { runrc = 0; - } + lookup_value = NULL; + } else { if (!transport_set_up_command(&argv, /* anchor for arg list */ @@ -4816,15 +4973,11 @@ while (*s != 0) NULL, /* no transporting address */ US"${run} expansion", /* for error messages */ &expand_string_message)) /* where to put error message */ - { goto EXPAND_FAILED; - } /* Create the child process, making it a group leader. */ - pid = child_open(USS argv, NULL, 0077, &fd_in, &fd_out, TRUE); - - if (pid < 0) + if ((pid = child_open(USS argv, NULL, 0077, &fd_in, &fd_out, TRUE)) < 0) { expand_string_message = string_sprintf("couldn't create child process: %s", strerror(errno)); @@ -4837,12 +4990,14 @@ while (*s != 0) /* 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. */ + the OS pipe buffer limit, we don't block forever. Remember to not release + memory just allocated for $value. */ + resetok = FALSE; f = fdopen(fd_out, "rb"); sigalrm_seen = FALSE; alarm(60); - lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL); + lookup_value = cat_file(f, NULL, &lsize, &lptr, NULL); alarm(0); (void)fclose(f); @@ -4850,9 +5005,9 @@ while (*s != 0) return code for serious disasters. Simple non-zero returns are passed on. */ - if (sigalrm_seen == TRUE || (runrc = child_close(pid, 30)) < 0) + if (sigalrm_seen || (runrc = child_close(pid, 30)) < 0) { - if (sigalrm_seen == TRUE || runrc == -256) + if (sigalrm_seen || runrc == -256) { expand_string_message = string_sprintf("command timed out"); killpg(pid, SIGKILL); /* Kill the whole process group */ @@ -4905,7 +5060,7 @@ while (*s != 0) case 3: goto EXPAND_FAILED; } - yield = string_cat(yield, &size, &ptr, sub[0], Ustrlen(sub[0])); + yield = string_cat(yield, &size, &ptr, sub[0]); o2m = Ustrlen(sub[2]) - 1; if (o2m >= 0) for (; oldptr < ptr; oldptr++) @@ -4982,7 +5137,7 @@ while (*s != 0) extract_substr(sub[2], val[0], val[1], &len); if (ret == NULL) goto EXPAND_FAILED; - yield = string_cat(yield, &size, &ptr, ret, len); + yield = string_catn(yield, &size, &ptr, ret, len); continue; } @@ -5000,7 +5155,7 @@ while (*s != 0) { uschar *sub[3]; md5 md5_base; - sha1 sha1_base; + hctx sha1_ctx; void *use_base; int type, i; int hashlen; /* Number of octets for the hash algorithm's output */ @@ -5022,79 +5177,81 @@ while (*s != 0) case 3: goto EXPAND_FAILED; } - if (Ustrcmp(sub[0], "md5") == 0) - { - type = HMAC_MD5; - use_base = &md5_base; - hashlen = 16; - hashblocklen = 64; - } - else if (Ustrcmp(sub[0], "sha1") == 0) - { - type = HMAC_SHA1; - use_base = &sha1_base; - hashlen = 20; - hashblocklen = 64; - } - else - { - expand_string_message = - string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]); - goto EXPAND_FAILED; - } + if (!skipping) + { + if (Ustrcmp(sub[0], "md5") == 0) + { + type = HMAC_MD5; + use_base = &md5_base; + hashlen = 16; + hashblocklen = 64; + } + else if (Ustrcmp(sub[0], "sha1") == 0) + { + type = HMAC_SHA1; + use_base = &sha1_ctx; + hashlen = 20; + hashblocklen = 64; + } + else + { + expand_string_message = + string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]); + goto EXPAND_FAILED; + } - keyptr = sub[1]; - keylen = Ustrlen(keyptr); + keyptr = sub[1]; + keylen = Ustrlen(keyptr); - /* If the key is longer than the hash block length, then hash the key - first */ + /* If the key is longer than the hash block length, then hash the key + first */ - if (keylen > hashblocklen) - { - chash_start(type, use_base); - chash_end(type, use_base, keyptr, keylen, keyhash); - keyptr = keyhash; - keylen = hashlen; - } + if (keylen > hashblocklen) + { + chash_start(type, use_base); + chash_end(type, use_base, keyptr, keylen, keyhash); + keyptr = keyhash; + keylen = hashlen; + } - /* Now make the inner and outer key values */ + /* Now make the inner and outer key values */ - memset(innerkey, 0x36, hashblocklen); - memset(outerkey, 0x5c, hashblocklen); + memset(innerkey, 0x36, hashblocklen); + memset(outerkey, 0x5c, hashblocklen); - for (i = 0; i < keylen; i++) - { - innerkey[i] ^= keyptr[i]; - outerkey[i] ^= keyptr[i]; - } + for (i = 0; i < keylen; i++) + { + innerkey[i] ^= keyptr[i]; + outerkey[i] ^= keyptr[i]; + } - /* Now do the hashes */ + /* Now do the hashes */ - chash_start(type, use_base); - chash_mid(type, use_base, innerkey); - chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash); + chash_start(type, use_base); + chash_mid(type, use_base, innerkey); + chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash); - chash_start(type, use_base); - chash_mid(type, use_base, outerkey); - chash_end(type, use_base, innerhash, hashlen, finalhash); + chash_start(type, use_base); + chash_mid(type, use_base, outerkey); + chash_end(type, use_base, innerhash, hashlen, finalhash); - /* Encode the final hash as a hex string */ + /* Encode the final hash as a hex string */ - p = finalhash_hex; - for (i = 0; i < hashlen; i++) - { - *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4]; - *p++ = hex_digits[finalhash[i] & 0x0f]; - } + p = finalhash_hex; + for (i = 0; i < hashlen; i++) + { + *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4]; + *p++ = hex_digits[finalhash[i] & 0x0f]; + } - DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%.*s)=%.*s\n", sub[0], - (int)keylen, keyptr, Ustrlen(sub[2]), sub[2], hashlen*2, finalhash_hex); + DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n", + sub[0], (int)keylen, keyptr, sub[2], hashlen*2, finalhash_hex); - yield = string_cat(yield, &size, &ptr, finalhash_hex, hashlen*2); + yield = string_catn(yield, &size, &ptr, finalhash_hex, hashlen*2); + } + continue; } - continue; - /* Handle global substitution for "sg" - like Perl's s/xxx/yyy/g operator. We have to save the numerical variables and restore them afterwards. */ @@ -5142,7 +5299,7 @@ while (*s != 0) { int ovector[3*(EXPAND_MAXN+1)]; int n = pcre_exec(re, NULL, CS subject, slen, moffset + moffsetextra, - PCRE_EOPT | emptyopt, ovector, sizeof(ovector)/sizeof(int)); + PCRE_EOPT | emptyopt, ovector, nelem(ovector)); int nn; uschar *insert; @@ -5161,7 +5318,7 @@ while (*s != 0) emptyopt = 0; continue; } - yield = string_cat(yield, &size, &ptr, subject+moffset, slen-moffset); + yield = string_catn(yield, &size, &ptr, subject+moffset, slen-moffset); break; } @@ -5178,11 +5335,11 @@ while (*s != 0) /* Copy the characters before the match, plus the expanded insertion. */ - yield = string_cat(yield, &size, &ptr, subject + moffset, + yield = string_catn(yield, &size, &ptr, subject + moffset, ovector[0] - moffset); insert = expand_string(sub[2]); if (insert == NULL) goto EXPAND_FAILED; - yield = string_cat(yield, &size, &ptr, insert, Ustrlen(insert)); + yield = string_cat(yield, &size, &ptr, insert); moffset = ovector[1]; moffsetextra = 0; @@ -5215,7 +5372,7 @@ while (*s != 0) case EITEM_EXTRACT: { int i; - int j = 2; + int j; int field_number = 1; BOOL field_number_set = FALSE; uschar *save_lookup_value = lookup_value; @@ -5223,16 +5380,51 @@ while (*s != 0) int save_expand_nmax = save_expand_strings(save_expand_nstring, save_expand_nlength); - /* Read the arguments */ + /* While skipping we cannot rely on the data for expansions being + available (eg. $item) hence cannot decide on numeric vs. keyed. + Read a maximum of 5 arguments (inclding the yes/no) */ + + if (skipping) + { + while (isspace(*s)) s++; + for (j = 5; j > 0 && *s == '{'; j--) + { + if (!expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok)) + goto EXPAND_FAILED; /*{*/ + if (*s++ != '}') + { + expand_string_message = US"missing '{' for arg of extract"; + goto EXPAND_FAILED_CURLY; + } + while (isspace(*s)) s++; + } + if ( Ustrncmp(s, "fail", 4) == 0 + && (s[4] == '}' || s[4] == ' ' || s[4] == '\t' || !s[4]) + ) + { + s += 4; + while (isspace(*s)) s++; + } + if (*s != '}') + { + expand_string_message = US"missing '}' closing extract"; + goto EXPAND_FAILED_CURLY; + } + } - for (i = 0; i < j; i++) + else for (i = 0, j = 2; i < j; i++) /* Read the proper number of arguments */ { while (isspace(*s)) s++; if (*s == '{') /*}*/ { 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; + if (*s++ != '}') + { + expand_string_message = string_sprintf( + "missing '}' closing arg %d of extract", i+1); + goto EXPAND_FAILED_CURLY; + } /* After removal of leading and trailing white space, the first argument must not be empty; if it consists entirely of digits @@ -5252,31 +5444,33 @@ while (*s != 0) while (len > 0 && isspace(p[len-1])) len--; p[len] = 0; - if (!skipping) + if (*p == 0) { - if (*p == 0) - { - expand_string_message = US"first argument of \"extract\" must " - "not be empty"; - goto EXPAND_FAILED; - } + expand_string_message = US"first argument of \"extract\" must " + "not be empty"; + goto EXPAND_FAILED; + } - if (*p == '-') - { - field_number = -1; - p++; - } - while (*p != 0 && isdigit(*p)) x = x * 10 + *p++ - '0'; - if (*p == 0) - { - field_number *= x; - j = 3; /* Need 3 args */ - field_number_set = TRUE; - } + if (*p == '-') + { + field_number = -1; + p++; + } + while (*p != 0 && isdigit(*p)) x = x * 10 + *p++ - '0'; + if (*p == 0) + { + field_number *= x; + j = 3; /* Need 3 args */ + field_number_set = TRUE; } } } - else goto EXPAND_FAILED_CURLY; + else + { + expand_string_message = string_sprintf( + "missing '{' for arg %d of extract", i+1); + goto EXPAND_FAILED_CURLY; + } } /* Extract either the numbered or the keyed substring into $value. If @@ -5329,11 +5523,20 @@ while (*s != 0) { while (isspace(*s)) s++; if (*s != '{') /*}*/ + { + expand_string_message = string_sprintf( + "missing '{' for arg %d of listextract", i+1); 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; + if (*s++ != '}') + { + expand_string_message = string_sprintf( + "missing '}' closing arg %d of listextract", i+1); + goto EXPAND_FAILED_CURLY; + } /* After removal of leading and trailing white space, the first argument must be numeric and nonempty. */ @@ -5390,7 +5593,7 @@ while (*s != 0) &yield, /* output pointer */ &size, /* output size */ &ptr, /* output current point */ - US"extract", /* condition type */ + US"listextract", /* condition type */ &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ @@ -5416,10 +5619,17 @@ while (*s != 0) /* Read the field argument */ while (isspace(*s)) s++; if (*s != '{') /*}*/ + { + expand_string_message = US"missing '{' for field arg of certextract"; goto EXPAND_FAILED_CURLY; + } sub[0] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); if (!sub[0]) goto EXPAND_FAILED; /*{*/ - if (*s++ != '}') goto EXPAND_FAILED_CURLY; + if (*s++ != '}') + { + expand_string_message = US"missing '}' closing field arg of certextract"; + goto EXPAND_FAILED_CURLY; + } /* strip spaces fore & aft */ { int len; @@ -5436,7 +5646,10 @@ while (*s != 0) /* inspect the cert argument */ while (isspace(*s)) s++; if (*s != '{') /*}*/ + { + expand_string_message = US"missing '{' for cert variable arg of certextract"; goto EXPAND_FAILED_CURLY; + } if (*++s != '$') { expand_string_message = US"second argument of \"certextract\" must " @@ -5445,7 +5658,11 @@ while (*s != 0) } sub[1] = expand_string_internal(s+1, TRUE, &s, skipping, FALSE, &resetok); if (!sub[1]) goto EXPAND_FAILED; /*{*/ - if (*s++ != '}') goto EXPAND_FAILED_CURLY; + if (*s++ != '}') + { + expand_string_message = US"missing '}' closing cert variable arg of certextract"; + goto EXPAND_FAILED_CURLY; + } if (skipping) lookup_value = NULL; @@ -5462,7 +5679,7 @@ while (*s != 0) &yield, /* output pointer */ &size, /* output size */ &ptr, /* output current point */ - US"extract", /* condition type */ + US"certextract", /* condition type */ &resetok)) { case 1: goto EXPAND_FAILED; /* when all is well, the */ @@ -5489,25 +5706,48 @@ while (*s != 0) uschar *save_lookup_value = lookup_value; while (isspace(*s)) s++; - if (*s++ != '{') goto EXPAND_FAILED_CURLY; + if (*s++ != '{') + { + expand_string_message = + string_sprintf("missing '{' for first arg of %s", name); + goto EXPAND_FAILED_CURLY; + } list = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok); if (list == NULL) goto EXPAND_FAILED; - if (*s++ != '}') goto EXPAND_FAILED_CURLY; + if (*s++ != '}') + { + expand_string_message = + string_sprintf("missing '}' closing first arg of %s", name); + goto EXPAND_FAILED_CURLY; + } if (item_type == EITEM_REDUCE) { uschar * t; while (isspace(*s)) s++; - if (*s++ != '{') goto EXPAND_FAILED_CURLY; + if (*s++ != '{') + { + expand_string_message = US"missing '{' for second arg of reduce"; + goto EXPAND_FAILED_CURLY; + } t = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok); - if (temp == NULL) goto EXPAND_FAILED; + if (!t) goto EXPAND_FAILED; lookup_value = t; - if (*s++ != '}') goto EXPAND_FAILED_CURLY; + if (*s++ != '}') + { + expand_string_message = US"missing '}' closing second arg of reduce"; + goto EXPAND_FAILED_CURLY; + } } while (isspace(*s)) s++; - if (*s++ != '{') goto EXPAND_FAILED_CURLY; + if (*s++ != '{') + { + expand_string_message = + string_sprintf("missing '{' for last arg of %s", name); + goto EXPAND_FAILED_CURLY; + } expr = s; @@ -5605,7 +5845,7 @@ while (*s != 0) separator character, or is an empty string. */ if (ptr != save_ptr && (temp[0] == *outsep || temp[0] == 0)) - yield = string_cat(yield, &size, &ptr, US" ", 1); + yield = string_catn(yield, &size, &ptr, US" ", 1); /* Add the string in "temp" to the output list that we are building, This is done in chunks by searching for the separator character. */ @@ -5613,21 +5853,22 @@ while (*s != 0) for (;;) { size_t seglen = Ustrcspn(temp, outsep); - yield = string_cat(yield, &size, &ptr, temp, seglen + 1); + + yield = string_catn(yield, &size, &ptr, temp, seglen + 1); /* If we got to the end of the string we output one character too many; backup and end the loop. Otherwise arrange to double the separator. */ if (temp[seglen] == '\0') { ptr--; break; } - yield = string_cat(yield, &size, &ptr, outsep, 1); + yield = string_catn(yield, &size, &ptr, outsep, 1); temp += seglen + 1; } /* Output a separator after the string: we will remove the redundant final one at the end. */ - yield = string_cat(yield, &size, &ptr, outsep, 1); + yield = string_catn(yield, &size, &ptr, outsep, 1); } /* End of iteration over the list loop */ /* REDUCE has generated no output above: output the final value of @@ -5635,8 +5876,7 @@ while (*s != 0) if (item_type == EITEM_REDUCE) { - yield = string_cat(yield, &size, &ptr, lookup_value, - Ustrlen(lookup_value)); + yield = string_cat(yield, &size, &ptr, lookup_value); lookup_value = save_lookup_value; /* Restore $value */ } @@ -5662,28 +5902,52 @@ while (*s != 0) uschar *save_iterate_item = iterate_item; while (isspace(*s)) s++; - if (*s++ != '{') goto EXPAND_FAILED_CURLY; + if (*s++ != '{') + { + expand_string_message = US"missing '{' for list arg of sort"; + goto EXPAND_FAILED_CURLY; + } srclist = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok); if (!srclist) goto EXPAND_FAILED; - if (*s++ != '}') goto EXPAND_FAILED_CURLY; + if (*s++ != '}') + { + expand_string_message = US"missing '}' closing list arg of sort"; + goto EXPAND_FAILED_CURLY; + } while (isspace(*s)) s++; - if (*s++ != '{') goto EXPAND_FAILED_CURLY; + if (*s++ != '{') + { + expand_string_message = US"missing '{' for comparator arg of sort"; + goto EXPAND_FAILED_CURLY; + } cmp = expand_string_internal(s, TRUE, &s, skipping, FALSE, &resetok); if (!cmp) goto EXPAND_FAILED; - if (*s++ != '}') goto EXPAND_FAILED_CURLY; + if (*s++ != '}') + { + expand_string_message = US"missing '}' closing comparator arg of sort"; + goto EXPAND_FAILED_CURLY; + } while (isspace(*s)) s++; - if (*s++ != '{') goto EXPAND_FAILED_CURLY; + if (*s++ != '{') + { + expand_string_message = US"missing '{' for extractor arg of sort"; + goto EXPAND_FAILED_CURLY; + } xtract = s; tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok); if (!tmp) goto EXPAND_FAILED; xtract = string_copyn(xtract, s - xtract); - if (*s++ != '}') goto EXPAND_FAILED_CURLY; + if (*s++ != '}') + { + expand_string_message = US"missing '}' closing extractor arg of sort"; + goto EXPAND_FAILED_CURLY; + } /*{*/ if (*s++ != '}') { /*{*/ @@ -5779,7 +6043,7 @@ while (*s != 0) } if (dstlist) - yield = string_cat(yield, &size, &ptr, dstlist, Ustrlen(dstlist)); + yield = string_cat(yield, &size, &ptr, dstlist); /* Restore preserved $item */ iterate_item = save_iterate_item; @@ -5799,12 +6063,12 @@ while (*s != 0) #define EXPAND_DLFUNC_MAX_ARGS 8 case EITEM_DLFUNC: - #ifndef EXPAND_DLFUNC - expand_string_message = US"\"${dlfunc\" encountered, but this facility " /*}*/ - "is not included in this binary"; - goto EXPAND_FAILED; +#ifndef EXPAND_DLFUNC + expand_string_message = US"\"${dlfunc\" encountered, but this facility " /*}*/ + "is not included in this binary"; + goto EXPAND_FAILED; - #else /* EXPAND_DLFUNC */ +#else /* EXPAND_DLFUNC */ { tree_node *t; exim_dlfunc_t *func; @@ -5877,7 +6141,7 @@ while (*s != 0) if(status == OK) { if (result == NULL) result = US""; - yield = string_cat(yield, &size, &ptr, result, Ustrlen(result)); + yield = string_cat(yield, &size, &ptr, result); continue; } else @@ -5890,7 +6154,43 @@ while (*s != 0) goto EXPAND_FAILED; } } - #endif /* EXPAND_DLFUNC */ +#endif /* EXPAND_DLFUNC */ + + case EITEM_ENV: /* ${env {name} {val_if_found} {val_if_unfound}} */ + { + uschar * key; + uschar *save_lookup_value = lookup_value; + + while (isspace(*s)) s++; + if (*s != '{') /*}*/ + goto EXPAND_FAILED; + + key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok); + if (!key) goto EXPAND_FAILED; /*{*/ + if (*s++ != '}') + { + expand_string_message = US"missing '{' for name arg of env"; + goto EXPAND_FAILED_CURLY; + } + + lookup_value = US getenv(CS key); + + switch(process_yesno( + skipping, /* were previously skipping */ + lookup_value != NULL, /* success/failure indicator */ + save_lookup_value, /* value to reset for string2 */ + &s, /* input pointer */ + &yield, /* output pointer */ + &size, /* output size */ + &ptr, /* output current point */ + US"env", /* condition type */ + &resetok)) + { + case 1: goto EXPAND_FAILED; /* when all is well, the */ + case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */ + } + continue; + } } /* EITEM_* switch */ /* Control reaches here if the name is not recognized as one of the more @@ -5911,13 +6211,12 @@ while (*s != 0) the arguments and then scan the main table. */ if ((c = chop_match(name, op_table_underscore, - sizeof(op_table_underscore)/sizeof(uschar *))) < 0) + nelem(op_table_underscore))) < 0) { arg = Ustrchr(name, '_'); if (arg != NULL) *arg = 0; - c = chop_match(name, op_table_main, - sizeof(op_table_main)/sizeof(uschar *)); - if (c >= 0) c += sizeof(op_table_underscore)/sizeof(uschar *); + c = chop_match(name, op_table_main, nelem(op_table_main)); + if (c >= 0) c += nelem(op_table_underscore); if (arg != NULL) *arg++ = '_'; /* Put back for error messages */ } @@ -5929,13 +6228,19 @@ while (*s != 0) case EOP_MD5: case EOP_SHA1: case EOP_SHA256: + case EOP_BASE64: if (s[1] == '$') { const uschar * s1 = s; sub = expand_string_internal(s+2, TRUE, &s1, skipping, FALSE, &resetok); if (!sub) goto EXPAND_FAILED; /*{*/ - if (*s1 != '}') goto EXPAND_FAILED_CURLY; + if (*s1 != '}') + { + expand_string_message = + string_sprintf("missing '}' closing cert arg of %s", name); + goto EXPAND_FAILED_CURLY; + } if ((vp = find_var_ent(sub)) && vp->type == vtype_cert) { s = s1+1; @@ -5964,6 +6269,47 @@ while (*s != 0) switch(c) { + case EOP_BASE32: + { + uschar *t; + unsigned long int n = Ustrtoul(sub, &t, 10); + uschar * s = NULL; + int sz = 0, i = 0; + + if (*t != 0) + { + expand_string_message = string_sprintf("argument for base32 " + "operator is \"%s\", which is not a decimal number", sub); + goto EXPAND_FAILED; + } + for ( ; n; n >>= 5) + s = string_catn(s, &sz, &i, &base32_chars[n & 0x1f], 1); + + while (i > 0) yield = string_catn(yield, &size, &ptr, &s[--i], 1); + continue; + } + + case EOP_BASE32D: + { + uschar *tt = sub; + unsigned long int n = 0; + uschar * s; + while (*tt) + { + uschar * t = Ustrchr(base32_chars, *tt++); + if (t == NULL) + { + expand_string_message = string_sprintf("argument for base32d " + "operator is \"%s\", which is not a base 32 number", sub); + goto EXPAND_FAILED; + } + n = n * 32 + (t - base32_chars); + } + s = string_sprintf("%ld", n); + yield = string_cat(yield, &size, &ptr, s); + continue; + } + case EOP_BASE62: { uschar *t; @@ -5975,7 +6321,7 @@ while (*s != 0) goto EXPAND_FAILED; } t = string_base62(n); - yield = string_cat(yield, &size, &ptr, t, Ustrlen(t)); + yield = string_cat(yield, &size, &ptr, t); continue; } @@ -5999,7 +6345,7 @@ while (*s != 0) n = n * BASE_62 + (t - base62_chars); } (void)sprintf(CS buf, "%ld", n); - yield = string_cat(yield, &size, &ptr, buf, Ustrlen(buf)); + yield = string_cat(yield, &size, &ptr, buf); continue; } @@ -6013,7 +6359,7 @@ while (*s != 0) expand_string_message); goto EXPAND_FAILED; } - yield = string_cat(yield, &size, &ptr, expanded, Ustrlen(expanded)); + yield = string_cat(yield, &size, &ptr, expanded); continue; } @@ -6022,7 +6368,7 @@ while (*s != 0) int count = 0; uschar *t = sub - 1; while (*(++t) != 0) { *t = tolower(*t); count++; } - yield = string_cat(yield, &size, &ptr, sub, count); + yield = string_catn(yield, &size, &ptr, sub, count); continue; } @@ -6031,7 +6377,7 @@ while (*s != 0) int count = 0; uschar *t = sub - 1; while (*(++t) != 0) { *t = toupper(*t); count++; } - yield = string_cat(yield, &size, &ptr, sub, count); + yield = string_catn(yield, &size, &ptr, sub, count); continue; } @@ -6040,7 +6386,7 @@ while (*s != 0) if (vp && *(void **)vp->value) { uschar * cp = tls_cert_fprt_md5(*(void **)vp->value); - yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp)); + yield = string_cat(yield, &size, &ptr, cp); } else #endif @@ -6052,7 +6398,7 @@ while (*s != 0) md5_start(&base); md5_end(&base, sub, Ustrlen(sub), digest); for(j = 0; j < 16; j++) sprintf(st+2*j, "%02x", digest[j]); - yield = string_cat(yield, &size, &ptr, US st, (int)strlen(st)); + yield = string_cat(yield, &size, &ptr, US st); } continue; @@ -6061,34 +6407,83 @@ while (*s != 0) if (vp && *(void **)vp->value) { uschar * cp = tls_cert_fprt_sha1(*(void **)vp->value); - yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp)); + yield = string_cat(yield, &size, &ptr, cp); } else #endif { - sha1 base; + hctx h; uschar digest[20]; int j; char st[41]; - sha1_start(&base); - sha1_end(&base, sub, Ustrlen(sub), digest); + sha1_start(&h); + sha1_end(&h, sub, Ustrlen(sub), digest); for(j = 0; j < 20; j++) sprintf(st+2*j, "%02X", digest[j]); - yield = string_cat(yield, &size, &ptr, US st, (int)strlen(st)); + yield = string_catn(yield, &size, &ptr, US st, 40); } continue; case EOP_SHA256: -#ifdef SUPPORT_TLS +#ifdef EXIM_HAVE_SHA2 if (vp && *(void **)vp->value) { uschar * cp = tls_cert_fprt_sha256(*(void **)vp->value); - yield = string_cat(yield, &size, &ptr, cp, (int)Ustrlen(cp)); + yield = string_cat(yield, &size, &ptr, cp); } else + { + hctx h; + blob b; + char st[3]; + + exim_sha_init(&h, HASH_SHA256); + exim_sha_update(&h, sub, Ustrlen(sub)); + exim_sha_finish(&h, &b); + while (b.len-- > 0) + { + sprintf(st, "%02X", *b.data++); + yield = string_catn(yield, &size, &ptr, US st, 2); + } + } +#else + expand_string_message = US"sha256 only supported with TLS"; #endif - expand_string_message = US"sha256 only supported for certificates"; continue; + case EOP_SHA3: +#ifdef EXIM_HAVE_SHA3 + { + hctx h; + blob b; + char st[3]; + hashmethod m = !arg ? HASH_SHA3_256 + : Ustrcmp(arg, "224") == 0 ? HASH_SHA3_224 + : Ustrcmp(arg, "256") == 0 ? HASH_SHA3_256 + : Ustrcmp(arg, "384") == 0 ? HASH_SHA3_384 + : Ustrcmp(arg, "512") == 0 ? HASH_SHA3_512 + : HASH_BADTYPE; + + if (m == HASH_BADTYPE) + { + expand_string_message = US"unrecognised sha3 variant"; + goto EXPAND_FAILED; + } + + exim_sha_init(&h, m); + exim_sha_update(&h, sub, Ustrlen(sub)); + exim_sha_finish(&h, &b); + while (b.len-- > 0) + { + sprintf(st, "%02X", *b.data++); + yield = string_catn(yield, &size, &ptr, US st, 2); + } + } + continue; +#else + expand_string_message = US"sha3 only supported with GnuTLS 3.5.0 +"; + goto EXPAND_FAILED; +#endif + /* Convert hex encoding to base64 encoding */ case EOP_HEX2B64: @@ -6132,8 +6527,8 @@ while (*s != 0) } } - enc = auth_b64encode(sub, out - sub); - yield = string_cat(yield, &size, &ptr, enc, Ustrlen(enc)); + enc = b64encode(sub, out - sub); + yield = string_cat(yield, &size, &ptr, enc); continue; } @@ -6145,10 +6540,10 @@ while (*s != 0) while (*(++t) != 0) { if (*t < 0x21 || 0x7E < *t) - yield = string_cat(yield, &size, &ptr, + yield = string_catn(yield, &size, &ptr, string_sprintf("\\x%02x", *t), 4); else - yield = string_cat(yield, &size, &ptr, t, 1); + yield = string_catn(yield, &size, &ptr, t, 1); } continue; } @@ -6164,7 +6559,7 @@ while (*s != 0) while (string_nextinlist(CUSS &sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++; cp = string_sprintf("%d", cnt); - yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp)); + yield = string_cat(yield, &size, &ptr, cp); continue; } @@ -6218,7 +6613,7 @@ while (*s != 0) { uschar * buf = US" : "; if (needsep) - yield = string_cat(yield, &size, &ptr, buf, 3); + yield = string_catn(yield, &size, &ptr, buf, 3); else needsep = TRUE; @@ -6234,21 +6629,21 @@ while (*s != 0) 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); + yield = string_catn(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); + yield = string_catn(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); + yield = string_catn(yield, &size, &ptr, (uschar *)tok, 1); if (*cp == sep) cp++; item = (uschar *)cp; } } } - yield = string_cat(yield, &size, &ptr, item, Ustrlen(item)); + yield = string_cat(yield, &size, &ptr, item); } continue; } @@ -6296,11 +6691,44 @@ while (*s != 0) /* Convert to masked textual format and add to output. */ - yield = string_cat(yield, &size, &ptr, buffer, + yield = string_catn(yield, &size, &ptr, buffer, host_nmtoa(count, binary, mask, buffer, '.')); continue; } + case EOP_IPV6NORM: + case EOP_IPV6DENORM: + { + int type = string_is_ip_address(sub, NULL); + int binary[4]; + uschar buffer[44]; + + switch (type) + { + case 6: + (void) host_aton(sub, binary); + break; + + case 4: /* convert to IPv4-mapped IPv6 */ + binary[0] = binary[1] = 0; + binary[2] = 0x0000ffff; + (void) host_aton(sub, binary+3); + break; + + case 0: + expand_string_message = + string_sprintf("\"%s\" is not an IP address", sub); + goto EXPAND_FAILED; + } + + yield = string_catn(yield, &size, &ptr, buffer, + c == EOP_IPV6NORM + ? ipv6_nmtoa(binary, buffer) + : host_nmtoa(4, binary, -1, buffer, ':') + ); + continue; + } + case EOP_ADDRESS: case EOP_LOCAL_PART: case EOP_DOMAIN: @@ -6314,12 +6742,12 @@ while (*s != 0) if (c != EOP_DOMAIN) { if (c == EOP_LOCAL_PART && domain != 0) end = start + domain - 1; - yield = string_cat(yield, &size, &ptr, sub+start, end-start); + yield = string_catn(yield, &size, &ptr, sub+start, end-start); } else if (domain != 0) { domain += start; - yield = string_cat(yield, &size, &ptr, sub+domain, end-domain); + yield = string_catn(yield, &size, &ptr, sub+domain, end-domain); } } continue; @@ -6354,25 +6782,25 @@ while (*s != 0) if (address != NULL) { if (ptr != save_ptr && address[0] == *outsep) - yield = string_cat(yield, &size, &ptr, US" ", 1); + yield = string_catn(yield, &size, &ptr, US" ", 1); for (;;) { size_t seglen = Ustrcspn(address, outsep); - yield = string_cat(yield, &size, &ptr, address, seglen + 1); + yield = string_catn(yield, &size, &ptr, address, seglen + 1); /* If we got to the end of the string we output one character too many. */ if (address[seglen] == '\0') { ptr--; break; } - yield = string_cat(yield, &size, &ptr, outsep, 1); + yield = string_catn(yield, &size, &ptr, outsep, 1); address += seglen + 1; } /* Output a separator after the string: we will remove the redundant final one at the end. */ - yield = string_cat(yield, &size, &ptr, outsep, 1); + yield = string_catn(yield, &size, &ptr, outsep, 1); } if (saveend == '\0') break; @@ -6419,24 +6847,24 @@ while (*s != 0) if (needs_quote) { - yield = string_cat(yield, &size, &ptr, US"\"", 1); + yield = string_catn(yield, &size, &ptr, US"\"", 1); t = sub - 1; while (*(++t) != 0) { if (*t == '\n') - yield = string_cat(yield, &size, &ptr, US"\\n", 2); + yield = string_catn(yield, &size, &ptr, US"\\n", 2); else if (*t == '\r') - yield = string_cat(yield, &size, &ptr, US"\\r", 2); + yield = string_catn(yield, &size, &ptr, US"\\r", 2); else { if (*t == '\\' || *t == '"') - yield = string_cat(yield, &size, &ptr, US"\\", 1); - yield = string_cat(yield, &size, &ptr, t, 1); + yield = string_catn(yield, &size, &ptr, US"\\", 1); + yield = string_catn(yield, &size, &ptr, t, 1); } } - yield = string_cat(yield, &size, &ptr, US"\"", 1); + yield = string_catn(yield, &size, &ptr, US"\"", 1); } - else yield = string_cat(yield, &size, &ptr, sub, Ustrlen(sub)); + else yield = string_cat(yield, &size, &ptr, sub); continue; } @@ -6468,7 +6896,7 @@ while (*s != 0) goto EXPAND_FAILED; } - yield = string_cat(yield, &size, &ptr, sub, Ustrlen(sub)); + yield = string_cat(yield, &size, &ptr, sub); continue; } @@ -6481,8 +6909,8 @@ while (*s != 0) while (*(++t) != 0) { if (!isalnum(*t)) - yield = string_cat(yield, &size, &ptr, US"\\", 1); - yield = string_cat(yield, &size, &ptr, t, 1); + yield = string_catn(yield, &size, &ptr, US"\\", 1); + yield = string_catn(yield, &size, &ptr, t, 1); } continue; } @@ -6495,7 +6923,7 @@ while (*s != 0) uschar buffer[2048]; const uschar *string = parse_quote_2047(sub, Ustrlen(sub), headers_charset, buffer, sizeof(buffer), FALSE); - yield = string_cat(yield, &size, &ptr, string, Ustrlen(string)); + yield = string_cat(yield, &size, &ptr, string); continue; } @@ -6512,7 +6940,7 @@ while (*s != 0) expand_string_message = error; goto EXPAND_FAILED; } - yield = string_cat(yield, &size, &ptr, decoded, len); + yield = string_catn(yield, &size, &ptr, decoded, len); continue; } @@ -6528,13 +6956,13 @@ while (*s != 0) GETUTF8INC(c, sub); if (c > 255) c = '_'; buff[0] = c; - yield = string_cat(yield, &size, &ptr, buff, 1); + yield = string_catn(yield, &size, &ptr, buff, 1); } continue; } /* replace illegal UTF-8 sequences by replacement character */ - + #define UTF8_REPLACEMENT_CHAR US"?" case EOP_UTF8CLEAN: @@ -6543,7 +6971,7 @@ while (*s != 0) int bytes_left = 0; long codepoint = -1; uschar seq_buff[4]; /* accumulate utf-8 here */ - + while (*sub != 0) { int complete = 0; @@ -6552,31 +6980,27 @@ while (*s != 0) if (bytes_left) { 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 { /* finished; output utf-8 sequence */ - yield = string_cat(yield, &size, &ptr, seq_buff, seq_len); + yield = string_catn(yield, &size, &ptr, seq_buff, seq_len); index = 0; } - } } } else /* no bytes left: new sequence */ { if((c & 0x80) == 0) /* 1-byte sequence, US-ASCII, keep it */ { - yield = string_cat(yield, &size, &ptr, &c, 1); + yield = string_catn(yield, &size, &ptr, &c, 1); continue; } if((c & 0xe0) == 0xc0) /* 2-byte sequence */ @@ -6609,22 +7033,84 @@ while (*s != 0) if (complete != 0) { bytes_left = index = 0; - yield = string_cat(yield, &size, &ptr, UTF8_REPLACEMENT_CHAR, 1); + yield = string_catn(yield, &size, &ptr, UTF8_REPLACEMENT_CHAR, 1); } if ((complete == 1) && ((c & 0x80) == 0)) - { /* ASCII character follows incomplete sequence */ - yield = string_cat(yield, &size, &ptr, &c, 1); - } + /* ASCII character follows incomplete sequence */ + yield = string_catn(yield, &size, &ptr, &c, 1); } continue; } +#ifdef SUPPORT_I18N + case EOP_UTF8_DOMAIN_TO_ALABEL: + { + 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, &size, &ptr, s); + continue; + } + + case EOP_UTF8_DOMAIN_FROM_ALABEL: + { + 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, &size, &ptr, s); + continue; + } + + case EOP_UTF8_LOCALPART_TO_ALABEL: + { + 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, &size, &ptr, s); + DEBUG(D_expand) debug_printf("yield: '%s'\n", yield); + continue; + } + + case EOP_UTF8_LOCALPART_FROM_ALABEL: + { + 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, &size, &ptr, s); + continue; + } +#endif /* EXPERIMENTAL_INTERNATIONAL */ + /* escape turns all non-printing characters into escape sequences. */ case EOP_ESCAPE: { const uschar *t = string_printing(sub); - yield = string_cat(yield, &size, &ptr, t, Ustrlen(t)); + yield = string_cat(yield, &size, &ptr, t); continue; } @@ -6644,7 +7130,7 @@ while (*s != 0) goto EXPAND_FAILED; } sprintf(CS var_buffer, PR_EXIM_ARITH, n); - yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer)); + yield = string_cat(yield, &size, &ptr, var_buffer); continue; } @@ -6660,7 +7146,7 @@ while (*s != 0) goto EXPAND_FAILED; } sprintf(CS var_buffer, "%d", n); - yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer)); + yield = string_cat(yield, &size, &ptr, var_buffer); continue; } @@ -6675,16 +7161,37 @@ while (*s != 0) goto EXPAND_FAILED; } t = readconf_printtime(n); - yield = string_cat(yield, &size, &ptr, t, Ustrlen(t)); + yield = string_cat(yield, &size, &ptr, t); continue; } /* Convert string to base64 encoding */ case EOP_STR2B64: + case EOP_BASE64: + { +#ifdef SUPPORT_TLS + uschar * s = vp && *(void **)vp->value + ? tls_cert_der_b64(*(void **)vp->value) + : b64encode(sub, Ustrlen(sub)); +#else + uschar * s = b64encode(sub, Ustrlen(sub)); +#endif + yield = string_cat(yield, &size, &ptr, s); + continue; + } + + case EOP_BASE64D: { - uschar *encstr = auth_b64encode(sub, Ustrlen(sub)); - yield = string_cat(yield, &size, &ptr, encstr, Ustrlen(encstr)); + uschar * s; + int len = b64decode(sub, &s); + 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, &size, &ptr, s); continue; } @@ -6694,7 +7201,7 @@ while (*s != 0) { uschar buff[24]; (void)sprintf(CS buff, "%d", Ustrlen(sub)); - yield = string_cat(yield, &size, &ptr, buff, Ustrlen(buff)); + yield = string_cat(yield, &size, &ptr, buff); continue; } @@ -6785,7 +7292,7 @@ while (*s != 0) extract_substr(sub, value1, value2, &len); if (ret == NULL) goto EXPAND_FAILED; - yield = string_cat(yield, &size, &ptr, ret, len); + yield = string_catn(yield, &size, &ptr, ret, len); continue; } @@ -6840,7 +7347,7 @@ while (*s != 0) (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); - yield = string_cat(yield, &size, &ptr, s, Ustrlen(s)); + yield = string_cat(yield, &size, &ptr, s); continue; } @@ -6855,7 +7362,7 @@ while (*s != 0) 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)); + yield = string_cat(yield, &size, &ptr, s); continue; } @@ -6874,7 +7381,7 @@ while (*s != 0) goto EXPAND_FAILED; } invert_address(reversed, sub); - yield = string_cat(yield, &size, &ptr, reversed, Ustrlen(reversed)); + yield = string_cat(yield, &size, &ptr, reversed); continue; } @@ -6904,8 +7411,7 @@ while (*s != 0) yield = NULL; size = 0; } - value = find_variable(name, FALSE, skipping, &newsize); - if (value == NULL) + if (!(value = find_variable(name, FALSE, skipping, &newsize))) { expand_string_message = string_sprintf("unknown variable in \"${%s}\"", name); @@ -6913,13 +7419,14 @@ while (*s != 0) goto EXPAND_FAILED; } len = Ustrlen(value); - if (yield == NULL && newsize != 0) + if (!yield && newsize) { yield = value; size = newsize; ptr = len; } - else yield = string_cat(yield, &size, &ptr, value, len); + else + yield = string_catn(yield, &size, &ptr, value, len); continue; } @@ -6960,9 +7467,9 @@ else if (resetok_p) *resetok_p = FALSE; DEBUG(D_expand) { - debug_printf("expanding: %.*s\n result: %s\n", (int)(s - string), string, + debug_printf(" expanding: %.*s\n result: %s\n", (int)(s - string), string, yield); - if (skipping) debug_printf("skipping: result is not used\n"); + if (skipping) debug_printf(" skipping: result is not used\n"); } return yield; @@ -6971,10 +7478,12 @@ to update the pointer to the terminator, for cases of nested calls with "fail". */ EXPAND_FAILED_CURLY: -expand_string_message = malformed_header? - US"missing or misplaced { or } - could be header name not terminated by colon" - : - US"missing or misplaced { or }"; +if (malformed_header) + expand_string_message = + US"missing or misplaced { or } - could be header name not terminated by colon"; + +else if (!expand_string_message || !*expand_string_message) + expand_string_message = US"missing or misplaced { or }"; /* At one point, Exim reset the store to yield (if yield was not NULL), but that is a bad idea, because expand_string_message is in dynamic store. */ @@ -7225,6 +7734,29 @@ return OK; +/* Avoid potentially exposing a password in a string about to be logged */ + +uschar * +expand_hide_passwords(uschar * s) +{ +return ( ( Ustrstr(s, "failed to expand") != NULL + || Ustrstr(s, "expansion of ") != NULL + ) + && ( Ustrstr(s, "mysql") != NULL + || Ustrstr(s, "pgsql") != NULL + || Ustrstr(s, "redis") != NULL + || Ustrstr(s, "sqlite") != NULL + || Ustrstr(s, "ldap:") != NULL + || Ustrstr(s, "ldaps:") != NULL + || Ustrstr(s, "ldapi:") != NULL + || Ustrstr(s, "ldapdn:") != NULL + || Ustrstr(s, "ldapm:") != NULL + ) ) + ? US"Temporary internal error" : s; +} + + + /************************************************* ************************************************** @@ -7240,7 +7772,7 @@ regex_match_and_setup(const pcre *re, uschar *subject, int options, int setup) { int ovector[3*(EXPAND_MAXN+1)]; int n = pcre_exec(re, NULL, subject, Ustrlen(subject), 0, PCRE_EOPT|options, - ovector, sizeof(ovector)/sizeof(int)); + ovector, nelem(ovector)); BOOL yield = n >= 0; if (n == 0) n = EXPAND_MAXN + 1; if (yield) @@ -7281,22 +7813,22 @@ for (i = 1; i < argc; i++) if (Ustrspn(argv[i], "abcdefghijklmnopqrtsuvwxyz0123456789-.:/") == Ustrlen(argv[i])) { - #ifdef LOOKUP_LDAP +#ifdef LOOKUP_LDAP eldap_default_servers = argv[i]; - #endif - #ifdef LOOKUP_MYSQL +#endif +#ifdef LOOKUP_MYSQL mysql_servers = argv[i]; - #endif - #ifdef LOOKUP_PGSQL +#endif +#ifdef LOOKUP_PGSQL pgsql_servers = argv[i]; - #endif - #ifdef EXPERIMENTAL_REDIS +#endif +#ifdef LOOKUP_REDIS redis_servers = argv[i]; - #endif +#endif } - #ifdef EXIM_PERL +#ifdef EXIM_PERL else opt_perl_startup = argv[i]; - #endif +#endif } printf("Testing string expansion: debug_level = %d\n\n", debug_level);