-/* $Cambridge: exim/src/src/expand.c,v 1.22 2005/05/23 16:58:56 fanf2 Exp $ */
+/* $Cambridge: exim/src/src/expand.c,v 1.38 2005/08/01 14:00:35 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
US"lookup",
US"nhash",
US"perl",
+ US"prvs",
+ US"prvscheck",
US"readfile",
US"readsocket",
US"run",
EITEM_LOOKUP,
EITEM_NHASH,
EITEM_PERL,
+ EITEM_PRVS,
+ EITEM_PRVSCHECK,
EITEM_READFILE,
EITEM_READSOCK,
EITEM_RUN,
US"match",
US"match_address",
US"match_domain",
+ US"match_ip",
US"match_local_part",
US"or",
US"pam",
ECOND_MATCH,
ECOND_MATCH_ADDRESS,
ECOND_MATCH_DOMAIN,
+ ECOND_MATCH_IP,
ECOND_MATCH_LOCAL_PART,
ECOND_OR,
ECOND_PAM,
{ "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_id", vtype_stringptr, &message_id },
{ "message_linecount", vtype_int, &message_linecount },
{ "parent_local_part", vtype_stringptr, &deliver_localpart_parent },
{ "pid", vtype_pid, NULL },
{ "primary_hostname", vtype_stringptr, &primary_hostname },
+ { "prvscheck_address", vtype_stringptr, &prvscheck_address },
+ { "prvscheck_keynum", vtype_stringptr, &prvscheck_keynum },
+ { "prvscheck_result", vtype_stringptr, &prvscheck_result },
{ "qualify_domain", vtype_stringptr, &qualify_domain_sender },
{ "qualify_recipient", vtype_stringptr, &qualify_domain_recipient },
{ "rcpt_count", vtype_int, &rcpt_count },
switch (var_table[middle].type)
{
- case vtype_filter_int:
- if (!filter_running) return NULL;
- /* Fall through */
-
#ifdef EXPERIMENTAL_DOMAINKEYS
case vtype_dk_verify:
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;
+ case DK_EXIM_ADDRESS_NONE: s = US"0"; break;
+ case DK_EXIM_ADDRESS_FROM_FROM: s = US"from"; break;
+ case DK_EXIM_ADDRESS_FROM_SENDER: s = US"sender"; break;
}
if (Ustrcmp(var_table[middle].name, "dk_status") == 0)
switch(dk_verify_block->result) {
- case DK_EXIM_RESULT_ERR: s = "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;
+ case DK_EXIM_RESULT_ERR: s = US"error"; break;
+ case DK_EXIM_RESULT_BAD_FORMAT: s = US"bad format"; break;
+ case DK_EXIM_RESULT_NO_KEY: s = US"no key"; break;
+ case DK_EXIM_RESULT_NO_SIGNATURE: s = US"no signature"; break;
+ case DK_EXIM_RESULT_REVOKED: s = US"revoked"; break;
+ case DK_EXIM_RESULT_NON_PARTICIPANT: s = US"non-participant"; break;
+ case DK_EXIM_RESULT_GOOD: s = US"good"; break;
+ case DK_EXIM_RESULT_BAD: s = US"bad"; break;
}
if (Ustrcmp(var_table[middle].name, "dk_signsall") == 0)
- s = (dk_verify_block->signsall)? "1" : "0";
+ s = (dk_verify_block->signsall)? US"1" : US"0";
if (Ustrcmp(var_table[middle].name, "dk_testing") == 0)
- s = (dk_verify_block->testing)? "1" : "0";
+ s = (dk_verify_block->testing)? US"1" : US"0";
if (Ustrcmp(var_table[middle].name, "dk_is_signed") == 0)
- s = (dk_verify_block->is_signed)? "1" : "0";
+ s = (dk_verify_block->is_signed)? US"1" : US"0";
return (s == NULL)? US"" : s;
#endif
+ case vtype_filter_int:
+ if (!filter_running) return NULL;
+ /* Fall through */
+ /* VVVVVVVVVVVV */
case vtype_int:
sprintf(CS var_buffer, "%d", *(int *)(var_table[middle].value)); /* Integer */
return var_buffer;
if (*ss == NULL && deliver_datafile >= 0) /* Read body when needed */
{
uschar *body;
- int start_offset = SPOOL_DATA_START_OFFSET;
+ off_t start_offset = SPOOL_DATA_START_OFFSET;
int len = message_body_visible;
if (len > message_size) len = message_size;
*ss = body = store_malloc(len+1);
cond_type = chop_match(name, cond_table, sizeof(cond_table)/sizeof(uschar *));
switch(cond_type)
{
- /* def: tests for a non-zero or non-NULL variable, or for an existing
- header */
+ /* def: tests for a non-empty variable, or for the existence of a header. If
+ yield == NULL we are in a skipping state, and don't care about the answer. */
case ECOND_DEF:
if (*s != ':')
(find_header(name, TRUE, NULL, FALSE, NULL) != NULL) == testfor;
}
- /* Test for a variable's having a non-empty value. If yield == NULL we
- are in a skipping state, and don't care about the answer. */
+ /* Test for a variable's having a non-empty value. A non-existent variable
+ causes an expansion failure. */
else
{
string_sprintf("unknown variable \"%s\" after \"def:\"", name);
return NULL;
}
- if (yield != NULL)
- *yield = (value[0] != 0 && Ustrcmp(value, "0") != 0) == testfor;
+ if (yield != NULL) *yield = (value[0] != 0) == testfor;
}
return s;
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:
case ECOND_CRYPTEQ:
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)
{
goto RETURN;
}
+/* The first following string must be braced. */
+
+if (*s++ != '{') goto FAILED_CURLY;
+
/* Expand the first substring. Forced failures are noticed only if we actually
want this string. Set skipping in the call in the fail case (this will always
be the case if we were already skipping). */
-sub1 = expand_string_internal(s+1, TRUE, &s, !yes);
+sub1 = expand_string_internal(s, TRUE, &s, !yes);
if (sub1 == 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(16);
+(void)string_format(days, 16, TIME_T_FMT,
+ (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 *
*************************************************/
/* 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);
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
}
#endif /* EXIM_PERL */
+ /* Transform email address to "prvs" scheme to use
+ as BATV-signed return path */
+
+ case EITEM_PRVS:
+ {
+ uschar *sub_arg[3];
+ uschar *p,*domain;
+
+ switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, US"prvs"))
+ {
+ case 1: goto EXPAND_FAILED_CURLY;
+ case 2:
+ case 3: goto EXPAND_FAILED;
+ }
+
+ /* If skipping, we don't actually do anything */
+ if (skipping) continue;
+
+ /* sub_arg[0] is the address */
+ 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";
+ 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";
+ goto EXPAND_FAILED;
+ }
+
+ /* Now separate the domain from the local part */
+ *domain++ = '\0';
+
+ yield = string_cat(yield,&size,&ptr,US"prvs=",5);
+ string_cat(yield,&size,&ptr,sub_arg[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,domain,Ustrlen(domain));
+
+ continue;
+ }
+
+ /* Check a prvs-encoded address for validity */
+
+ case EITEM_PRVSCHECK:
+ {
+ uschar *sub_arg[3];
+ int mysize = 0, myptr = 0;
+ const pcre *re;
+ uschar *p;
+ /* 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);
+
+ /* 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"))
+ {
+ 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})\\@(.+)$",
+ TRUE,FALSE);
+
+ if (regex_match_and_setup(re,sub_arg[0],0,-1)) {
+ uschar *local_part = string_copyn(expand_nstring[1],expand_nlength[1]);
+ uschar *key_num = string_copyn(expand_nstring[2],expand_nlength[2]);
+ uschar *daystamp = string_copyn(expand_nstring[3],expand_nlength[3]);
+ uschar *hash = string_copyn(expand_nstring[4],expand_nlength[4]);
+ uschar *domain = string_copyn(expand_nstring[5],expand_nlength[5]);
+
+ DEBUG(D_expand) debug_printf("prvscheck localpart: %s\n", local_part);
+ DEBUG(D_expand) debug_printf("prvscheck key number: %s\n", key_num);
+ DEBUG(D_expand) debug_printf("prvscheck daystamp: %s\n", daystamp);
+ DEBUG(D_expand) debug_printf("prvscheck hash: %s\n", hash);
+ DEBUG(D_expand) debug_printf("prvscheck domain: %s\n", domain);
+
+ /* Set up expansion variables */
+ prvscheck_address = string_cat(NULL, &mysize, &myptr, local_part, Ustrlen(local_part));
+ string_cat(prvscheck_address,&mysize,&myptr,US"@",1);
+ string_cat(prvscheck_address,&mysize,&myptr,domain,Ustrlen(domain));
+ prvscheck_address[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"))
+ {
+ 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);
+ if (p == NULL)
+ {
+ expand_string_message = US"hmac-sha1 conversion failed";
+ goto EXPAND_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;
+
+ (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)
+ {
+ prvscheck_result = US"1";
+ DEBUG(D_expand) debug_printf("prvscheck: success, $pvrs_result set to 1\n");
+ }
+ else
+ {
+ prvscheck_result = NULL;
+ DEBUG(D_expand) debug_printf("prvscheck: signature expired, $pvrs_result unset\n");
+ }
+ }
+ else
+ {
+ prvscheck_result = NULL;
+ DEBUG(D_expand) debug_printf("prvscheck: hash failure, $pvrs_result unset\n");
+ }
+ }
+ 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"))
+ {
+ case 1: goto EXPAND_FAILED_CURLY;
+ case 2:
+ case 3: goto EXPAND_FAILED;
+ }
+ }
+
+ continue;
+ }
+
/* Handle "readfile" to insert an entire file */
case EITEM_READFILE:
}
yield = cat_file(f, yield, &size, &ptr, sub_arg[1]);
- fclose(f);
+ (void)fclose(f);
continue;
}
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. */
/* Nothing is written to the standard input. */
- close(fd_in);
+ (void)close(fd_in);
/* Wait for the process to finish, applying the timeout, and inspect its
return code for serious disasters. Simple non-zero returns are passed on.
f = fdopen(fd_out, "rb");
lookup_value = NULL;
lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL);
- fclose(f);
+ (void)fclose(f);
}
/* Process the yes/no strings; $value may be useful in both cases */
if (*p == 0)
{
- expand_string_message = US"first argument of \"expand\" must not "
- "be empty";
+ expand_string_message = US"first argument of \"extract\" must "
+ "not be empty";
goto EXPAND_FAILED;
}
smode[10] = 0;
s = string_sprintf("mode=%04lo smode=%s inode=%ld device=%ld links=%ld "
- "uid=%ld gid=%ld size=%ld atime=%ld mtime=%ld ctime=%ld",
+ "uid=%ld gid=%ld size=" OFF_T_FMT " atime=%ld mtime=%ld ctime=%ld",
(long)(st.st_mode & 077777), smode, (long)st.st_ino,
(long)st.st_dev, (long)st.st_nlink, (long)st.st_uid,
- (long)st.st_gid, (long)st.st_size, (long)st.st_atime,
+ (long)st.st_gid, st.st_size, (long)st.st_atime,
(long)st.st_mtime, (long)st.st_ctime);
yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
continue;
}
-
/*************************************************
**************************************************
* Stand-alone test program *