-/* $Cambridge: exim/src/src/expand.c,v 1.80 2007/02/06 10:00:24 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
/* See the file NOTICE for conditions of use and distribution. */
/* Recursively called function */
-static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL);
+static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL, BOOL);
#ifdef STAND_ALONE
#ifndef SUPPORT_CRYPTEQ
static uschar *item_table[] = {
US"dlfunc",
US"extract",
+ US"filter",
US"hash",
US"hmac",
US"if",
US"length",
US"lookup",
+ US"map",
US"nhash",
US"perl",
US"prvs",
US"prvscheck",
US"readfile",
US"readsocket",
+ US"reduce",
US"run",
US"sg",
US"substr",
enum {
EITEM_DLFUNC,
EITEM_EXTRACT,
+ EITEM_FILTER,
EITEM_HASH,
EITEM_HMAC,
EITEM_IF,
EITEM_LENGTH,
EITEM_LOOKUP,
+ EITEM_MAP,
EITEM_NHASH,
EITEM_PERL,
EITEM_PRVS,
EITEM_PRVSCHECK,
EITEM_READFILE,
EITEM_READSOCK,
+ EITEM_REDUCE,
EITEM_RUN,
EITEM_SG,
EITEM_SUBSTR,
US"from_utf8",
US"local_part",
US"quote_local_part",
+ US"reverse_ip",
US"time_eval",
US"time_interval"};
EOP_FROM_UTF8,
EOP_LOCAL_PART,
EOP_QUOTE_LOCAL_PART,
+ EOP_REVERSE_IP,
EOP_TIME_EVAL,
EOP_TIME_INTERVAL };
static uschar *op_table_main[] = {
US"address",
+ US"addresses",
US"base62",
US"base62d",
US"domain",
US"nh",
US"nhash",
US"quote",
+ US"randint",
US"rfc2047",
US"rfc2047d",
US"rxquote",
enum {
EOP_ADDRESS = sizeof(op_table_underscore)/sizeof(uschar *),
+ EOP_ADDRESSES,
EOP_BASE62,
EOP_BASE62D,
EOP_DOMAIN,
EOP_NH,
EOP_NHASH,
EOP_QUOTE,
+ EOP_RANDINT,
EOP_RFC2047,
EOP_RFC2047D,
EOP_RXQUOTE,
US">",
US">=",
US"and",
+ US"bool",
+ US"bool_lax",
US"crypteq",
US"def",
US"eq",
US"gei",
US"gt",
US"gti",
+ US"inlist",
+ US"inlisti",
US"isip",
US"isip4",
US"isip6",
ECOND_NUM_G,
ECOND_NUM_GE,
ECOND_AND,
+ ECOND_BOOL,
+ ECOND_BOOL_LAX,
ECOND_CRYPTEQ,
ECOND_DEF,
ECOND_STR_EQ,
ECOND_STR_GEI,
ECOND_STR_GT,
ECOND_STR_GTI,
+ ECOND_INLIST,
+ ECOND_INLISTI,
ECOND_ISIP,
ECOND_ISIP4,
ECOND_ISIP6,
/* 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
vtype_localpart, /* extract local part from string */
vtype_domain, /* extract domain from string */
vtype_recipients, /* extract recipients from recipients list */
- /* (enabled only during system filtering */
+ /* (available only in system filters, ACLs, and */
+ /* local_scan()) */
vtype_todbsdin, /* value not used; generate BSD inbox tod */
vtype_tode, /* value not used; generate tod in epoch format */
vtype_todf, /* value not used; generate full tod */
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
};
/* This table must be kept in alphabetical order. */
{ "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 },
{ "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
{ "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 },
#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 },
{ "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] },
{ "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 },
{ "srs_status", vtype_stringptr, &srs_status },
#endif
{ "thisaddress", vtype_stringptr, &filter_thisaddress },
+ { "tls_bits", vtype_int, &tls_bits },
{ "tls_certificate_verified", vtype_int, &tls_certificate_verified },
{ "tls_cipher", vtype_stringptr, &tls_cipher },
{ "tls_peerdn", vtype_stringptr, &tls_peerdn },
/* For textual hashes */
-static char *hashcodes = "abcdefghijklmnopqrtsuvwxyz"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "0123456789";
+static const char *hashcodes = "abcdefghijklmnopqrtsuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
enum { HMAC_MD5, HMAC_SHA1 };
forced fail or lookup defer. All store used by the function can be released on
exit.
+The actual false-value tests should be replicated for ECOND_BOOL_LAX.
+
Arguments:
condition the condition string
m1 text to be incorporated in panic error
+/*************************************************
+* 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 and OpenSSL is used, then this will not be used.
+The GNUTLS randomness functions found do not seem amenable to extracting
+random numbers outside of a TLS context. Any volunteers?
+
+Arguments:
+ max range maximum
+Returns a random number in range [0, max-1]
+*/
+
+#if !defined(SUPPORT_TLS) || defined(USE_GNUTLS)
+int
+pseudo_random_number(int max)
+{
+ 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
+}
+
+#endif
+
/*************************************************
* Pick out a name from a string *
*************************************************/
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 */
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 */
if (len > 0)
{
body[len] = 0;
- while (len > 0)
+ if (message_body_newlines) /* Separate loops for efficiency */
+ {
+ while (len > 0)
+ { if (body[--len] == 0) body[len] = ' '; }
+ }
+ else
{
- if (body[--len] == '\n' || body[len] == 0) body[len] = ' ';
+ while (len > 0)
+ { if (body[--len] == '\n' || body[len] == 0) body[len] = ' '; }
}
}
}
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,
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
+
}
}
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);
if (sub[i] == NULL) return 3;
if (*s++ != '}') return 1;
while (isspace(*s)) s++;
BOOL testfor = TRUE;
BOOL tempcond, combined_cond;
BOOL *subcondptr;
+BOOL sub2_honour_dollar = TRUE;
int i, rc, cond_type, roffset;
int num[2];
struct stat statbuf;
while (isspace(*s)) s++;
if (*s != '{') goto COND_FAILED_CURLY_START;
- sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL);
+ sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE);
if (sub[0] == NULL) return NULL;
if (*s++ != '}') goto COND_FAILED_CURLY_END;
/* 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:
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 != '{')
{
"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);
if (sub[i] == NULL) return NULL;
if (*s++ != '}') goto COND_FAILED_CURLY_END;
}
else /* {crypt} or {crypt16} and non-{ at start */
+ /* }-for-text-editors */
{
int which = 0;
uschar *coded;
}
break;
#endif /* SUPPORT_CRYPTEQ */
+
+ case ECOND_INLIST:
+ case ECOND_INLISTI:
+ {
+ int sep = 0;
+ BOOL found = FALSE;
+ uschar *save_iterate_item = iterate_item;
+ int (*compare)(const uschar *, const uschar *);
+
+ 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)
+ {
+ found = TRUE;
+ break;
+ }
+ iterate_item = save_iterate_item;
+ *yield = found;
+ }
+
} /* Switch for comparison conditions */
return s; /* End of comparison conditions */
case ECOND_FORANY:
{
int sep = 0;
- uschar *iterate_item_save = iterate_item;
+ 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));
+ sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL), TRUE);
if (sub[0] == NULL) return NULL;
if (*s++ != '}') goto COND_FAILED_CURLY_END;
{
expand_string_message = string_sprintf("%s inside \"%s\" condition",
expand_string_message, name);
+ iterate_item = save_iterate_item;
return NULL;
}
DEBUG(D_expand) debug_printf("%s: condition evaluated to %s\n", name,
if (tempcond == (cond_type == ECOND_FORANY)) break;
}
- iterate_item = iterate_item_save;
+ iterate_item = save_iterate_item;
return s;
}
+ /* 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;
+ ourname = cond_type == ECOND_BOOL_LAX ? US"bool_lax" : US"bool";
+ switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname))
+ {
+ 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"<empty>");
+ /* 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:
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);
if (sub1 == NULL && (yes || !expand_string_forcedfail)) goto FAILED;
expand_string_forcedfail = FALSE;
if (*s++ != '}') goto FAILED_CURLY;
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);
if (sub2 == NULL && (!yes || !expand_string_forcedfail)) goto FAILED;
expand_string_forcedfail = FALSE;
if (*s++ != '}') goto FAILED_CURLY;
int op = *s++;
int 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 == INT_MIN && op != '*')
+ {
+ DEBUG(D_expand)
+ debug_printf("Integer exception dodging: %d%c-1 coerced to %d\n",
+ INT_MIN, op, INT_MAX);
+ x = INT_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;
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. 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 }
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
Returns: NULL if expansion fails:
expand_string_forcedfail is set TRUE if failure was forced
static uschar *
expand_string_internal(uschar *string, BOOL ket_ends, uschar **left,
- BOOL skipping)
+ BOOL skipping, BOOL honour_dollar)
{
int ptr = 0;
int size = Ustrlen(string)+ 64;
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"";
if (ket_ends && *s == '}') break;
- if (*s != '$')
+ if (*s != '$' || !honour_dollar)
{
yield = string_cat(yield, &size, &ptr, s++, 1);
continue;
if (ptr == 0 && yield != NULL)
{
- store_reset(yield);
+ if (resetok) store_reset(yield);
yield = NULL;
size = 0;
}
while (isspace(*s)) s++;
if (*s == '{')
{
- key = expand_string_internal(s+1, TRUE, &s, skipping);
+ key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE);
if (key == NULL) goto EXPAND_FAILED;
if (*s++ != '}') goto EXPAND_FAILED_CURLY;
while (isspace(*s)) s++;
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);
if (filename == NULL) goto EXPAND_FAILED;
if (*s++ != '}') goto EXPAND_FAILED_CURLY;
while (isspace(*s)) s++;
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;
*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));
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);
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");
if (*s == '{')
{
- if (expand_string_internal(s+1, TRUE, &s, TRUE) == NULL)
+ if (expand_string_internal(s+1, TRUE, &s, TRUE, TRUE) == NULL)
goto EXPAND_FAILED;
if (*s++ != '}') goto EXPAND_FAILED_CURLY;
while (isspace(*s)) s++;
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);
if (arg == NULL) goto EXPAND_FAILED;
yield = string_cat(yield, &size, &ptr, arg, Ustrlen(arg));
if (*s++ != '}') goto EXPAND_FAILED_CURLY;
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);
if (arg == NULL) goto EXPAND_FAILED;
while (isspace(*s)) s++;
if (*s++ != '}') goto EXPAND_FAILED_CURLY;
(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 */
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 */
while (isspace(*s)) s++;
if (*s == '{')
{
- sub[i] = expand_string_internal(s+1, TRUE, &s, skipping);
+ sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE);
if (sub[i] == NULL) goto EXPAND_FAILED;
if (*s++ != '}') goto EXPAND_FAILED_CURLY;
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";
}
+ /* Handle list operations */
+
+ case EITEM_FILTER:
+ case EITEM_MAP:
+ case EITEM_REDUCE:
+ {
+ int sep = 0;
+ int save_ptr = ptr;
+ uschar outsep[2] = { '\0', '\0' };
+ uschar *list, *expr, *temp;
+ uschar *save_iterate_item = iterate_item;
+ uschar *save_lookup_value = lookup_value;
+
+ while (isspace(*s)) s++;
+ if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+
+ list = expand_string_internal(s, TRUE, &s, skipping, TRUE);
+ if (list == NULL) goto EXPAND_FAILED;
+ if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+ if (item_type == EITEM_REDUCE)
+ {
+ while (isspace(*s)) s++;
+ if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+ temp = expand_string_internal(s, TRUE, &s, skipping, TRUE);
+ if (temp == NULL) goto EXPAND_FAILED;
+ lookup_value = temp;
+ if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+ }
+
+ while (isspace(*s)) s++;
+ if (*s++ != '{') goto EXPAND_FAILED_CURLY;
+
+ expr = s;
+
+ /* For EITEM_FILTER, call eval_condition once, with result discarded (as
+ if scanning a "false" part). This allows us to find the end of the
+ condition, because if the list is empty, we won't actually evaluate the
+ condition for real. For EITEM_MAP and EITEM_REDUCE, do the same, using
+ the normal internal expansion function. */
+
+ if (item_type == EITEM_FILTER)
+ {
+ temp = eval_condition(expr, NULL);
+ if (temp != NULL) s = temp;
+ }
+ else
+ {
+ temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE);
+ }
+
+ if (temp == NULL)
+ {
+ expand_string_message = string_sprintf("%s inside \"%s\" item",
+ expand_string_message, name);
+ goto EXPAND_FAILED;
+ }
+
+ 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++;
+ if (*s++ != '}')
+ {
+ expand_string_message = string_sprintf("missing } at end of \"%s\"",
+ name);
+ goto EXPAND_FAILED;
+ }
+
+ /* If we are skipping, we can now just move on to the next item. When
+ processing for real, we perform the iteration. */
+
+ if (skipping) continue;
+ while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)) != NULL)
+ {
+ *outsep = (uschar)sep; /* Separator as a string */
+
+ DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, iterate_item);
+
+ if (item_type == EITEM_FILTER)
+ {
+ BOOL condresult;
+ if (eval_condition(expr, &condresult) == NULL)
+ {
+ iterate_item = save_iterate_item;
+ lookup_value = save_lookup_value;
+ expand_string_message = string_sprintf("%s inside \"%s\" condition",
+ expand_string_message, name);
+ goto EXPAND_FAILED;
+ }
+ DEBUG(D_expand) debug_printf("%s: condition is %s\n", name,
+ condresult? "true":"false");
+ if (condresult)
+ temp = iterate_item; /* TRUE => include this item */
+ else
+ continue; /* FALSE => skip this item */
+ }
+
+ /* EITEM_MAP and EITEM_REDUCE */
+
+ else
+ {
+ temp = expand_string_internal(expr, TRUE, NULL, skipping, TRUE);
+ if (temp == NULL)
+ {
+ iterate_item = save_iterate_item;
+ expand_string_message = string_sprintf("%s inside \"%s\" item",
+ expand_string_message, name);
+ goto EXPAND_FAILED;
+ }
+ if (item_type == EITEM_REDUCE)
+ {
+ lookup_value = temp; /* Update the value of $value */
+ continue; /* and continue the iteration */
+ }
+ }
+
+ /* We reach here for FILTER if the condition is true, always for MAP,
+ and never for REDUCE. The value in "temp" is to be added to the output
+ list that is being created, ensuring that any occurrences of the
+ separator character are doubled. Unless we are dealing with the first
+ item of the output list, add in a space if the new item begins with the
+ 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);
+
+ /* Add the string in "temp" to the output list that we are building,
+ This is done in chunks by searching for the separator character. */
+
+ for (;;)
+ {
+ size_t seglen = Ustrcspn(temp, outsep);
+ yield = string_cat(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);
+ 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);
+ } /* End of iteration over the list loop */
+
+ /* REDUCE has generated no output above: output the final value of
+ $value. */
+
+ if (item_type == EITEM_REDUCE)
+ {
+ yield = string_cat(yield, &size, &ptr, lookup_value,
+ Ustrlen(lookup_value));
+ lookup_value = save_lookup_value; /* Restore $value */
+ }
+
+ /* FILTER and MAP generate lists: if they have generated anything, remove
+ the redundant final separator. Even though an empty item at the end of a
+ list does not count, this is tidier. */
+
+ else if (ptr != save_ptr) ptr--;
+
+ /* Restore preserved $item */
+
+ iterate_item = save_iterate_item;
+ continue;
+ }
+
+
/* 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
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]);
{
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);
if (sub == NULL) goto EXPAND_FAILED;
s++;
case EOP_EXPAND:
{
- uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping);
+ uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE);
if (expanded == NULL)
{
expand_string_message =
continue;
}
+ case EOP_ADDRESSES:
+ {
+ uschar outsep[2] = { ':', '\0' };
+ uschar *address, *error;
+ int save_ptr = ptr;
+ int start, end, domain; /* Not really used */
+
+ while (isspace(*sub)) sub++;
+ if (*sub == '>') { *outsep = *++sub; ++sub; }
+ parse_allow_group = TRUE;
+
+ for (;;)
+ {
+ uschar *p = parse_find_address_end(sub, FALSE);
+ uschar saveend = *p;
+ *p = '\0';
+ address = parse_extract_address(sub, &error, &start, &end, &domain,
+ FALSE);
+ *p = saveend;
+
+ /* Add the address to the output list that we are building. This is
+ done in chunks by searching for the separator character. At the
+ start, unless we are dealing with the first address of the output
+ list, add in a space if the new address begins with the separator
+ character, or is an empty string. */
+
+ if (address != NULL)
+ {
+ if (ptr != save_ptr && address[0] == *outsep)
+ yield = string_cat(yield, &size, &ptr, US" ", 1);
+
+ for (;;)
+ {
+ size_t seglen = Ustrcspn(address, outsep);
+ yield = string_cat(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);
+ 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);
+ }
+
+ if (saveend == '\0') break;
+ sub = p + 1;
+ }
+
+ /* If we have generated anything, remove the redundant final
+ separator. */
+
+ if (ptr != save_ptr) ptr--;
+ parse_allow_group = FALSE;
+ continue;
+ }
+
+
/* quote puts a string in quotes if it is empty or contains anything
other than alphamerics, underscore, dot, or hyphen.
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)
continue;
}
+ /* pseudo-random number less than N */
+
+ case EOP_RANDINT:
+ {
+ int max;
+ uschar *s;
+
+ max = expand_string_integer(sub, TRUE);
+ if (expand_string_message != NULL)
+ goto EXPAND_FAILED;
+ s = string_sprintf("%d", pseudo_random_number(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:
int newsize = 0;
if (ptr == 0)
{
- store_reset(yield);
+ if (resetok) store_reset(yield);
yield = NULL;
size = 0;
}
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);
DEBUG(D_expand)
{
debug_printf("expanding: %.*s\n result: %s\n", (int)(s - string), 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);
}
errno = 0;
expand_string_message = NULL; /* Indicates no error */
+
+/* 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 = strtol(CS s, CSS &endptr, 10);
if (endptr == s)