-/* $Cambridge: exim/src/src/expand.c,v 1.60 2006/09/18 14:49:23 ph10 Exp $ */
+/* $Cambridge: exim/src/src/expand.c,v 1.91 2007/10/04 13:23:05 tom Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
/* See the file NOTICE for conditions of use and distribution. */
#include "exim.h"
+/* Recursively called function */
+
+static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL);
+
#ifdef STAND_ALONE
#ifndef SUPPORT_CRYPTEQ
#define SUPPORT_CRYPTEQ
#endif
#endif
+#ifdef LOOKUP_LDAP
+#include "lookups/ldap.h"
+#endif
+
#ifdef SUPPORT_CRYPTEQ
#ifdef CRYPT_H
#include <crypt.h>
#endif
#endif
-#ifdef LOOKUP_LDAP
-#include "lookups/ldap.h"
-#endif
-
-
-
-/* Recursively called function */
+/* The handling of crypt16() is a mess. I will record below the analysis of the
+mess that was sent to me. We decided, however, to make changing this very low
+priority, because in practice people are moving away from the crypt()
+algorithms nowadays, so it doesn't seem worth it.
+
+<quote>
+There is an algorithm named "crypt16" in Ultrix and Tru64. It crypts
+the first 8 characters of the password using a 20-round version of crypt
+(standard crypt does 25 rounds). It then crypts the next 8 characters,
+or an empty block if the password is less than 9 characters, using a
+20-round version of crypt and the same salt as was used for the first
+block. Charaters after the first 16 are ignored. It always generates
+a 16-byte hash, which is expressed together with the salt as a string
+of 24 base 64 digits. Here are some links to peruse:
+
+ http://cvs.pld.org.pl/pam/pamcrypt/crypt16.c?rev=1.2
+ http://seclists.org/bugtraq/1999/Mar/0076.html
+
+There's a different algorithm named "bigcrypt" in HP-UX, Digital Unix,
+and OSF/1. This is the same as the standard crypt if given a password
+of 8 characters or less. If given more, it first does the same as crypt
+using the first 8 characters, then crypts the next 8 (the 9th to 16th)
+using as salt the first two base 64 digits from the first hash block.
+If the password is more than 16 characters then it crypts the 17th to 24th
+characters using as salt the first two base 64 digits from the second hash
+block. And so on: I've seen references to it cutting off the password at
+40 characters (5 blocks), 80 (10 blocks), or 128 (16 blocks). Some links:
+
+ http://cvs.pld.org.pl/pam/pamcrypt/bigcrypt.c?rev=1.2
+ http://seclists.org/bugtraq/1999/Mar/0109.html
+ http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/HTML/AA-Q0R2D-
+ TET1_html/sec.c222.html#no_id_208
+
+Exim has something it calls "crypt16". It will either use a native
+crypt16 or its own implementation. A native crypt16 will presumably
+be the one that I called "crypt16" above. The internal "crypt16"
+function, however, is a two-block-maximum implementation of what I called
+"bigcrypt". The documentation matches the internal code.
+
+I suspect that whoever did the "crypt16" stuff for Exim didn't realise
+that crypt16 and bigcrypt were different things.
+
+Exim uses the LDAP-style scheme identifier "{crypt16}" to refer
+to whatever it is using under that name. This unfortunately sets a
+precedent for using "{crypt16}" to identify two incompatible algorithms
+whose output can't be distinguished. With "{crypt16}" thus rendered
+ambiguous, I suggest you deprecate it and invent two new identifiers
+for the two algorithms.
+
+Both crypt16 and bigcrypt are very poor algorithms, btw. Hashing parts
+of the password separately means they can be cracked separately, so
+the double-length hash only doubles the cracking effort instead of
+squaring it. I recommend salted SHA-1 ({SSHA}), or the Blowfish-based
+bcrypt ({CRYPT}$2a$).
+</quote>
+*/
-static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL);
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,
static uschar *op_table_main[] = {
US"address",
+ US"addresses",
US"base62",
US"base62d",
US"domain",
US"nhash",
US"quote",
US"rfc2047",
+ US"rfc2047d",
US"rxquote",
US"s",
US"sha1",
enum {
EOP_ADDRESS = sizeof(op_table_underscore)/sizeof(uschar *),
+ EOP_ADDRESSES,
EOP_BASE62,
EOP_BASE62D,
EOP_DOMAIN,
EOP_NHASH,
EOP_QUOTE,
EOP_RFC2047,
+ EOP_RFC2047D,
EOP_RXQUOTE,
EOP_S,
EOP_SHA1,
US"eqi",
US"exists",
US"first_delivery",
+ US"forall",
+ US"forany",
US"ge",
US"gei",
US"gt",
ECOND_STR_EQI,
ECOND_EXISTS,
ECOND_FIRST_DELIVERY,
+ ECOND_FORALL,
+ ECOND_FORANY,
ECOND_STR_GE,
ECOND_STR_GEI,
ECOND_STR_GT,
vtype_stringptr, /* value is address of pointer to string */
vtype_msgbody, /* as stringptr, but read when first required */
vtype_msgbody_end, /* ditto, the end of the message */
- vtype_msgheaders, /* the message's headers */
+ vtype_msgheaders, /* the message's headers, processed */
+ vtype_msgheaders_raw, /* the message's headers, unprocessed */
vtype_localpart, /* extract local part from string */
vtype_domain, /* extract domain from string */
vtype_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 */
/* This table must be kept in alphabetical order. */
static var_entry var_table[] = {
+ /* WARNING: Do not invent variables whose names start acl_c or acl_m because
+ they will be confused with user-creatable ACL variables. */
{ "acl_verify_message", vtype_stringptr, &acl_verify_message },
{ "address_data", vtype_stringptr, &deliver_address_data },
{ "address_file", vtype_stringptr, &address_file },
{ "dk_signsall", vtype_dk_verify, NULL },
{ "dk_status", vtype_dk_verify, NULL },
{ "dk_testing", vtype_dk_verify, NULL },
+#endif
+#ifdef EXPERIMENTAL_DKIM
+ { "dkim_domain", vtype_stringptr, &dkim_signing_domain },
+ { "dkim_selector", vtype_stringptr, &dkim_signing_selector },
#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 },
{ "inode", vtype_ino, &deliver_inode },
{ "interface_address", vtype_stringptr, &interface_address },
{ "interface_port", vtype_int, &interface_port },
+ { "item", vtype_stringptr, &iterate_item },
#ifdef LOOKUP_LDAP
{ "ldap_dn", vtype_stringptr, &eldap_dn },
#endif
#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 },
{ "message_body_size", vtype_int, &message_body_size },
{ "message_exim_id", vtype_stringptr, &message_id },
{ "message_headers", vtype_msgheaders, NULL },
+ { "message_headers_raw", vtype_msgheaders_raw, NULL },
{ "message_id", vtype_stringptr, &message_id },
{ "message_linecount", vtype_int, &message_linecount },
{ "message_size", vtype_int, &message_size },
{ "rcpt_fail_count", vtype_int, &rcpt_fail_count },
{ "received_count", vtype_int, &received_count },
{ "received_for", vtype_stringptr, &received_for },
+ { "received_ip_address", vtype_stringptr, &interface_address },
+ { "received_port", vtype_int, &interface_port },
{ "received_protocol", vtype_stringptr, &received_protocol },
{ "received_time", vtype_int, &received_time },
{ "recipient_data", vtype_stringptr, &recipient_data },
{ "sender_rate_period", vtype_stringptr, &sender_rate_period },
{ "sender_rcvhost", vtype_stringptr, &sender_rcvhost },
{ "sender_verify_failure",vtype_stringptr, &sender_verify_failure },
+ { "sending_ip_address", vtype_stringptr, &sending_ip_address },
+ { "sending_port", vtype_int, &sending_port },
{ "smtp_active_hostname", vtype_stringptr, &smtp_active_hostname },
{ "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] },
newsize return the size of memory block that was obtained; may be NULL
if exists_only is TRUE
want_raw TRUE if called for $rh_ or $rheader_ variables; no processing,
- other than concatenating, will be done on the header
+ other than concatenating, will be done on the header. Also used
+ for $message_headers_raw.
charset name of charset to translate MIME words to; used only if
want_raw is false; if NULL, no translation is done (this is
used for $bh_ and $bheader_)
while (isspace(*t)) t++; /* remove leading white space */
ilen = h->slen - (t - h->text); /* length to insert */
+ /* Unless wanted raw, remove trailing whitespace, including the
+ newline. */
+
+ if (!want_raw)
+ while (ilen > 0 && isspace(t[ilen-1])) ilen--;
+
/* Set comma = 1 if handling a single header and it's one of those
that contains an address list, except when asked for raw headers. Only
need to do this once. */
/* First pass - compute total store needed; second pass - compute
total store used, including this header. */
- size += ilen + comma;
+ size += ilen + comma + 1; /* +1 for the newline */
/* Second pass - concatentate the data, up to a maximum. Note that
the loop stops when size hits the limit. */
{
if (size > header_insert_maxlen)
{
- ilen -= size - header_insert_maxlen;
+ ilen -= size - header_insert_maxlen - 1;
comma = 0;
}
Ustrncpy(ptr, t, ilen);
ptr += ilen;
- if (comma != 0 && ilen > 0)
+
+ /* For a non-raw header, put in the comma if needed, then add
+ back the newline we removed above, provided there was some text in
+ the header. */
+
+ if (!want_raw && ilen > 0)
{
- ptr[-1] = ',';
+ if (comma != 0) *ptr++ = ',';
*ptr++ = '\n';
}
}
}
}
- /* At end of first pass, truncate size if necessary, and get the buffer
- to hold the data, returning the buffer size. */
+ /* At end of first pass, return NULL if no header found. Then truncate size
+ if necessary, and get the buffer to hold the data, returning the buffer size.
+ */
if (i == 0)
{
}
}
-/* Remove a redundant added comma if present */
-
-if (comma != 0 && ptr > yield) ptr -= 2;
-
/* That's all we do for raw header expansion. */
if (want_raw)
*ptr = 0;
}
-/* Otherwise, we remove trailing whitespace, including newlines. Then we do RFC
-2047 decoding, translating the charset if requested. The rfc2047_decode2()
+/* Otherwise, remove a final newline and a redundant added comma. Then we do
+RFC 2047 decoding, translating the charset if requested. The rfc2047_decode2()
function can return an error with decoded data if the charset translation
fails. If decoding fails, it returns NULL. */
else
{
uschar *decoded, *error;
- while (ptr > yield && isspace(ptr[-1])) ptr--;
+ if (ptr > yield && ptr[-1] == '\n') ptr--;
+ if (ptr > yield && comma != 0 && ptr[-1] == ',') ptr--;
*ptr = 0;
decoded = rfc2047_decode2(yield, check_rfc2047_length, charset, '?', NULL,
newsize, &error);
int first = 0;
int last = var_table_size;
-/* Handle ACL variables, which are not in the table because their number may
-vary depending on a build-time setting. If the variable's name is not of the
-form acl_mddd or acl_cddd, where the d's are digits, fall through to look for
-other names that start with acl_. */
+/* Handle ACL variables, whose names are of the form acl_cxxx or acl_mxxx.
+Originally, xxx had to be a number in the range 0-9 (later 0-19), but from
+release 4.64 onwards arbitrary names are permitted, as long as the first 5
+characters are acl_c or acl_m and the sixth is either a digit or an underscore
+(this gave backwards compatibility at the changeover). There may be built-in
+variables whose names start acl_ but they should never start in this way. This
+slightly messy specification is a consequence of the history, needless to say.
-if (Ustrncmp(name, "acl_", 4) == 0)
- {
- uschar *endptr;
- int offset = -1;
- int max = 0;
-
- if (name[4] == 'm')
- {
- offset = ACL_CVARS;
- max = ACL_MVARS;
- }
- else if (name[4] == 'c')
- {
- offset = 0;
- max = ACL_CVARS;
- }
+If an ACL variable does not exist, treat it as empty, unless strict_acl_vars is
+set, in which case give an error. */
- if (offset >= 0)
- {
- int n = Ustrtoul(name + 5, &endptr, 10);
- if (*endptr == 0 && n < max)
- return (acl_var[offset + n] == NULL)? US"" : acl_var[offset + n];
- }
+if ((Ustrncmp(name, "acl_c", 5) == 0 || Ustrncmp(name, "acl_m", 5) == 0) &&
+ !isalpha(name[5]))
+ {
+ 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;
}
-/* Similarly for $auth<n> variables. */
+/* Handle $auth<n> variables. */
if (Ustrncmp(name, "auth", 4) == 0)
{
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 */
case vtype_msgheaders:
return find_header(NULL, exists_only, newsize, FALSE, NULL);
+ case vtype_msgheaders_raw:
+ 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 **)(var_table[middle].value);
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] = ' '; }
}
}
}
+/*************************************************
+* Elaborate message for bad variable *
+*************************************************/
+
+/* For the "unknown variable" message, take a look at the variable's name, and
+give additional information about possible ACL variables. The extra information
+is added on to expand_string_message.
+
+Argument: the name of the variable
+Returns: nothing
+*/
+
+static void
+check_variable_error_message(uschar *name)
+{
+if (Ustrncmp(name, "acl_", 4) == 0)
+ expand_string_message = string_sprintf("%s (%s)", expand_string_message,
+ (name[4] == 'c' || name[4] == 'm')?
+ (isalpha(name[5])?
+ US"6th character of a user-defined ACL variable must be a digit or underscore" :
+ US"strict_acl_vars is set" /* Syntax is OK, it has to be this */
+ ) :
+ US"user-defined ACL variables must start acl_c or acl_m");
+}
+
+
+
/*************************************************
* Read and evaluate a condition *
*************************************************/
s = read_name(name, 256, s+1, US"_");
- /* Test for a header's existence */
+ /* Test for a header's existence. If the name contains a closing brace
+ character, this may be a user error where the terminating colon has been
+ omitted. Set a flag to adjust a subsequent error message in this case. */
if (Ustrncmp(name, "h_", 2) == 0 ||
Ustrncmp(name, "rh_", 3) == 0 ||
Ustrncmp(name, "bheader_", 8) == 0)
{
s = read_header_name(name, 256, s);
+ if (Ustrchr(name, '}') != NULL) malformed_header = TRUE;
if (yield != NULL) *yield =
(find_header(name, TRUE, NULL, FALSE, NULL) != NULL) == testfor;
}
expand_string_message = (name[0] == 0)?
string_sprintf("variable name omitted after \"def:\"") :
string_sprintf("unknown variable \"%s\" after \"def:\"", name);
+ check_variable_error_message(name);
return NULL;
}
if (yield != NULL) *yield = (value[0] != 0) == testfor;
conditions that compare numbers do not start with a letter. This just saves
checking for them individually. */
- if (!isalpha(name[0]))
+ if (!isalpha(name[0]) && yield != NULL)
{
- num[i] = expand_string_integer(sub[i], FALSE);
- if (expand_string_message != NULL) return NULL;
+ if (sub[i][0] == 0)
+ {
+ num[i] = 0;
+ DEBUG(D_expand)
+ debug_printf("empty string cast to zero for numerical comparison\n");
+ }
+ else
+ {
+ num[i] = expand_string_integer(sub[i], FALSE);
+ if (expand_string_message != NULL) return NULL;
+ }
}
}
return ++s;
+ /* forall/forany: iterates a condition with different values */
+
+ case ECOND_FORALL:
+ case ECOND_FORANY:
+ {
+ int sep = 0;
+ uschar *save_iterate_item = iterate_item;
+
+ while (isspace(*s)) s++;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START;
+ sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL));
+ if (sub[0] == NULL) return NULL;
+ if (*s++ != '}') goto COND_FAILED_CURLY_END;
+
+ while (isspace(*s)) s++;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START;
+
+ sub[1] = s;
+
+ /* 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 it empty, we won't actually evaluate the condition for real. */
+
+ s = eval_condition(sub[1], NULL);
+ if (s == NULL)
+ {
+ expand_string_message = string_sprintf("%s inside \"%s\" condition",
+ expand_string_message, name);
+ return NULL;
+ }
+ while (isspace(*s)) s++;
+
+ if (*s++ != '}')
+ {
+ expand_string_message = string_sprintf("missing } at end of condition "
+ "inside \"%s\"", name);
+ return NULL;
+ }
+
+ if (yield != NULL) *yield = !testfor;
+ while ((iterate_item = string_nextinlist(&sub[0], &sep, NULL, 0)) != NULL)
+ {
+ DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, iterate_item);
+ if (eval_condition(sub[1], &tempcond) == NULL)
+ {
+ 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,
+ tempcond? "true":"false");
+
+ if (yield != NULL) *yield = (tempcond == testfor);
+ if (tempcond == (cond_type == ECOND_FORANY)) break;
+ }
+
+ iterate_item = save_iterate_item;
+ return s;
+ }
+
+
/* Unknown condition */
default:
* Evaluate numeric expression *
*************************************************/
-/* This is a set of mutually recursive functions that evaluate a simple
-arithmetic expression involving only + - * / and parentheses. The only one that
-is called from elsewhere is eval_expr, whose interface is:
+/* This is a set of mutually recursive functions that evaluate an arithmetic
+expression involving + - * / % & | ^ ~ << >> and parentheses. The only one of
+these functions that is called from elsewhere is eval_expr, whose interface is:
Arguments:
- sptr pointer to the pointer to the string - gets updated
- decimal TRUE if numbers are to be assumed decimal
- error pointer to where to put an error message - must be NULL on input
- endket TRUE if ')' must terminate - FALSE for external call
+ sptr pointer to the pointer to the string - gets updated
+ decimal TRUE if numbers are to be assumed decimal
+ error pointer to where to put an error message - must be NULL on input
+ endket TRUE if ')' must terminate - FALSE for external call
-
-Returns: on success: the value of the expression, with *error still NULL
- on failure: an undefined value, with *error = a message
+Returns: on success: the value of the expression, with *error still NULL
+ on failure: an undefined value, with *error = a message
*/
-static int eval_sumterm(uschar **, BOOL, uschar **);
+static int eval_op_or(uschar **, BOOL, uschar **);
+
static int
eval_expr(uschar **sptr, BOOL decimal, uschar **error, BOOL endket)
{
uschar *s = *sptr;
-int x = eval_sumterm(&s, decimal, error);
+int x = eval_op_or(&s, decimal, error);
if (*error == NULL)
{
- while (*s == '+' || *s == '-')
+ if (endket)
{
- int op = *s++;
- int y = eval_sumterm(&s, decimal, error);
- if (*error != NULL) break;
- if (op == '+') x += y; else x -= y;
- }
- if (*error == NULL)
- {
- if (endket)
- {
- if (*s != ')')
- *error = US"expecting closing parenthesis";
- else
- while (isspace(*(++s)));
- }
- else if (*s != 0) *error = US"expecting + or -";
+ if (*s != ')')
+ *error = US"expecting closing parenthesis";
+ else
+ while (isspace(*(++s)));
}
+ else if (*s != 0) *error = US"expecting operator";
}
-
*sptr = s;
return x;
}
+
static int
-eval_term(uschar **sptr, BOOL decimal, uschar **error)
+eval_number(uschar **sptr, BOOL decimal, uschar **error)
{
register int c;
int n;
uschar *s = *sptr;
while (isspace(*s)) s++;
c = *s;
-if (isdigit(c) || ((c == '-' || c == '+') && isdigit(s[1])))
+if (isdigit(c))
{
int count;
(void)sscanf(CS s, (decimal? "%d%n" : "%i%n"), &n, &count);
return n;
}
-static int eval_sumterm(uschar **sptr, BOOL decimal, uschar **error)
+
+static int eval_op_unary(uschar **sptr, BOOL decimal, uschar **error)
+{
+uschar *s = *sptr;
+int x;
+while (isspace(*s)) s++;
+if (*s == '+' || *s == '-' || *s == '~')
+ {
+ int op = *s++;
+ x = eval_op_unary(&s, decimal, error);
+ if (op == '-') x = -x;
+ else if (op == '~') x = ~x;
+ }
+else
+ {
+ x = eval_number(&s, decimal, error);
+ }
+*sptr = s;
+return x;
+}
+
+
+static int eval_op_mult(uschar **sptr, BOOL decimal, uschar **error)
{
uschar *s = *sptr;
-int x = eval_term(&s, decimal, error);
+int x = eval_op_unary(&s, decimal, error);
if (*error == NULL)
{
while (*s == '*' || *s == '/' || *s == '%')
{
int op = *s++;
- int y = eval_term(&s, decimal, error);
+ int y = eval_op_unary(&s, decimal, error);
if (*error != NULL) break;
if (op == '*') x *= y;
else if (op == '/') x /= y;
}
+static int eval_op_sum(uschar **sptr, BOOL decimal, uschar **error)
+{
+uschar *s = *sptr;
+int x = eval_op_mult(&s, decimal, error);
+if (*error == NULL)
+ {
+ while (*s == '+' || *s == '-')
+ {
+ int op = *s++;
+ int y = eval_op_mult(&s, decimal, error);
+ if (*error != NULL) break;
+ if (op == '+') x += y; else x -= y;
+ }
+ }
+*sptr = s;
+return x;
+}
+
+
+static int eval_op_shift(uschar **sptr, BOOL decimal, uschar **error)
+{
+uschar *s = *sptr;
+int x = eval_op_sum(&s, decimal, error);
+if (*error == NULL)
+ {
+ while ((*s == '<' || *s == '>') && s[1] == s[0])
+ {
+ int y;
+ int op = *s++;
+ s++;
+ y = eval_op_sum(&s, decimal, error);
+ if (*error != NULL) break;
+ if (op == '<') x <<= y; else x >>= y;
+ }
+ }
+*sptr = s;
+return x;
+}
+
+
+static int eval_op_and(uschar **sptr, BOOL decimal, uschar **error)
+{
+uschar *s = *sptr;
+int x = eval_op_shift(&s, decimal, error);
+if (*error == NULL)
+ {
+ while (*s == '&')
+ {
+ int y;
+ s++;
+ y = eval_op_shift(&s, decimal, error);
+ if (*error != NULL) break;
+ x &= y;
+ }
+ }
+*sptr = s;
+return x;
+}
+
+
+static int eval_op_xor(uschar **sptr, BOOL decimal, uschar **error)
+{
+uschar *s = *sptr;
+int x = eval_op_and(&s, decimal, error);
+if (*error == NULL)
+ {
+ while (*s == '^')
+ {
+ int y;
+ s++;
+ y = eval_op_and(&s, decimal, error);
+ if (*error != NULL) break;
+ x ^= y;
+ }
+ }
+*sptr = s;
+return x;
+}
+
+
+static int eval_op_or(uschar **sptr, BOOL decimal, uschar **error)
+{
+uschar *s = *sptr;
+int x = eval_op_xor(&s, decimal, error);
+if (*error == NULL)
+ {
+ while (*s == '|')
+ {
+ int y;
+ s++;
+ y = eval_op_xor(&s, decimal, error);
+ if (*error != NULL) break;
+ x |= y;
+ }
+ }
+*sptr = s;
+return x;
+}
+
/*************************************************
value = find_header(name, FALSE, &newsize, want_raw, charset);
/* If we didn't find the header, and the header contains a closing brace
- characters, this may be a user error where the terminating colon
+ character, this may be a user error where the terminating colon
has been omitted. Set a flag to adjust the error message in this case.
But there is no error here - nothing gets inserted. */
{
expand_string_message =
string_sprintf("unknown variable name \"%s\"", name);
+ check_variable_error_message(name);
goto EXPAND_FAILED;
}
}
*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);
else
{
shost.name = server_name;
- if (host_find_byname(&shost, NULL, NULL, FALSE) != HOST_FOUND)
+ if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE, NULL,
+ FALSE) != HOST_FOUND)
{
expand_string_message =
string_sprintf("no IP address found for host %s", shost.name);
else
{
+ int rc;
if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
{
expand_string_message = string_sprintf("failed to create socket: %s",
sockun.sun_family = AF_UNIX;
sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
sub_arg[0]);
- if(connect(fd, (struct sockaddr *)(&sockun), sizeof(sockun)) == -1)
+
+ sigalrm_seen = FALSE;
+ alarm(timeout);
+ rc = connect(fd, (struct sockaddr *)(&sockun), sizeof(sockun));
+ alarm(0);
+ if (sigalrm_seen)
+ {
+ expand_string_message = US "socket connect timed out";
+ goto SOCK_FAIL;
+ }
+ if (rc < 0)
{
expand_string_message = string_sprintf("failed to connect to socket "
"%s: %s", sub_arg[0], strerror(errno));
}
}
+ /* Shut down the sending side of the socket. This helps some servers to
+ recognise that it is their turn to do some work. Just in case some
+ system doesn't have this function, make it conditional. */
+
+ #ifdef SHUT_WR
+ shutdown(fd, SHUT_WR);
+ #endif
+
/* Now we need to read from the socket, under a timeout. The function
that reads a file can be used. */
}
+ /* 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);
+ 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);
+ 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);
+ }
+
+ 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);
+ 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
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.
continue;
}
+ /* RFC 2047 decode */
+
+ case EOP_RFC2047D:
+ {
+ int len;
+ uschar *error;
+ uschar *decoded = rfc2047_decode(sub, check_rfc2047_length,
+ headers_charset, '?', &len, &error);
+ if (error != NULL)
+ {
+ expand_string_message = error;
+ goto EXPAND_FAILED;
+ }
+ yield = string_cat(yield, &size, &ptr, decoded, len);
+ continue;
+ }
+
/* from_utf8 converts UTF-8 to 8859-1, turning non-existent chars into
underscores */
{
expand_string_message =
string_sprintf("unknown variable in \"${%s}\"", name);
+ check_variable_error_message(name);
goto EXPAND_FAILED;
}
len = Ustrlen(value);
errno = 0;
expand_string_message = NULL; /* Indicates no error */
-value = strtol(CS s, CSS &endptr, 0);
+value = strtol(CS s, CSS &endptr, 10);
if (endptr == s)
{