-/* $Cambridge: exim/src/src/expand.c,v 1.28 2005/06/17 08:23:28 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
/* 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, 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);
alphabetical order. */
static uschar *item_table[] = {
+ US"acl",
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",
US"tr" };
enum {
+ EITEM_ACL,
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"};
enum {
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"h",
US"hash",
US"hex2b64",
+ US"hexquote",
US"l",
US"lc",
US"length",
+ US"listcount",
+ US"listnamed",
US"mask",
US"md5",
US"nh",
US"nhash",
US"quote",
+ US"randint",
US"rfc2047",
+ US"rfc2047d",
US"rxquote",
US"s",
US"sha1",
enum {
EOP_ADDRESS = sizeof(op_table_underscore)/sizeof(uschar *),
+ EOP_ADDRESSES,
EOP_BASE62,
EOP_BASE62D,
EOP_DOMAIN,
EOP_H,
EOP_HASH,
EOP_HEX2B64,
+ EOP_HEXQUOTE,
EOP_L,
EOP_LC,
EOP_LENGTH,
+ EOP_LISTCOUNT,
+ EOP_LISTNAMED,
EOP_MASK,
EOP_MD5,
EOP_NH,
EOP_NHASH,
EOP_QUOTE,
+ EOP_RANDINT,
EOP_RFC2047,
+ EOP_RFC2047D,
EOP_RXQUOTE,
EOP_S,
EOP_SHA1,
US"==", /* Backward compatibility */
US">",
US">=",
+ US"acl",
US"and",
+ US"bool",
+ US"bool_lax",
US"crypteq",
US"def",
US"eq",
US"eqi",
US"exists",
US"first_delivery",
+ US"forall",
+ US"forany",
US"ge",
US"gei",
US"gt",
US"gti",
+ US"inlist",
+ US"inlisti",
US"isip",
US"isip4",
US"isip6",
US"match",
US"match_address",
US"match_domain",
+ US"match_ip",
US"match_local_part",
US"or",
US"pam",
ECOND_NUM_EE,
ECOND_NUM_G,
ECOND_NUM_GE,
+ ECOND_ACL,
ECOND_AND,
+ ECOND_BOOL,
+ ECOND_BOOL_LAX,
ECOND_CRYPTEQ,
ECOND_DEF,
ECOND_STR_EQ,
ECOND_STR_EQI,
ECOND_EXISTS,
ECOND_FIRST_DELIVERY,
+ ECOND_FORALL,
+ ECOND_FORANY,
ECOND_STR_GE,
ECOND_STR_GEI,
ECOND_STR_GT,
ECOND_STR_GTI,
+ ECOND_INLIST,
+ ECOND_INLISTI,
ECOND_ISIP,
ECOND_ISIP4,
ECOND_ISIP6,
ECOND_MATCH,
ECOND_MATCH_ADDRESS,
ECOND_MATCH_DOMAIN,
+ ECOND_MATCH_IP,
ECOND_MATCH_LOCAL_PART,
ECOND_OR,
ECOND_PAM,
/* 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_ino, /* value is address of ino_t (not always an int) */
vtype_uid, /* value is address of uid_t (not always an int) */
vtype_gid, /* value is address of gid_t (not always an int) */
+ vtype_bool, /* value is address of bool */
vtype_stringptr, /* value is address of pointer to string */
vtype_msgbody, /* as stringptr, but read when first required */
vtype_msgbody_end, /* ditto, the end of the message */
- vtype_msgheaders, /* the message's headers */
+ 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 */
+ vtype_string_func, /* value is string returned by given function */
vtype_todbsdin, /* value not used; generate BSD inbox tod */
vtype_tode, /* value not used; generate tod in epoch format */
+ vtype_todel, /* value not used; generate tod in epoch/usec format */
vtype_todf, /* value not used; generate full tod */
vtype_todl, /* value not used; generate log tod */
vtype_todlf, /* value not used; generate log file datestamp tod */
vtype_load_avg, /* value not used; result is int from os_getloadavg */
vtype_pspace, /* partition space; value is T/F for spool/log */
vtype_pinodes /* partition inodes; value is T/F for spool/log */
-#ifdef EXPERIMENTAL_DOMAINKEYS
- ,vtype_dk_verify /* Serve request out of DomainKeys verification structure */
-#endif
+ #ifndef DISABLE_DKIM
+ ,vtype_dkim /* Lookup of value in DKIM signature */
+ #endif
};
+static uschar * fn_recipients(void);
+
/* This table must be kept in alphabetical order. */
static var_entry var_table[] = {
- { "acl_c0", vtype_stringptr, &acl_var[0] },
- { "acl_c1", vtype_stringptr, &acl_var[1] },
- { "acl_c2", vtype_stringptr, &acl_var[2] },
- { "acl_c3", vtype_stringptr, &acl_var[3] },
- { "acl_c4", vtype_stringptr, &acl_var[4] },
- { "acl_c5", vtype_stringptr, &acl_var[5] },
- { "acl_c6", vtype_stringptr, &acl_var[6] },
- { "acl_c7", vtype_stringptr, &acl_var[7] },
- { "acl_c8", vtype_stringptr, &acl_var[8] },
- { "acl_c9", vtype_stringptr, &acl_var[9] },
- { "acl_m0", vtype_stringptr, &acl_var[10] },
- { "acl_m1", vtype_stringptr, &acl_var[11] },
- { "acl_m2", vtype_stringptr, &acl_var[12] },
- { "acl_m3", vtype_stringptr, &acl_var[13] },
- { "acl_m4", vtype_stringptr, &acl_var[14] },
- { "acl_m5", vtype_stringptr, &acl_var[15] },
- { "acl_m6", vtype_stringptr, &acl_var[16] },
- { "acl_m7", vtype_stringptr, &acl_var[17] },
- { "acl_m8", vtype_stringptr, &acl_var[18] },
- { "acl_m9", vtype_stringptr, &acl_var[19] },
+ /* WARNING: Do not invent variables whose names start acl_c or acl_m because
+ they will be confused with user-creatable ACL variables. */
+ { "acl_arg1", vtype_stringptr, &acl_arg[0] },
+ { "acl_arg2", vtype_stringptr, &acl_arg[1] },
+ { "acl_arg3", vtype_stringptr, &acl_arg[2] },
+ { "acl_arg4", vtype_stringptr, &acl_arg[3] },
+ { "acl_arg5", vtype_stringptr, &acl_arg[4] },
+ { "acl_arg6", vtype_stringptr, &acl_arg[5] },
+ { "acl_arg7", vtype_stringptr, &acl_arg[6] },
+ { "acl_arg8", vtype_stringptr, &acl_arg[7] },
+ { "acl_arg9", vtype_stringptr, &acl_arg[8] },
+ { "acl_narg", vtype_int, &acl_narg },
{ "acl_verify_message", vtype_stringptr, &acl_verify_message },
{ "address_data", vtype_stringptr, &deliver_address_data },
{ "address_file", vtype_stringptr, &address_file },
{ "address_pipe", vtype_stringptr, &address_pipe },
+ { "authenticated_fail_id",vtype_stringptr, &authenticated_fail_id },
{ "authenticated_id", vtype_stringptr, &authenticated_id },
{ "authenticated_sender",vtype_stringptr, &authenticated_sender },
{ "authentication_failed",vtype_int, &authentication_failed },
+#ifdef WITH_CONTENT_SCAN
+ { "av_failed", vtype_int, &av_failed },
+#endif
#ifdef EXPERIMENTAL_BRIGHTMAIL
{ "bmi_alt_location", vtype_stringptr, &bmi_alt_location },
{ "bmi_base64_tracker_verdict", vtype_stringptr, &bmi_base64_tracker_verdict },
{ "compile_date", vtype_stringptr, &version_date },
{ "compile_number", vtype_stringptr, &version_cnumber },
{ "csa_status", vtype_stringptr, &csa_status },
+#ifdef EXPERIMENTAL_DCC
+ { "dcc_header", vtype_stringptr, &dcc_header },
+ { "dcc_result", vtype_stringptr, &dcc_result },
+#endif
#ifdef WITH_OLD_DEMIME
{ "demime_errorlevel", vtype_int, &demime_errorlevel },
{ "demime_reason", vtype_stringptr, &demime_reason },
#endif
-#ifdef EXPERIMENTAL_DOMAINKEYS
- { "dk_domain", vtype_stringptr, &dk_signing_domain },
- { "dk_is_signed", vtype_dk_verify, NULL },
- { "dk_result", vtype_dk_verify, NULL },
- { "dk_selector", vtype_stringptr, &dk_signing_selector },
- { "dk_sender", vtype_dk_verify, NULL },
- { "dk_sender_domain", vtype_dk_verify, NULL },
- { "dk_sender_local_part",vtype_dk_verify, NULL },
- { "dk_sender_source", vtype_dk_verify, NULL },
- { "dk_signsall", vtype_dk_verify, NULL },
- { "dk_status", vtype_dk_verify, NULL },
- { "dk_testing", vtype_dk_verify, NULL },
+#ifndef DISABLE_DKIM
+ { "dkim_algo", vtype_dkim, (void *)DKIM_ALGO },
+ { "dkim_bodylength", vtype_dkim, (void *)DKIM_BODYLENGTH },
+ { "dkim_canon_body", vtype_dkim, (void *)DKIM_CANON_BODY },
+ { "dkim_canon_headers", vtype_dkim, (void *)DKIM_CANON_HEADERS },
+ { "dkim_copiedheaders", vtype_dkim, (void *)DKIM_COPIEDHEADERS },
+ { "dkim_created", vtype_dkim, (void *)DKIM_CREATED },
+ { "dkim_cur_signer", vtype_stringptr, &dkim_cur_signer },
+ { "dkim_domain", vtype_stringptr, &dkim_signing_domain },
+ { "dkim_expires", vtype_dkim, (void *)DKIM_EXPIRES },
+ { "dkim_headernames", vtype_dkim, (void *)DKIM_HEADERNAMES },
+ { "dkim_identity", vtype_dkim, (void *)DKIM_IDENTITY },
+ { "dkim_key_granularity",vtype_dkim, (void *)DKIM_KEY_GRANULARITY },
+ { "dkim_key_nosubdomains",vtype_dkim, (void *)DKIM_NOSUBDOMAINS },
+ { "dkim_key_notes", vtype_dkim, (void *)DKIM_KEY_NOTES },
+ { "dkim_key_srvtype", vtype_dkim, (void *)DKIM_KEY_SRVTYPE },
+ { "dkim_key_testing", vtype_dkim, (void *)DKIM_KEY_TESTING },
+ { "dkim_selector", vtype_stringptr, &dkim_signing_selector },
+ { "dkim_signers", vtype_stringptr, &dkim_signers },
+ { "dkim_verify_reason", vtype_dkim, (void *)DKIM_VERIFY_REASON },
+ { "dkim_verify_status", vtype_dkim, (void *)DKIM_VERIFY_STATUS},
+#endif
+#ifdef EXPERIMENTAL_DMARC
+ { "dmarc_ar_header", vtype_stringptr, &dmarc_ar_header },
+ { "dmarc_status", vtype_stringptr, &dmarc_status },
+ { "dmarc_status_text", vtype_stringptr, &dmarc_status_text },
+ { "dmarc_used_domain", vtype_stringptr, &dmarc_used_domain },
#endif
{ "dnslist_domain", vtype_stringptr, &dnslist_domain },
+ { "dnslist_matched", vtype_stringptr, &dnslist_matched },
{ "dnslist_text", vtype_stringptr, &dnslist_text },
{ "dnslist_value", vtype_stringptr, &dnslist_value },
{ "domain", vtype_stringptr, &deliver_domain },
#ifdef WITH_OLD_DEMIME
{ "found_extension", vtype_stringptr, &found_extension },
#endif
+ { "headers_added", vtype_string_func, &fn_hdrs_added },
{ "home", vtype_stringptr, &deliver_home },
{ "host", vtype_stringptr, &deliver_host },
{ "host_address", vtype_stringptr, &deliver_host_address },
{ "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 },
{ "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure },
- { "recipients", vtype_recipients, NULL },
+ { "recipients", vtype_string_func, &fn_recipients },
{ "recipients_count", vtype_int, &recipients_count },
#ifdef WITH_CONTENT_SCAN
{ "regex_match_string", vtype_stringptr, ®ex_match_string },
{ "reply_address", vtype_reply, NULL },
{ "return_path", vtype_stringptr, &return_path },
{ "return_size_limit", vtype_int, &bounce_return_size_limit },
+ { "router_name", vtype_stringptr, &router_name },
{ "runrc", vtype_int, &runrc },
{ "self_hostname", vtype_stringptr, &self_hostname },
{ "sender_address", vtype_stringptr, &sender_address },
{ "sender_helo_name", vtype_stringptr, &sender_helo_name },
{ "sender_host_address", vtype_stringptr, &sender_host_address },
{ "sender_host_authenticated",vtype_stringptr, &sender_host_authenticated },
+ { "sender_host_dnssec", vtype_bool, &sender_host_dnssec },
{ "sender_host_name", vtype_host_lookup, NULL },
{ "sender_host_port", vtype_int, &sender_host_port },
{ "sender_ident", vtype_stringptr, &sender_ident },
{ "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_argument", vtype_stringptr, &smtp_command_argument },
+ { "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_certificate_verified", vtype_int, &tls_certificate_verified },
- { "tls_cipher", vtype_stringptr, &tls_cipher },
- { "tls_peerdn", vtype_stringptr, &tls_peerdn },
+
+ /* The non-(in,out) variables are now deprecated */
+ { "tls_bits", vtype_int, &tls_in.bits },
+ { "tls_certificate_verified", vtype_int, &tls_in.certificate_verified },
+ { "tls_cipher", vtype_stringptr, &tls_in.cipher },
+
+ { "tls_in_bits", vtype_int, &tls_in.bits },
+ { "tls_in_certificate_verified", vtype_int, &tls_in.certificate_verified },
+ { "tls_in_cipher", vtype_stringptr, &tls_in.cipher },
+ { "tls_in_peerdn", vtype_stringptr, &tls_in.peerdn },
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+ { "tls_in_sni", vtype_stringptr, &tls_in.sni },
+#endif
+ { "tls_out_bits", vtype_int, &tls_out.bits },
+ { "tls_out_certificate_verified", vtype_int,&tls_out.certificate_verified },
+ { "tls_out_cipher", vtype_stringptr, &tls_out.cipher },
+ { "tls_out_peerdn", vtype_stringptr, &tls_out.peerdn },
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+ { "tls_out_sni", vtype_stringptr, &tls_out.sni },
+#endif
+
+ { "tls_peerdn", vtype_stringptr, &tls_in.peerdn }, /* mind the alphabetical order! */
+#if defined(SUPPORT_TLS) && !defined(USE_GNUTLS)
+ { "tls_sni", vtype_stringptr, &tls_in.sni }, /* mind the alphabetical order! */
+#endif
+
{ "tod_bsdinbox", vtype_todbsdin, NULL },
{ "tod_epoch", vtype_tode, NULL },
+ { "tod_epoch_l", vtype_todel, NULL },
{ "tod_full", vtype_todf, NULL },
{ "tod_log", vtype_todl, NULL },
{ "tod_logfile", vtype_todlf, NULL },
{ "tod_zone", vtype_todzone, NULL },
{ "tod_zulu", vtype_todzulu, NULL },
+ { "transport_name", vtype_stringptr, &transport_name },
{ "value", vtype_stringptr, &lookup_value },
{ "version_number", vtype_stringptr, &version_string },
{ "warn_message_delay", vtype_stringptr, &warnmsg_delay },
/* For textual hashes */
-static char *hashcodes = "abcdefghijklmnopqrtsuvwxyz"
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "0123456789";
+static const char *hashcodes = "abcdefghijklmnopqrtsuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "0123456789";
enum { HMAC_MD5, HMAC_SHA1 };
/* This function is called to expand a string, and test the result for a "true"
or "false" value. Failure of the expansion yields FALSE; logged unless it was a
-forced fail or lookup defer. All store used by the function can be released on
-exit.
+forced fail or lookup defer.
+
+We used to release all store used, but this is not not safe due
+to ${dlfunc } and ${acl }. In any case expand_string_internal()
+is reasonably careful to release what it can.
+
+The actual false-value tests should be replicated for ECOND_BOOL_LAX.
Arguments:
condition the condition string
expand_check_condition(uschar *condition, uschar *m1, uschar *m2)
{
int rc;
-void *reset_point = store_get(0);
uschar *ss = expand_string(condition);
if (ss == NULL)
{
}
rc = ss[0] != 0 && Ustrcmp(ss, "0") != 0 && strcmpic(ss, US"no") != 0 &&
strcmpic(ss, US"false") != 0;
-store_reset(reset_point);
return rc;
}
+
+/*************************************************
+* Pseudo-random number generation *
+*************************************************/
+
+/* Pseudo-random number generation. The result is not "expected" to be
+cryptographically strong but not so weak that someone will shoot themselves
+in the foot using it as a nonce in some email header scheme or whatever
+weirdness they'll twist this into. The result should ideally handle fork().
+
+However, if we're stuck unable to provide this, then we'll fall back to
+appallingly bad randomness.
+
+If SUPPORT_TLS is defined then this will not be used except as an emergency
+fallback.
+
+Arguments:
+ max range maximum
+Returns a random number in range [0, max-1]
+*/
+
+#ifdef SUPPORT_TLS
+# define vaguely_random_number vaguely_random_number_fallback
+#endif
+int
+vaguely_random_number(int max)
+{
+#ifdef SUPPORT_TLS
+# undef vaguely_random_number
+#endif
+ static pid_t pid = 0;
+ pid_t p2;
+#if defined(HAVE_SRANDOM) && !defined(HAVE_SRANDOMDEV)
+ struct timeval tv;
+#endif
+
+ p2 = getpid();
+ if (p2 != pid)
+ {
+ if (pid != 0)
+ {
+
+#ifdef HAVE_ARC4RANDOM
+ /* cryptographically strong randomness, common on *BSD platforms, not
+ so much elsewhere. Alas. */
+ arc4random_stir();
+#elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV)
+#ifdef HAVE_SRANDOMDEV
+ /* uses random(4) for seeding */
+ srandomdev();
+#else
+ gettimeofday(&tv, NULL);
+ srandom(tv.tv_sec | tv.tv_usec | getpid());
+#endif
+#else
+ /* Poor randomness and no seeding here */
+#endif
+
+ }
+ pid = p2;
+ }
+
+#ifdef HAVE_ARC4RANDOM
+ return arc4random() % max;
+#elif defined(HAVE_SRANDOM) || defined(HAVE_SRANDOMDEV)
+ return random() % max;
+#else
+ /* This one returns a 16-bit number, definitely not crypto-strong */
+ return random_number(max);
+#endif
+}
+
+
+
+
/*************************************************
* Pick out a name from a string *
*************************************************/
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, TRUE, charset, '?', NULL, newsize, &error);
+ decoded = rfc2047_decode2(yield, check_rfc2047_length, charset, '?', NULL,
+ newsize, &error);
if (error != NULL)
{
DEBUG(D_any) debug_printf("*** error in RFC 2047 decoding: %s\n"
+/*************************************************
+* Return list of recipients *
+*************************************************/
+/* A recipients list is available only during system message filtering,
+during ACL processing after DATA, and while expanding pipe commands
+generated from a system filter, but not elsewhere. */
+
+static uschar *
+fn_recipients(void)
+{
+if (!enable_dollar_recipients) return NULL; else
+ {
+ int size = 128;
+ int ptr = 0;
+ int i;
+ uschar * s = store_get(size);
+ for (i = 0; i < recipients_count; i++)
+ {
+ if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2);
+ s = string_cat(s, &size, &ptr, recipients_list[i].address,
+ Ustrlen(recipients_list[i].address));
+ }
+ s[ptr] = 0; /* string_cat() leaves room */
+ return s;
+ }
+}
+
+
/*************************************************
* Find value of a variable *
*************************************************/
int first = 0;
int last = var_table_size;
+/* 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 an ACL variable does not exist, treat it as empty, unless strict_acl_vars is
+set, in which case give an error. */
+
+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;
+ }
+
+/* Handle $auth<n> variables. */
+
+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];
+ }
+
+/* For all other variables, search the table */
+
while (last > first)
{
uschar *s, *domain;
if (c < 0) { last = middle; continue; }
/* Found an existing variable. If in skipping state, the value isn't needed,
- and we want to avoid processing (such as looking up up the host name). */
+ and we want to avoid processing (such as looking up the host name). */
if (skipping) return US"";
case vtype_filter_int:
if (!filter_running) return NULL;
/* Fall through */
-
-#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 = "0"; break;
- case DK_EXIM_ADDRESS_FROM_FROM: s = "from"; break;
- case DK_EXIM_ADDRESS_FROM_SENDER: s = "sender"; break;
- }
-
- if (Ustrcmp(var_table[middle].name, "dk_status") == 0)
- switch(dk_verify_block->result) {
- case DK_EXIM_RESULT_ERR: s = "error"; break;
- case DK_EXIM_RESULT_BAD_FORMAT: s = "bad format"; break;
- case DK_EXIM_RESULT_NO_KEY: s = "no key"; break;
- case DK_EXIM_RESULT_NO_SIGNATURE: s = "no signature"; break;
- case DK_EXIM_RESULT_REVOKED: s = "revoked"; break;
- case DK_EXIM_RESULT_NON_PARTICIPANT: s = "non-participant"; break;
- case DK_EXIM_RESULT_GOOD: s = "good"; break;
- case DK_EXIM_RESULT_BAD: s = "bad"; break;
- }
-
- if (Ustrcmp(var_table[middle].name, "dk_signsall") == 0)
- s = (dk_verify_block->signsall)? "1" : "0";
-
- if (Ustrcmp(var_table[middle].name, "dk_testing") == 0)
- s = (dk_verify_block->testing)? "1" : "0";
-
- if (Ustrcmp(var_table[middle].name, "dk_is_signed") == 0)
- s = (dk_verify_block->is_signed)? "1" : "0";
-
- return (s == NULL)? US"" : s;
-#endif
-
+ /* VVVVVVVVVVVV */
case vtype_int:
sprintf(CS var_buffer, "%d", *(int *)(var_table[middle].value)); /* Integer */
return var_buffer;
sprintf(CS var_buffer, "%ld", (long int)(*(uid_t *)(var_table[middle].value))); /* uid */
return var_buffer;
+ case vtype_bool:
+ sprintf(CS var_buffer, "%s", *(BOOL *)(var_table[middle].value) ? "yes" : "no"); /* bool */
+ return var_buffer;
+
case vtype_stringptr: /* Pointer to string */
s = *((uschar **)(var_table[middle].value));
return (s == NULL)? US"" : s;
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 */
domain = Ustrrchr(s, '@');
if (domain == NULL) return s;
if (domain - s > sizeof(var_buffer) - 1)
- log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than %d in "
- "string expansion", sizeof(var_buffer));
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "local part longer than " SIZE_T_FMT
+ " in string expansion", sizeof(var_buffer));
Ustrncpy(var_buffer, s, domain - s);
var_buffer[domain - s] = 0;
return var_buffer;
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] = ' '; }
}
}
}
case vtype_tode: /* Unix epoch time of day */
return tod_stamp(tod_epoch);
+ case vtype_todel: /* Unix epoch/usec time of day */
+ return tod_stamp(tod_epoch_l);
+
case vtype_todf: /* Full time of day */
return tod_stamp(tod_full);
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, FALSE,
+ s = find_header(US"reply-to:", exists_only, newsize, TRUE,
headers_charset);
+ if (s != NULL) while (isspace(*s)) s++;
if (s == NULL || *s == 0)
- s = find_header(US"from:", exists_only, newsize, FALSE, headers_charset);
+ {
+ *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;
- /* A recipients list is available only during system message filtering,
- during ACL processing after DATA, and while expanding pipe commands
- generated from a system filter, but not elsewhere. */
-
- case vtype_recipients:
- if (!enable_dollar_recipients) return NULL; else
+ case vtype_string_func:
{
- int size = 128;
- int ptr = 0;
- int i;
- s = store_get(size);
- for (i = 0; i < recipients_count; i++)
- {
- if (i != 0) s = string_cat(s, &size, &ptr, US", ", 2);
- s = string_cat(s, &size, &ptr, recipients_list[i].address,
- Ustrlen(recipients_list[i].address));
- }
- s[ptr] = 0; /* string_cat() leaves room */
+ uschar * (*fn)() = var_table[middle].value;
+ return fn();
}
- return s;
case vtype_pspace:
{
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
+
}
}
+void
+modify_variable(uschar *name, void * value)
+{
+int first = 0;
+int last = var_table_size;
+
+while (last > first)
+ {
+ int middle = (first + last)/2;
+ int c = Ustrcmp(name, var_table[middle].name);
+
+ if (c > 0) { first = middle + 1; continue; }
+ if (c < 0) { last = middle; continue; }
+
+ /* Found an existing variable; change the item it refers to */
+ var_table[middle].value = value;
+ return;
+ }
+return; /* Unknown variable name, fail silently */
+}
+
+
+
+
+
/*************************************************
* Read and expand substrings *
*************************************************/
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++;
+/*************************************************
+* 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");
+}
+
+
+
+/*
+Load args from sub array to globals, and call acl_check().
+Sub array will be corrupted on return.
+
+Returns: OK access is granted by an ACCEPT verb
+ DISCARD access is granted by a DISCARD verb
+ FAIL access is denied
+ FAIL_DROP access is denied; drop the connection
+ DEFER can't tell at the moment
+ ERROR disaster
+*/
+static int
+eval_acl(uschar ** sub, int nsub, uschar ** user_msgp)
+{
+int i;
+uschar *tmp;
+int sav_narg = acl_narg;
+int ret;
+extern int acl_where;
+
+if(--nsub > sizeof(acl_arg)/sizeof(*acl_arg)) nsub = sizeof(acl_arg)/sizeof(*acl_arg);
+for (i = 0; i < nsub && sub[i+1]; i++)
+ {
+ tmp = acl_arg[i];
+ acl_arg[i] = sub[i+1]; /* place callers args in the globals */
+ sub[i+1] = tmp; /* stash the old args using our caller's storage */
+ }
+acl_narg = i;
+while (i < nsub)
+ {
+ sub[i+1] = acl_arg[i];
+ acl_arg[i++] = NULL;
+ }
+
+DEBUG(D_expand)
+ debug_printf("expanding: acl: %s arg: %s%s\n",
+ sub[0],
+ acl_narg>0 ? acl_arg[0] : US"<none>",
+ acl_narg>1 ? " +more" : "");
+
+ret = acl_eval(acl_where, sub[0], user_msgp, &tmp);
+
+for (i = 0; i < nsub; i++)
+ acl_arg[i] = sub[i+1]; /* restore old args */
+acl_narg = sav_narg;
+
+return ret;
+}
+
+
+
+
/*************************************************
* Read and evaluate a condition *
*************************************************/
BOOL testfor = TRUE;
BOOL tempcond, combined_cond;
BOOL *subcondptr;
+BOOL sub2_honour_dollar = TRUE;
int i, rc, cond_type, roffset;
-int num[2];
+int_eximarith_t num[2];
struct stat statbuf;
uschar name[256];
-uschar *sub[4];
+uschar *sub[10];
const pcre *re;
const uschar *rerror;
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);
+ /* {-for-text-editors */
+ 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;
case ECOND_PWCHECK:
while (isspace(*s)) s++;
- if (*s != '{') goto COND_FAILED_CURLY_START;
+ if (*s != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
- sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL);
+ sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE);
if (sub[0] == NULL) return NULL;
+ /* {-for-text-editors */
if (*s++ != '}') goto COND_FAILED_CURLY_END;
if (yield == NULL) return s; /* No need to run the test if skipping */
case ECOND_ISIP4:
case ECOND_ISIP6:
rc = string_is_ip_address(sub[0], NULL);
- *yield = ((cond_type == ECOND_ISIP)? (rc > 0) :
+ *yield = ((cond_type == ECOND_ISIP)? (rc != 0) :
(cond_type == ECOND_ISIP4)? (rc == 4) : (rc == 6)) == testfor;
break;
return s;
+ /* call ACL (in a conditional context). Accept true, deny false.
+ Defer is a forced-fail. Anything set by message= goes to $value.
+ Up to ten parameters are used; we use the braces round the name+args
+ like the saslauthd condition does, to permit a variable number of args.
+ See also the expansion-item version EITEM_ACL and the traditional
+ acl modifier ACLC_ACL.
+ */
+
+ case ECOND_ACL:
+ /* ${if acl {{name}{arg1}{arg2}...} {yes}{no}} */
+ {
+ uschar *user_msg;
+ BOOL cond = FALSE;
+ int size = 0;
+ int ptr = 0;
+
+ while (isspace(*s)) s++;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START; /*}*/
+
+ switch(read_subs(sub, sizeof(sub)/sizeof(*sub), 1,
+ &s, yield == NULL, TRUE, US"acl"))
+ {
+ case 1: expand_string_message = US"too few arguments or bracketing "
+ "error for acl";
+ case 2:
+ case 3: return NULL;
+ }
+
+ if (yield != NULL) switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+ {
+ case OK:
+ cond = TRUE;
+ case FAIL:
+ lookup_value = NULL;
+ if (user_msg)
+ {
+ lookup_value = string_cat(NULL, &size, &ptr, user_msg, Ustrlen(user_msg));
+ lookup_value[ptr] = '\0';
+ }
+ *yield = cond == testfor;
+ break;
+
+ case DEFER:
+ expand_string_forcedfail = TRUE;
+ default:
+ expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
+ return NULL;
+ }
+ return s;
+ }
+
+
/* saslauthd: does Cyrus saslauthd authentication. Four parameters are used:
${if saslauthd {{username}{password}{service}{realm}} {yes}[no}}
However, the last two are optional. That is why the whole set is enclosed
- in their own set or braces. */
+ in their own set of braces. */
case ECOND_SASLAUTHD:
#ifndef CYRUS_SASLAUTHD_SOCKET
goto COND_FAILED_NOT_COMPILED;
#else
while (isspace(*s)) s++;
- if (*s++ != '{') goto COND_FAILED_CURLY_START;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
switch(read_subs(sub, 4, 2, &s, yield == NULL, TRUE, US"saslauthd"))
{
case 1: expand_string_message = US"too few arguments or bracketing "
/* 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;
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)
{
- uschar *endptr;
- num[i] = (int)Ustrtol((const uschar *)sub[i], &endptr, 10);
- if (tolower(*endptr) == 'k')
- {
- num[i] *= 1024;
- endptr++;
- }
- else if (tolower(*endptr) == 'm')
+ if (sub[i][0] == 0)
{
- num[i] *= 1024*1024;
- endptr++;
+ num[i] = 0;
+ DEBUG(D_expand)
+ debug_printf("empty string cast to zero for numerical comparison\n");
}
- while (isspace(*endptr)) endptr++;
- if (*endptr != 0)
+ else
{
- expand_string_message = string_sprintf("\"%s\" is not a number",
- sub[i]);
- return NULL;
+ num[i] = expand_string_integer(sub[i], FALSE);
+ if (expand_string_message != NULL) return NULL;
}
}
}
{
case ECOND_NUM_E:
case ECOND_NUM_EE:
- *yield = (num[0] == num[1]) == testfor;
+ tempcond = (num[0] == num[1]);
break;
case ECOND_NUM_G:
- *yield = (num[0] > num[1]) == testfor;
+ tempcond = (num[0] > num[1]);
break;
case ECOND_NUM_GE:
- *yield = (num[0] >= num[1]) == testfor;
+ tempcond = (num[0] >= num[1]);
break;
case ECOND_NUM_L:
- *yield = (num[0] < num[1]) == testfor;
+ tempcond = (num[0] < num[1]);
break;
case ECOND_NUM_LE:
- *yield = (num[0] <= num[1]) == testfor;
+ tempcond = (num[0] <= num[1]);
break;
case ECOND_STR_LT:
- *yield = (Ustrcmp(sub[0], sub[1]) < 0) == testfor;
+ tempcond = (Ustrcmp(sub[0], sub[1]) < 0);
break;
case ECOND_STR_LTI:
- *yield = (strcmpic(sub[0], sub[1]) < 0) == testfor;
+ tempcond = (strcmpic(sub[0], sub[1]) < 0);
break;
case ECOND_STR_LE:
- *yield = (Ustrcmp(sub[0], sub[1]) <= 0) == testfor;
+ tempcond = (Ustrcmp(sub[0], sub[1]) <= 0);
break;
case ECOND_STR_LEI:
- *yield = (strcmpic(sub[0], sub[1]) <= 0) == testfor;
+ tempcond = (strcmpic(sub[0], sub[1]) <= 0);
break;
case ECOND_STR_EQ:
- *yield = (Ustrcmp(sub[0], sub[1]) == 0) == testfor;
+ tempcond = (Ustrcmp(sub[0], sub[1]) == 0);
break;
case ECOND_STR_EQI:
- *yield = (strcmpic(sub[0], sub[1]) == 0) == testfor;
+ tempcond = (strcmpic(sub[0], sub[1]) == 0);
break;
case ECOND_STR_GT:
- *yield = (Ustrcmp(sub[0], sub[1]) > 0) == testfor;
+ tempcond = (Ustrcmp(sub[0], sub[1]) > 0);
break;
case ECOND_STR_GTI:
- *yield = (strcmpic(sub[0], sub[1]) > 0) == testfor;
+ tempcond = (strcmpic(sub[0], sub[1]) > 0);
break;
case ECOND_STR_GE:
- *yield = (Ustrcmp(sub[0], sub[1]) >= 0) == testfor;
+ tempcond = (Ustrcmp(sub[0], sub[1]) >= 0);
break;
case ECOND_STR_GEI:
- *yield = (strcmpic(sub[0], sub[1]) >= 0) == testfor;
+ tempcond = (strcmpic(sub[0], sub[1]) >= 0);
break;
case ECOND_MATCH: /* Regular expression match */
"\"%s\": %s at offset %d", sub[1], rerror, roffset);
return NULL;
}
- *yield = regex_match_and_setup(re, sub[0], 0, -1) == testfor;
+ tempcond = regex_match_and_setup(re, sub[0], 0, -1);
break;
case ECOND_MATCH_ADDRESS: /* Match in an address list */
MCL_DOMAIN + MCL_NOEXPAND, TRUE, NULL);
goto MATCHED_SOMETHING;
+ case ECOND_MATCH_IP: /* Match IP address in a host list */
+ if (sub[0][0] != 0 && string_is_ip_address(sub[0], NULL) == 0)
+ {
+ expand_string_message = string_sprintf("\"%s\" is not an IP address",
+ sub[0]);
+ return NULL;
+ }
+ else
+ {
+ unsigned int *nullcache = NULL;
+ check_host_block cb;
+
+ cb.host_name = US"";
+ cb.host_address = sub[0];
+
+ /* If the host address starts off ::ffff: it is an IPv6 address in
+ IPv4-compatible mode. Find the IPv4 part for checking against IPv4
+ addresses. */
+
+ cb.host_ipv4 = (Ustrncmp(cb.host_address, "::ffff:", 7) == 0)?
+ cb.host_address + 7 : cb.host_address;
+
+ rc = match_check_list(
+ &sub[1], /* the list */
+ 0, /* separator character */
+ &hostlist_anchor, /* anchor pointer */
+ &nullcache, /* cache pointer */
+ check_host, /* function for testing */
+ &cb, /* argument for function */
+ MCL_HOST, /* type of check */
+ sub[0], /* text for debugging */
+ NULL); /* where to pass back data */
+ }
+ goto MATCHED_SOMETHING;
+
case ECOND_MATCH_LOCAL_PART:
rc = match_isinlist(sub[0], &(sub[1]), 0, &localpartlist_anchor, NULL,
MCL_LOCALPART + MCL_NOEXPAND, TRUE, NULL);
/* Fall through */
-
+ /* VVVVVVVVVVVV */
MATCHED_SOMETHING:
switch(rc)
{
case OK:
- *yield = testfor;
+ tempcond = TRUE;
break;
case FAIL:
- *yield = !testfor;
+ tempcond = FALSE;
break;
case DEFER:
/* Various "encrypted" comparisons. If the second string starts with
"{" then an encryption type is given. Default to crypt() or crypt16()
(build-time choice). */
+ /* }-for-text-editors */
case ECOND_CRYPTEQ:
#ifndef SUPPORT_CRYPTEQ
uschar *coded = auth_b64encode((uschar *)digest, 16);
DEBUG(D_auth) debug_printf("crypteq: using MD5+B64 hashing\n"
" subject=%s\n crypted=%s\n", coded, sub[1]+5);
- *yield = (Ustrcmp(coded, sub[1]+5) == 0) == testfor;
+ tempcond = (Ustrcmp(coded, sub[1]+5) == 0);
}
else if (sublen == 32)
{
coded[32] = 0;
DEBUG(D_auth) debug_printf("crypteq: using MD5+hex hashing\n"
" subject=%s\n crypted=%s\n", coded, sub[1]+5);
- *yield = (strcmpic(coded, sub[1]+5) == 0) == testfor;
+ tempcond = (strcmpic(coded, sub[1]+5) == 0);
}
else
{
DEBUG(D_auth) debug_printf("crypteq: length for MD5 not 24 or 32: "
"fail\n crypted=%s\n", sub[1]+5);
- *yield = !testfor;
+ tempcond = FALSE;
}
}
uschar *coded = auth_b64encode((uschar *)digest, 20);
DEBUG(D_auth) debug_printf("crypteq: using SHA1+B64 hashing\n"
" subject=%s\n crypted=%s\n", coded, sub[1]+6);
- *yield = (Ustrcmp(coded, sub[1]+6) == 0) == testfor;
+ tempcond = (Ustrcmp(coded, sub[1]+6) == 0);
}
else if (sublen == 40)
{
coded[40] = 0;
DEBUG(D_auth) debug_printf("crypteq: using SHA1+hex hashing\n"
" subject=%s\n crypted=%s\n", coded, sub[1]+6);
- *yield = (strcmpic(coded, sub[1]+6) == 0) == testfor;
+ tempcond = (strcmpic(coded, sub[1]+6) == 0);
}
else
{
DEBUG(D_auth) debug_printf("crypteq: length for SHA-1 not 28 or 40: "
"fail\n crypted=%s\n", sub[1]+6);
- *yield = !testfor;
+ tempcond = FALSE;
}
}
else /* {crypt} or {crypt16} and non-{ at start */
+ /* }-for-text-editors */
{
int which = 0;
uschar *coded;
sub[1] += 9;
which = 2;
}
- else if (sub[1][0] == '{')
+ else if (sub[1][0] == '{') /* }-for-text-editors */
{
expand_string_message = string_sprintf("unknown encryption mechanism "
"in \"%s\"", sub[1]);
salt), force failure. Otherwise we get false positives: with an empty
string the yield of crypt() is an empty string! */
- *yield = (Ustrlen(sub[1]) < 2)? !testfor :
- (Ustrcmp(coded, sub[1]) == 0) == testfor;
+ tempcond = (Ustrlen(sub[1]) < 2)? FALSE :
+ (Ustrcmp(coded, sub[1]) == 0);
}
break;
#endif /* SUPPORT_CRYPTEQ */
+
+ case ECOND_INLIST:
+ case ECOND_INLISTI:
+ {
+ int sep = 0;
+ uschar *save_iterate_item = iterate_item;
+ int (*compare)(const uschar *, const uschar *);
+
+ tempcond = FALSE;
+ if (cond_type == ECOND_INLISTI)
+ compare = strcmpic;
+ else
+ compare = (int (*)(const uschar *, const uschar *)) strcmp;
+
+ while ((iterate_item = string_nextinlist(&sub[1], &sep, NULL, 0)) != NULL)
+ if (compare(sub[0], iterate_item) == 0)
+ {
+ tempcond = TRUE;
+ break;
+ }
+ iterate_item = save_iterate_item;
+ }
+
} /* Switch for comparison conditions */
+ *yield = tempcond == testfor;
return s; /* End of comparison conditions */
combined_cond = (cond_type == ECOND_AND);
while (isspace(*s)) s++;
- if (*s++ != '{') goto COND_FAILED_CURLY_START;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
for (;;)
{
while (isspace(*s)) s++;
+ /* {-for-text-editors */
if (*s == '}') break;
- if (*s != '{')
+ if (*s != '{') /* }-for-text-editors */
{
expand_string_message = string_sprintf("each subcondition "
"inside an \"%s{...}\" condition must be in its own {}", name);
}
while (isspace(*s)) s++;
+ /* {-for-text-editors */
if (*s++ != '}')
{
+ /* {-for-text-editors */
expand_string_message = string_sprintf("missing } at end of condition "
"inside \"%s\" group", name);
return NULL;
return ++s;
- /* Unknown condition */
-
- default:
- expand_string_message = string_sprintf("unknown condition \"%s\"", name);
- return NULL;
- } /* End switch on condition type */
-
-/* Missing braces at start and end of data */
+ /* forall/forany: iterates a condition with different values */
-COND_FAILED_CURLY_START:
-expand_string_message = string_sprintf("missing { after \"%s\"", name);
-return NULL;
+ case ECOND_FORALL:
+ case ECOND_FORANY:
+ {
+ int sep = 0;
+ uschar *save_iterate_item = iterate_item;
-COND_FAILED_CURLY_END:
-expand_string_message = string_sprintf("missing } at end of \"%s\" condition",
- name);
-return NULL;
+ while (isspace(*s)) s++;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
+ sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL), TRUE);
+ if (sub[0] == NULL) return NULL;
+ /* {-for-text-editors */
+ if (*s++ != '}') goto COND_FAILED_CURLY_END;
-/* A condition requires code that is not compiled */
+ while (isspace(*s)) s++;
+ if (*s++ != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
-#if !defined(SUPPORT_PAM) || !defined(RADIUS_CONFIG_FILE) || \
- !defined(LOOKUP_LDAP) || !defined(CYRUS_PWCHECK_SOCKET) || \
- !defined(SUPPORT_CRYPTEQ) || !defined(CYRUS_SASLAUTHD_SOCKET)
-COND_FAILED_NOT_COMPILED:
-expand_string_message = string_sprintf("support for \"%s\" not compiled",
- name);
-return NULL;
-#endif
-}
+ 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++;
+
+ /* {-for-text-editors */
+ if (*s++ != '}')
+ {
+ /* {-for-text-editors */
+ 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;
+ }
+
+
+ /* The bool{} expansion condition maps a string to boolean.
+ The values supported should match those supported by the ACL condition
+ (acl.c, ACLC_CONDITION) so that we keep to a minimum the different ideas
+ of true/false. Note that Router "condition" rules have a different
+ interpretation, where general data can be used and only a few values
+ map to FALSE.
+ Note that readconf.c boolean matching, for boolean configuration options,
+ only matches true/yes/false/no.
+ The bool_lax{} condition matches the Router logic, which is much more
+ liberal. */
+ case ECOND_BOOL:
+ case ECOND_BOOL_LAX:
+ {
+ uschar *sub_arg[1];
+ uschar *t, *t2;
+ uschar *ourname;
+ size_t len;
+ BOOL boolvalue = FALSE;
+ while (isspace(*s)) s++;
+ if (*s != '{') goto COND_FAILED_CURLY_START; /* }-for-text-editors */
+ ourname = cond_type == ECOND_BOOL_LAX ? US"bool_lax" : US"bool";
+ switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname))
+ {
+ 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:
+ expand_string_message = string_sprintf("unknown condition \"%s\"", name);
+ return NULL;
+ } /* End switch on condition type */
+
+/* Missing braces at start and end of data */
+
+COND_FAILED_CURLY_START:
+expand_string_message = string_sprintf("missing { after \"%s\"", name);
+return NULL;
+
+COND_FAILED_CURLY_END:
+expand_string_message = string_sprintf("missing } at end of \"%s\" condition",
+ name);
+return NULL;
+
+/* A condition requires code that is not compiled */
+
+#if !defined(SUPPORT_PAM) || !defined(RADIUS_CONFIG_FILE) || \
+ !defined(LOOKUP_LDAP) || !defined(CYRUS_PWCHECK_SOCKET) || \
+ !defined(SUPPORT_CRYPTEQ) || !defined(CYRUS_SASLAUTHD_SOCKET)
+COND_FAILED_NOT_COMPILED:
+expand_string_message = string_sprintf("support for \"%s\" not compiled",
+ name);
+return NULL;
+#endif
+}
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;
-
-
/*************************************************
* Handle MD5 or SHA-1 computation for HMAC *
*************************************************/
+/********************************************************
+* prvs: Get last three digits of days since Jan 1, 1970 *
+********************************************************/
+
+/* This is needed to implement the "prvs" BATV reverse
+ path signing scheme
+
+Argument: integer "days" offset to add or substract to
+ or from the current number of days.
+
+Returns: pointer to string containing the last three
+ digits of the number of days since Jan 1, 1970,
+ modified by the offset argument, NULL if there
+ was an error in the conversion.
+
+*/
+
+static uschar *
+prvs_daystamp(int day_offset)
+{
+uschar *days = store_get(32); /* Need at least 24 for cases */
+(void)string_format(days, 32, TIME_T_FMT, /* where TIME_T_FMT is %lld */
+ (time(NULL) + day_offset*86400)/86400);
+return (Ustrlen(days) >= 3) ? &days[Ustrlen(days)-3] : US"100";
+}
+
+
+
+/********************************************************
+* prvs: perform HMAC-SHA1 computation of prvs bits *
+********************************************************/
+
+/* This is needed to implement the "prvs" BATV reverse
+ path signing scheme
+
+Arguments:
+ address RFC2821 Address to use
+ key The key to use (must be less than 64 characters
+ in size)
+ key_num Single-digit key number to use. Defaults to
+ '0' when NULL.
+
+Returns: pointer to string containing the first three
+ bytes of the final hash in hex format, NULL if
+ there was an error in the process.
+*/
+
+static uschar *
+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;
+uschar innerhash[20];
+uschar finalhash[20];
+uschar innerkey[64];
+uschar outerkey[64];
+uschar *finalhash_hex = store_get(40);
+
+if (key_num == NULL)
+ key_num = US"0";
+
+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[offset] = '\0';
+
+DEBUG(D_expand) debug_printf("prvs: hash source is '%s'\n", hash_source);
+
+memset(innerkey, 0x36, 64);
+memset(outerkey, 0x5c, 64);
+
+for (i = 0; i < Ustrlen(key); i++)
+ {
+ innerkey[i] ^= 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, use_base);
+chash_mid(HMAC_SHA1, use_base, outerkey);
+chash_end(HMAC_SHA1, use_base, innerhash, 20, finalhash);
+
+p = finalhash_hex;
+for (i = 0; i < 3; i++)
+ {
+ *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
+ *p++ = hex_digits[finalhash[i] & 0x0f];
+ }
+*p = '\0';
+
+return finalhash_hex;
+}
+
+
+
+
/*************************************************
* Join a file onto the output string *
*************************************************/
* 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_eximarith_t eval_op_or(uschar **, BOOL, uschar **);
-static int
+
+static int_eximarith_t
eval_expr(uschar **sptr, BOOL decimal, uschar **error, BOOL endket)
{
uschar *s = *sptr;
-int x = eval_sumterm(&s, decimal, error);
+int_eximarith_t x = eval_op_or(&s, decimal, error);
if (*error == NULL)
{
- while (*s == '+' || *s == '-')
- {
- 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 (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)
+
+static int_eximarith_t
+eval_number(uschar **sptr, BOOL decimal, uschar **error)
{
register int c;
-int n;
+int_eximarith_t n;
uschar *s = *sptr;
while (isspace(*s)) s++;
c = *s;
-if (isdigit(c) || ((c == '-' || c == '+') && isdigit(s[1])))
+if (isdigit(c))
{
int count;
- (void)sscanf(CS s, (decimal? "%d%n" : "%i%n"), &n, &count);
+ (void)sscanf(CS s, (decimal? SC_EXIM_DEC "%n" : SC_EXIM_ARITH "%n"), &n, &count);
s += count;
- if (tolower(*s) == 'k') { n *= 1024; s++; }
- else if (tolower(*s) == 'm') { n *= 1024*1024; s++; }
+ switch (tolower(*s))
+ {
+ default: break;
+ case 'k': n *= 1024; s++; break;
+ case 'm': n *= 1024*1024; s++; break;
+ case 'g': n *= 1024*1024*1024; s++; break;
+ }
while (isspace (*s)) s++;
}
else if (c == '(')
return n;
}
-static int eval_sumterm(uschar **sptr, BOOL decimal, uschar **error)
+
+static int_eximarith_t
+eval_op_unary(uschar **sptr, BOOL decimal, uschar **error)
{
uschar *s = *sptr;
-int x = eval_term(&s, decimal, error);
+int_eximarith_t 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_eximarith_t
+eval_op_mult(uschar **sptr, BOOL decimal, uschar **error)
+{
+uschar *s = *sptr;
+int_eximarith_t x = eval_op_unary(&s, decimal, error);
if (*error == NULL)
{
- while (*s == '*' || *s == '/')
+ while (*s == '*' || *s == '/' || *s == '%')
{
int op = *s++;
- int y = eval_term(&s, decimal, error);
+ int_eximarith_t y = eval_op_unary(&s, decimal, error);
if (*error != NULL) break;
- if (op == '*') x *= y; else x /= y;
+ /* SIGFPE both on div/mod by zero and on INT_MIN / -1, which would give
+ * a value of INT_MAX+1. Note that INT_MIN * -1 gives INT_MIN for me, which
+ * is a bug somewhere in [gcc 4.2.1, FreeBSD, amd64]. In fact, -N*-M where
+ * -N*M is INT_MIN will yielf INT_MIN.
+ * Since we don't support floating point, this is somewhat simpler.
+ * Ideally, we'd return an error, but since we overflow for all other
+ * arithmetic, consistency suggests otherwise, but what's the correct value
+ * to use? There is none.
+ * The C standard guarantees overflow for unsigned arithmetic but signed
+ * overflow invokes undefined behaviour; in practice, this is overflow
+ * except for converting INT_MIN to INT_MAX+1. We also can't guarantee
+ * that long/longlong larger than int are available, or we could just work
+ * with larger types. We should consider whether to guarantee 32bit eval
+ * and 64-bit working variables, with errors returned. For now ...
+ * So, the only SIGFPEs occur with a non-shrinking div/mod, thus -1; we
+ * can just let the other invalid results occur otherwise, as they have
+ * until now. For this one case, we can coerce.
+ */
+ if (y == -1 && x == EXIM_ARITH_MIN && op != '*')
+ {
+ DEBUG(D_expand)
+ debug_printf("Integer exception dodging: " PR_EXIM_ARITH "%c-1 coerced to " PR_EXIM_ARITH "\n",
+ EXIM_ARITH_MIN, op, EXIM_ARITH_MAX);
+ x = EXIM_ARITH_MAX;
+ continue;
+ }
+ if (op == '*')
+ x *= y;
+ else
+ {
+ if (y == 0)
+ {
+ *error = (op == '/') ? US"divide by zero" : US"modulo by zero";
+ x = 0;
+ break;
+ }
+ if (op == '/')
+ x /= y;
+ else
+ x %= y;
+ }
}
}
*sptr = s;
}
+static int_eximarith_t
+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)
+ {
+ while (*s == '+' || *s == '-')
+ {
+ int op = *s++;
+ int_eximarith_t y = eval_op_mult(&s, decimal, error);
+ if (*error != NULL) break;
+ if (op == '+') x += y; else x -= y;
+ }
+ }
+*sptr = s;
+return x;
+}
+
+
+static int_eximarith_t
+eval_op_shift(uschar **sptr, BOOL decimal, uschar **error)
+{
+uschar *s = *sptr;
+int_eximarith_t x = eval_op_sum(&s, decimal, error);
+if (*error == NULL)
+ {
+ while ((*s == '<' || *s == '>') && s[1] == s[0])
+ {
+ int_eximarith_t 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_eximarith_t
+eval_op_and(uschar **sptr, BOOL decimal, uschar **error)
+{
+uschar *s = *sptr;
+int_eximarith_t x = eval_op_shift(&s, decimal, error);
+if (*error == NULL)
+ {
+ while (*s == '&')
+ {
+ int_eximarith_t y;
+ s++;
+ y = eval_op_shift(&s, decimal, error);
+ if (*error != NULL) break;
+ x &= y;
+ }
+ }
+*sptr = s;
+return x;
+}
+
+
+static int_eximarith_t
+eval_op_xor(uschar **sptr, BOOL decimal, uschar **error)
+{
+uschar *s = *sptr;
+int_eximarith_t x = eval_op_and(&s, decimal, error);
+if (*error == NULL)
+ {
+ while (*s == '^')
+ {
+ int_eximarith_t y;
+ s++;
+ y = eval_op_and(&s, decimal, error);
+ if (*error != NULL) break;
+ x ^= y;
+ }
+ }
+*sptr = s;
+return x;
+}
+
+
+static int_eximarith_t
+eval_op_or(uschar **sptr, BOOL decimal, uschar **error)
+{
+uschar *s = *sptr;
+int_eximarith_t x = eval_op_xor(&s, decimal, error);
+if (*error == NULL)
+ {
+ while (*s == '|')
+ {
+ int_eximarith_t y;
+ s++;
+ y = eval_op_xor(&s, decimal, error);
+ if (*error != NULL) break;
+ x |= y;
+ }
+ }
+*sptr = s;
+return x;
+}
+
/*************************************************
use that without copying. This is helpful for expanding strings like
$message_headers which can get very long.
+There's a problem if a ${dlfunc item has side-effects that cause allocation,
+since resetting the store at the end of the expansion will free store that was
+allocated by the plugin code as well as the slop after the expanded string. So
+we skip any resets if ${dlfunc has been used. The same applies for ${acl. 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;
}
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;
}
}
switch(item_type)
{
+ /* Call an ACL from an expansion. We feed data in via $acl_arg1 - $acl_arg9.
+ If the ACL returns accept or reject we return content set by "message ="
+ There is currently no limit on recursion; this would have us call
+ acl_check_internal() directly and get a current level from somewhere.
+ See also the acl expansion condition ECOND_ACL and the traditional
+ acl modifier ACLC_ACL.
+ Assume that the function has side-effects on the store that must be preserved.
+ */
+
+ case EITEM_ACL:
+ /* ${acl {name} {arg1}{arg2}...} */
+ {
+ uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */
+ uschar *user_msg;
+
+ switch(read_subs(sub, 10, 1, &s, skipping, TRUE, US"acl"))
+ {
+ case 1: goto EXPAND_FAILED_CURLY;
+ case 2:
+ case 3: goto EXPAND_FAILED;
+ }
+ if (skipping) continue;
+
+ resetok = FALSE;
+ switch(eval_acl(sub, sizeof(sub)/sizeof(*sub), &user_msg))
+ {
+ case OK:
+ case FAIL:
+ if (user_msg)
+ yield = string_cat(yield, &size, &ptr, user_msg, Ustrlen(user_msg));
+ continue;
+
+ case DEFER:
+ expand_string_forcedfail = TRUE;
+ default:
+ expand_string_message = string_sprintf("error from acl \"%s\"", sub[0]);
+ goto EXPAND_FAILED;
+ }
+ }
+
/* Handle conditionals - preserve the values of the numerical expansion
variables in case they get changed by a regular expression match in the
condition. If not, they retain their external settings. At the end
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++;
/* Check that a key was provided for those lookup types that need it,
and was not supplied for those that use the query style. */
- if (!mac_islookup(stype, lookup_querystyle))
+ if (!mac_islookup(stype, lookup_querystyle|lookup_absfilequery))
{
if (key == NULL)
{
}
/* Get the next string in brackets and expand it. It is the file name for
- single-key+file lookups, and the whole query otherwise. */
+ single-key+file lookups, and the whole query otherwise. In the case of
+ queries that also require a file name (e.g. sqlite), the file name comes
+ 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 this isn't a single-key+file lookup, re-arrange the variables
- to be appropriate for the search_ functions. */
+ to be appropriate for the search_ functions. For query-style lookups,
+ 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)
{
+ while (isspace(*filename)) filename++;
key = filename;
- filename = NULL;
+
+ if (mac_islookup(stype, lookup_querystyle))
+ {
+ filename = NULL;
+ }
+ else
+ {
+ if (*filename != '/')
+ {
+ expand_string_message = string_sprintf(
+ "absolute file name expected for \"%s\" lookup", name);
+ goto EXPAND_FAILED;
+ }
+ while (*key != 0 && !isspace(*key)) key++;
+ if (*key != 0) *key++ = 0;
+ }
}
/* If skipping, don't do the next bit - just lookup_value == NULL, as if
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 = Ustrrchr(sub_arg[0],'@');
if ( (domain == NULL) || (domain == sub_arg[0]) || (Ustrlen(domain) == 1) )
{
- expand_string_message = US"first parameter must be a qualified email address";
+ expand_string_message = US"prvs first argument must be a qualified email address";
+ goto EXPAND_FAILED;
+ }
+
+ /* Calculate the hash. The second argument must be a single-digit
+ key number, or unset. */
+
+ if (sub_arg[2] != NULL &&
+ (!isdigit(sub_arg[2][0]) || sub_arg[2][1] != 0))
+ {
+ expand_string_message = US"prvs second argument must be a single digit";
goto EXPAND_FAILED;
}
- /* Calculate the hash */
p = prvs_hmac_sha1(sub_arg[0],sub_arg[1],sub_arg[2],prvs_daystamp(7));
if (p == NULL)
{
- expand_string_message = US"hmac-sha1 conversion failed";
+ expand_string_message = US"prvs hmac-sha1 conversion failed";
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));
int mysize = 0, myptr = 0;
const pcre *re;
uschar *p;
- /* Ugliness: We want to expand parameter 1 first, then set
+
+ /* TF: Ugliness: We want to expand parameter 1 first, then set
up expansion variables that are used in the expansion of
parameter 2. So we clone the string for the first
- expansion, where we only expand paramter 1. */
- uschar *s_backup = string_copy(s);
+ expansion, where we only expand parameter 1.
+
+ PH: Actually, that isn't necessary. The read_subs() function is
+ designed to work this way for the ${if and ${lookup expansions. I've
+ tidied the code.
+ */
/* Reset expansion variables */
prvscheck_result = NULL;
prvscheck_address = NULL;
prvscheck_keynum = NULL;
- switch(read_subs(sub_arg, 1, 1, &s_backup, skipping, FALSE, US"prvs"))
+ switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs"))
{
case 1: goto EXPAND_FAILED_CURLY;
case 2:
case 3: goto EXPAND_FAILED;
}
- re = regex_must_compile(US"^prvs\\=(.+)\\/([0-9])([0-9]{3})([A-F0-9]{6})\\@(.+)$",
+ re = regex_must_compile(US"^prvs\\=([0-9])([0-9]{3})([A-F0-9]{6})\\=(.+)\\@(.+)$",
TRUE,FALSE);
- if (regex_match_and_setup(re,sub_arg[0],0,-1)) {
- uschar *local_part = string_copyn(expand_nstring[1],expand_nlength[1]);
- uschar *key_num = string_copyn(expand_nstring[2],expand_nlength[2]);
- uschar *daystamp = string_copyn(expand_nstring[3],expand_nlength[3]);
- uschar *hash = string_copyn(expand_nstring[4],expand_nlength[4]);
+ if (regex_match_and_setup(re,sub_arg[0],0,-1))
+ {
+ 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);
prvscheck_address[myptr] = '\0';
prvscheck_keynum = string_copy(key_num);
- /* Now re-expand all arguments in the usual manner */
- switch(read_subs(sub_arg, 3, 3, &s, skipping, TRUE, US"prvs"))
+ /* Now expand the second argument */
+ switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs"))
{
case 1: goto EXPAND_FAILED_CURLY;
case 2:
case 3: goto EXPAND_FAILED;
}
- if (*sub_arg[2] == '\0')
- yield = string_cat(yield,&size,&ptr,prvscheck_address,Ustrlen(prvscheck_address));
- else
- yield = string_cat(yield,&size,&ptr,sub_arg[2],Ustrlen(sub_arg[2]));
-
/* Now we have the key and can check the address. */
- p = prvs_hmac_sha1(prvscheck_address, sub_arg[1], prvscheck_keynum, daystamp);
+
+ p = prvs_hmac_sha1(prvscheck_address, sub_arg[0], prvscheck_keynum,
+ daystamp);
+
if (p == NULL)
{
expand_string_message = US"hmac-sha1 conversion failed";
DEBUG(D_expand) debug_printf("prvscheck: received hash is %s\n", hash);
DEBUG(D_expand) debug_printf("prvscheck: own hash is %s\n", p);
+
if (Ustrcmp(p,hash) == 0)
{
/* Success, valid BATV address. Now check the expiry date. */
uschar *now = prvs_daystamp(0);
unsigned int inow = 0,iexpire = 1;
- sscanf(CS now,"%u",&inow);
- sscanf(CS daystamp,"%u",&iexpire);
+ (void)sscanf(CS now,"%u",&inow);
+ (void)sscanf(CS daystamp,"%u",&iexpire);
/* When "iexpire" is < 7, a "flip" has occured.
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");
prvscheck_result = NULL;
DEBUG(D_expand) debug_printf("prvscheck: hash failure, $pvrs_result unset\n");
}
- }
+
+ /* Now expand the final argument. We leave this till now so that
+ it can include $prvscheck_result. */
+
+ switch(read_subs(sub_arg, 1, 0, &s, skipping, TRUE, US"prvs"))
+ {
+ case 1: goto EXPAND_FAILED_CURLY;
+ case 2:
+ 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]));
+
+ /* Reset the "internal" variables afterwards, because they are in
+ dynamic store that will be reclaimed if the expansion succeeded. */
+
+ prvscheck_address = NULL;
+ prvscheck_keynum = NULL;
+ }
else
{
/* Does not look like a prvs encoded address, return the empty string.
- We need to make sure all subs are expanded first. */
- switch(read_subs(sub_arg, 3, 3, &s, skipping, TRUE, US"prvs"))
+ We need to make sure all subs are expanded first, so as to skip over
+ the entire item. */
+
+ switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"prvs"))
{
case 1: goto EXPAND_FAILED_CURLY;
case 2:
}
yield = cat_file(f, yield, &size, &ptr, sub_arg[1]);
- fclose(f);
+ (void)fclose(f);
continue;
}
}
else sub_arg[3] = NULL; /* No eol if no timeout */
- /* If skipping, we don't actually do anything */
+ /* If skipping, we don't actually do anything. Otherwise, arrange to
+ connect to either an IP or a Unix socket. */
if (!skipping)
{
- /* Make a connection to the socket */
+ /* Handle an IP (internet) domain */
- if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
+ if (Ustrncmp(sub_arg[0], "inet:", 5) == 0)
{
- expand_string_message = string_sprintf("failed to create socket: %s",
- strerror(errno));
- goto SOCK_FAIL;
+ BOOL connected = FALSE;
+ int namelen, port;
+ host_item shost;
+ host_item *h;
+ uschar *server_name = sub_arg[0] + 5;
+ uschar *port_name = Ustrrchr(server_name, ':');
+
+ /* Sort out the port */
+
+ if (port_name == NULL)
+ {
+ expand_string_message =
+ string_sprintf("missing port for readsocket %s", sub_arg[0]);
+ goto EXPAND_FAILED;
+ }
+ *port_name++ = 0; /* Terminate server name */
+
+ if (isdigit(*port_name))
+ {
+ uschar *end;
+ port = Ustrtol(port_name, &end, 0);
+ if (end != port_name + Ustrlen(port_name))
+ {
+ expand_string_message =
+ string_sprintf("invalid port number %s", port_name);
+ goto EXPAND_FAILED;
+ }
+ }
+ else
+ {
+ struct servent *service_info = getservbyname(CS port_name, "tcp");
+ if (service_info == NULL)
+ {
+ expand_string_message = string_sprintf("unknown port \"%s\"",
+ port_name);
+ goto EXPAND_FAILED;
+ }
+ port = ntohs(service_info->s_port);
+ }
+
+ /* Sort out the server. */
+
+ shost.next = NULL;
+ shost.address = NULL;
+ shost.port = port;
+ shost.mx = -1;
+
+ namelen = Ustrlen(server_name);
+
+ /* Anything enclosed in [] must be an IP address. */
+
+ if (server_name[0] == '[' &&
+ server_name[namelen - 1] == ']')
+ {
+ server_name[namelen - 1] = 0;
+ server_name++;
+ if (string_is_ip_address(server_name, NULL) == 0)
+ {
+ expand_string_message =
+ string_sprintf("malformed IP address \"%s\"", server_name);
+ goto EXPAND_FAILED;
+ }
+ shost.name = shost.address = server_name;
+ }
+
+ /* Otherwise check for an unadorned IP address */
+
+ else if (string_is_ip_address(server_name, NULL) != 0)
+ shost.name = shost.address = server_name;
+
+ /* Otherwise lookup IP address(es) from the name */
+
+ else
+ {
+ shost.name = server_name;
+ 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);
+ goto EXPAND_FAILED;
+ }
+ }
+
+ /* Try to connect to the server - test each IP till one works */
+
+ for (h = &shost; h != NULL; h = h->next)
+ {
+ int af = (Ustrchr(h->address, ':') != 0)? AF_INET6 : AF_INET;
+ if ((fd = ip_socket(SOCK_STREAM, af)) == -1)
+ {
+ expand_string_message = string_sprintf("failed to create socket: "
+ "%s", strerror(errno));
+ goto SOCK_FAIL;
+ }
+
+ if (ip_connect(fd, af, h->address, port, timeout) == 0)
+ {
+ connected = TRUE;
+ break;
+ }
+ }
+
+ if (!connected)
+ {
+ expand_string_message = string_sprintf("failed to connect to "
+ "socket %s: couldn't connect to any host", sub_arg[0],
+ strerror(errno));
+ goto SOCK_FAIL;
+ }
}
- 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)
+ /* Handle a Unix domain socket */
+
+ else
{
- expand_string_message = string_sprintf("failed to connect to socket "
- "%s: %s", sub_arg[0], strerror(errno));
- goto SOCK_FAIL;
+ int rc;
+ if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
+ {
+ expand_string_message = string_sprintf("failed to create socket: %s",
+ strerror(errno));
+ goto SOCK_FAIL;
+ }
+
+ sockun.sun_family = AF_UNIX;
+ sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
+ sub_arg[0]);
+
+ 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));
+ goto SOCK_FAIL;
+ }
}
+
DEBUG(D_expand) debug_printf("connected to socket %s\n", sub_arg[0]);
/* Write the request string, if not empty */
}
}
+ /* 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. */
alarm(timeout);
yield = cat_file(f, yield, &size, &ptr, sub_arg[3]);
alarm(0);
- fclose(f);
+ (void)fclose(f);
/* After a timeout, we restore the pointer in the result, that is,
make sure we add nothing from the socket. */
if (sigalrm_seen)
{
ptr = save_ptr;
- expand_string_message = US"socket read timed out";
+ expand_string_message = US "socket read timed out";
goto SOCK_FAIL;
}
}
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;
/* Nothing is written to the standard input. */
- close(fd_in);
+ (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);
- 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;
- /* After removal of leading and trailing white space, the first
- argument must not be empty; if it consists entirely of digits
- (optionally preceded by a minus sign), this is a numerical
- extraction, and we expect 3 arguments. */
+ /* After removal of leading and trailing white space, the first
+ argument must not be empty; if it consists entirely of digits
+ (optionally preceded by a minus sign), this is a numerical
+ extraction, and we expect 3 arguments. */
+
+ if (i == 0)
+ {
+ int len;
+ int x = 0;
+ uschar *p = sub[0];
+
+ while (isspace(*p)) p++;
+ sub[0] = p;
+
+ len = Ustrlen(p);
+ while (len > 0 && isspace(p[len-1])) len--;
+ p[len] = 0;
+
+ if (*p == 0 && !skipping)
+ {
+ 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;
+ }
+ }
+ }
+ else goto EXPAND_FAILED_CURLY;
+ }
+
+ /* Extract either the numbered or the keyed substring into $value. If
+ skipping, just pretend the extraction failed. */
+
+ lookup_value = skipping? NULL : field_number_set?
+ expand_gettokened(field_number, sub[1], sub[2]) :
+ expand_getkeyed(sub[0], sub[1]);
+
+ /* If no string follows, $value gets substituted; otherwise there can
+ be yes/no strings, as for lookup or if. */
+
+ switch(process_yesno(
+ skipping, /* were previously skipping */
+ lookup_value != NULL, /* success/failure indicator */
+ save_lookup_value, /* value to reset for string2 */
+ &s, /* input pointer */
+ &yield, /* output pointer */
+ &size, /* output size */
+ &ptr, /* output current point */
+ US"extract")) /* condition type */
+ {
+ case 1: goto EXPAND_FAILED; /* when all is well, the */
+ case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */
+ }
+
+ /* All done - restore numerical variables. */
+
+ restore_expand_strings(save_expand_nmax, save_expand_nstring,
+ save_expand_nlength);
+
+ continue;
+ }
+
+
+ /* Handle list operations */
+
+ 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 (i == 0)
- {
- int len;
- int x = 0;
- uschar *p = sub[0];
+ if (ptr != save_ptr && (temp[0] == *outsep || temp[0] == 0))
+ yield = string_cat(yield, &size, &ptr, US" ", 1);
- while (isspace(*p)) p++;
- sub[0] = p;
+ /* Add the string in "temp" to the output list that we are building,
+ This is done in chunks by searching for the separator character. */
- len = Ustrlen(p);
- while (len > 0 && isspace(p[len-1])) len--;
- p[len] = 0;
+ for (;;)
+ {
+ size_t seglen = Ustrcspn(temp, outsep);
+ yield = string_cat(yield, &size, &ptr, temp, seglen + 1);
- if (*p == 0)
- {
- expand_string_message = US"first argument of \"extract\" must "
- "not be empty";
- goto EXPAND_FAILED;
- }
+ /* 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 (*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 (temp[seglen] == '\0') { ptr--; break; }
+ yield = string_cat(yield, &size, &ptr, outsep, 1);
+ temp += seglen + 1;
}
- else goto EXPAND_FAILED_CURLY;
- }
- /* Extract either the numbered or the keyed substring into $value. If
- skipping, just pretend the extraction failed. */
+ /* Output a separator after the string: we will remove the redundant
+ final one at the end. */
- lookup_value = skipping? NULL : field_number_set?
- expand_gettokened(field_number, sub[1], sub[2]) :
- expand_getkeyed(sub[0], sub[1]);
+ yield = string_cat(yield, &size, &ptr, outsep, 1);
+ } /* End of iteration over the list loop */
- /* If no string follows, $value gets substituted; otherwise there can
- be yes/no strings, as for lookup or if. */
+ /* REDUCE has generated no output above: output the final value of
+ $value. */
- switch(process_yesno(
- skipping, /* were previously skipping */
- lookup_value != NULL, /* success/failure indicator */
- save_lookup_value, /* value to reset for string2 */
- &s, /* input pointer */
- &yield, /* output pointer */
- &size, /* output size */
- &ptr, /* output current point */
- US"extract")) /* condition type */
+ if (item_type == EITEM_REDUCE)
{
- case 1: goto EXPAND_FAILED; /* when all is well, the */
- case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */
+ yield = string_cat(yield, &size, &ptr, lookup_value,
+ Ustrlen(lookup_value));
+ lookup_value = save_lookup_value; /* Restore $value */
}
- /* All done - restore numerical variables. */
+ /* 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. */
- restore_expand_strings(save_expand_nmax, save_expand_nstring,
- save_expand_nlength);
+ else if (ptr != save_ptr) ptr--;
+ /* Restore preserved $item */
+
+ iterate_item = save_iterate_item;
continue;
}
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++;
continue;
}
+ /* Note that for Darwin and Cygwin, BASE_62 actually has the value 36 */
+
case EOP_BASE62D:
{
uschar buf[16];
if (t == NULL)
{
expand_string_message = string_sprintf("argument for base62d "
- "operator is \"%s\", which is not a base 62 number", sub);
+ "operator is \"%s\", which is not a base %d number", sub,
+ BASE_62);
goto EXPAND_FAILED;
}
- n = n * 62 + (t - base62_chars);
+ n = n * BASE_62 + (t - base62_chars);
}
(void)sprintf(CS buf, "%ld", n);
yield = string_cat(yield, &size, &ptr, buf, Ustrlen(buf));
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;
}
+ /* Convert octets outside 0x21..0x7E to \xXX form */
+
+ case EOP_HEXQUOTE:
+ {
+ uschar *t = sub - 1;
+ while (*(++t) != 0)
+ {
+ if (*t < 0x21 || 0x7E < *t)
+ yield = string_cat(yield, &size, &ptr,
+ string_sprintf("\\x%02x", *t), 4);
+ else
+ yield = string_cat(yield, &size, &ptr, t, 1);
+ }
+ continue;
+ }
+
+ /* count the number of list elements */
+
+ case EOP_LISTCOUNT:
+ {
+ int cnt = 0;
+ int sep = 0;
+ uschar * cp;
+ uschar buffer[256];
+
+ while (string_nextinlist(&sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++;
+ cp = string_sprintf("%d", cnt);
+ yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp));
+ continue;
+ }
+
+ /* expand a named list given the name */
+ /* handles nested named lists; requotes as colon-sep list */
+
+ case EOP_LISTNAMED:
+ {
+ tree_node *t = NULL;
+ uschar * list;
+ int sep = 0;
+ uschar * item;
+ uschar * suffix = US"";
+ BOOL needsep = FALSE;
+ uschar buffer[256];
+
+ if (*sub == '+') sub++;
+ if (arg == NULL) /* no-argument version */
+ {
+ if (!(t = tree_search(addresslist_anchor, sub)) &&
+ !(t = tree_search(domainlist_anchor, sub)) &&
+ !(t = tree_search(hostlist_anchor, sub)))
+ t = tree_search(localpartlist_anchor, sub);
+ }
+ else switch(*arg) /* specific list-type version */
+ {
+ case 'a': t = tree_search(addresslist_anchor, sub); suffix = US"_a"; break;
+ case 'd': t = tree_search(domainlist_anchor, sub); suffix = US"_d"; break;
+ case 'h': t = tree_search(hostlist_anchor, sub); suffix = US"_h"; break;
+ case 'l': t = tree_search(localpartlist_anchor, sub); suffix = US"_l"; break;
+ default:
+ expand_string_message = string_sprintf("bad suffix on \"list\" operator");
+ goto EXPAND_FAILED;
+ }
+
+ if(!t)
+ {
+ expand_string_message = string_sprintf("\"%s\" is not a %snamed list",
+ sub, !arg?""
+ : *arg=='a'?"address "
+ : *arg=='d'?"domain "
+ : *arg=='h'?"host "
+ : *arg=='l'?"localpart "
+ : 0);
+ goto EXPAND_FAILED;
+ }
+
+ list = ((namedlist_block *)(t->data.ptr))->string;
+
+ while ((item = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
+ {
+ uschar * buf = US" : ";
+ if (needsep)
+ yield = string_cat(yield, &size, &ptr, buf, 3);
+ else
+ needsep = TRUE;
+
+ if (*item == '+') /* list item is itself a named list */
+ {
+ uschar * sub = string_sprintf("${listnamed%s:%s}", suffix, item);
+ item = expand_string_internal(sub, FALSE, NULL, FALSE, TRUE);
+ }
+ else if (sep != ':') /* item from non-colon-sep list, re-quote for colon list-separator */
+ {
+ char * cp;
+ char tok[3];
+ tok[0] = sep; tok[1] = ':'; tok[2] = 0;
+ while ((cp= strpbrk((const char *)item, tok)))
+ {
+ yield = string_cat(yield, &size, &ptr, item, cp-(char *)item);
+ if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */
+ {
+ yield = string_cat(yield, &size, &ptr, US"::", 2);
+ item = (uschar *)cp;
+ }
+ else /* sep in item; should already be doubled; emit once */
+ {
+ yield = string_cat(yield, &size, &ptr, (uschar *)tok, 1);
+ if (*cp == sep) cp++;
+ item = (uschar *)cp;
+ }
+ }
+ }
+ yield = string_cat(yield, &size, &ptr, item, Ustrlen(item));
+ }
+ continue;
+ }
+
/* mask applies a mask to an IP address; for example the result of
${mask:131.111.10.206/28} is 131.111.10.192/28. */
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)
{
uschar buffer[2048];
uschar *string = parse_quote_2047(sub, Ustrlen(sub), headers_charset,
- buffer, sizeof(buffer));
+ buffer, sizeof(buffer), FALSE);
yield = string_cat(yield, &size, &ptr, string, Ustrlen(string));
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 */
{
uschar *save_sub = sub;
uschar *error = NULL;
- int n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
+ int_eximarith_t n = eval_expr(&sub, (c == EOP_EVAL10), &error, FALSE);
if (error != NULL)
{
expand_string_message = string_sprintf("error in expression "
save_sub);
goto EXPAND_FAILED;
}
- sprintf(CS var_buffer, "%d", n);
+ sprintf(CS var_buffer, PR_EXIM_ARITH, n);
yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer));
continue;
}
/* Handle time period formating */
+ case EOP_TIME_EVAL:
+ {
+ int n = readconf_readtime(sub, 0, FALSE);
+ if (n < 0)
+ {
+ expand_string_message = string_sprintf("string \"%s\" is not an "
+ "Exim time interval in \"%s\" operator", sub, name);
+ goto EXPAND_FAILED;
+ }
+ sprintf(CS var_buffer, "%d", n);
+ yield = string_cat(yield, &size, &ptr, var_buffer, Ustrlen(var_buffer));
+ continue;
+ }
+
case EOP_TIME_INTERVAL:
{
int n;
mode_t mode;
struct stat st;
+ if ((expand_forbid & RDO_EXISTS) != 0)
+ {
+ expand_string_message = US"Use of the stat() expansion is not permitted";
+ goto EXPAND_FAILED;
+ }
+
if (stat(CS sub, &st) < 0)
{
expand_string_message = string_sprintf("stat(%s) failed: %s",
continue;
}
+ /* vaguely random number less than N */
+
+ case EOP_RANDINT:
+ {
+ int_eximarith_t max;
+ uschar *s;
+
+ max = expand_string_integer(sub, TRUE);
+ if (expand_string_message != NULL)
+ goto EXPAND_FAILED;
+ s = string_sprintf("%d", vaguely_random_number((int)max));
+ yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
+ continue;
+ }
+
+ /* Reverse IP, including IPv6 to dotted-nibble */
+
+ case EOP_REVERSE_IP:
+ {
+ int family, maskptr;
+ uschar reversed[128];
+
+ family = string_is_ip_address(sub, &maskptr);
+ if (family == 0)
+ {
+ expand_string_message = string_sprintf(
+ "reverse_ip() not given an IP address [%s]", sub);
+ goto EXPAND_FAILED;
+ }
+ invert_address(reversed, sub);
+ yield = string_cat(yield, &size, &ptr, reversed, Ustrlen(reversed));
+ continue;
+ }
+
/* Unknown operator */
default:
int newsize = 0;
if (ptr == 0)
{
- store_reset(yield);
+ if (resetok) store_reset(yield);
yield = NULL;
size = 0;
}
{
expand_string_message =
string_sprintf("unknown variable in \"${%s}\"", name);
+ check_variable_error_message(name);
goto EXPAND_FAILED;
}
len = Ustrlen(value);
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);
}
/* Expand a string, and convert the result into an integer.
-Argument: the string to be expanded
+Arguments:
+ string the string to be expanded
+ isplus TRUE if a non-negative number is expected
Returns: the integer value, or
-1 for an expansion error ) in both cases, message in
-2 for an integer interpretation error ) expand_string_message
-
+ expand_string_message is set NULL for an OK integer
*/
-int
-expand_string_integer(uschar *string)
+int_eximarith_t
+expand_string_integer(uschar *string, BOOL isplus)
{
-long int value;
+int_eximarith_t value;
uschar *s = expand_string(string);
uschar *msg = US"invalid integer \"%s\"";
uschar *endptr;
+/* If expansion failed, expand_string_message will be set. */
+
if (s == NULL) return -1;
/* On an overflow, strtol() returns LONG_MAX or LONG_MIN, and sets errno
systems, so we set it zero ourselves. */
errno = 0;
-value = strtol(CS s, CSS &endptr, 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 = strtoll(CS s, CSS &endptr, 10);
if (endptr == s)
{
msg = US"integer expected but \"%s\" found";
}
+else if (value < 0 && isplus)
+ {
+ msg = US"non-negative integer expected but \"%s\" found";
+ }
else
{
- /* Ensure we can cast this down to an int */
- if (value > INT_MAX || value < INT_MIN) errno = ERANGE;
-
- if (errno != ERANGE)
+ switch (tolower(*endptr))
{
- if (tolower(*endptr) == 'k')
- {
- if (value > INT_MAX/1024 || value < INT_MIN/1024) errno = ERANGE;
- else value *= 1024;
+ default:
+ break;
+ case 'k':
+ if (value > EXIM_ARITH_MAX/1024 || value < EXIM_ARITH_MIN/1024) errno = ERANGE;
+ else value *= 1024;
endptr++;
- }
- else if (tolower(*endptr) == 'm')
- {
- if (value > INT_MAX/(1024*1024) || value < INT_MIN/(1024*1024))
- errno = ERANGE;
+ break;
+ case 'm':
+ if (value > EXIM_ARITH_MAX/(1024*1024) || value < EXIM_ARITH_MIN/(1024*1024)) errno = ERANGE;
else value *= 1024*1024;
endptr++;
- }
+ break;
+ case 'g':
+ if (value > EXIM_ARITH_MAX/(1024*1024*1024) || value < EXIM_ARITH_MIN/(1024*1024*1024)) errno = ERANGE;
+ else value *= 1024*1024*1024;
+ endptr++;
+ break;
}
if (errno == ERANGE)
msg = US"absolute value of integer \"%s\" is too large (overflow)";
else
{
while (isspace(*endptr)) endptr++;
- if (*endptr == 0) return (int)value;
+ if (*endptr == 0) return value;
}
}
return -2;
}
-/********************************************************
-* prvs: Get last three digits of days since Jan 1, 1970 *
-********************************************************/
-
-/* This is needed to implement the "prvs" BATV reverse
- path signing scheme
-
-Argument: integer "days" offset to add or substract to
- or from the current number of days.
-
-Returns: pointer to string containing the last three
- digits of the number of days since Jan 1, 1970,
- modified by the offset argument, NULL if there
- was an error in the conversion.
-
-*/
-
-uschar *
-prvs_daystamp(int day_offset)
-{
-uschar *days = store_get(10);
-snprintf(CS days, 9, "%lld", (((long long)time(NULL))+(day_offset*86400))/86400);
-return (Ustrlen(days) >= 3) ? &days[Ustrlen(days)-3] : NULL;
-}
-
-/********************************************************
-* prvs: perform HMAC-SHA1 computation of prvs bits *
-********************************************************/
-
-/* This is needed to implement the "prvs" BATV reverse
- path signing scheme
-
-Arguments:
- address RFC2821 Address to use
- key The key to use (must be less than 64 characters
- in size)
- key_num Single-digit key number to use. Defaults to
- '0' when NULL.
-
-Returns: pointer to string containing the first three
- bytes of the final hash in hex format, NULL if
- there was an error in the process.
-*/
-
-uschar *
-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;
-uschar innerhash[20];
-uschar finalhash[20];
-uschar innerkey[64];
-uschar outerkey[64];
-uschar *finalhash_hex = store_get(40);
-
-if (key_num == NULL)
- key_num = US"0";
-
-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[offset] = '\0';
-
-DEBUG(D_expand) debug_printf("prvs: hash source is '%s'\n", hash_source);
-
-memset(innerkey, 0x36, 64);
-memset(outerkey, 0x5c, 64);
-
-for (i = 0; i < Ustrlen(key); i++)
- {
- innerkey[i] ^= 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, use_base);
-chash_mid(HMAC_SHA1, use_base, outerkey);
-chash_end(HMAC_SHA1, use_base, innerhash, 20, finalhash);
-
-p = finalhash_hex;
-for (i = 0; i < 3; i++)
- {
- *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
- *p++ = hex_digits[finalhash[i] & 0x0f];
- }
-*p = '\0';
-
-return finalhash_hex;
-}
/*************************************************
**************************************************