Add a whole pile of casts to get rid of compiler warnings in Tom's DK
[exim.git] / src / src / expand.c
index d27530bd7b16059ce9beb9bc4e313b02792c55c5..f2ce1f98960397a590db7f46470fd25adc2ecf23 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/expand.c,v 1.17 2005/03/22 14:11:54 ph10 Exp $ */
+/* $Cambridge: exim/src/src/expand.c,v 1.32 2005/06/20 11:20:41 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -57,6 +57,8 @@ static uschar *item_table[] = {
   US"lookup",
   US"nhash",
   US"perl",
+  US"prvs",
+  US"prvscheck",
   US"readfile",
   US"readsocket",
   US"run",
@@ -74,6 +76,8 @@ enum {
   EITEM_LOOKUP,
   EITEM_NHASH,
   EITEM_PERL,
+  EITEM_PRVS,
+  EITEM_PRVSCHECK,
   EITEM_READFILE,
   EITEM_READSOCK,
   EITEM_RUN,
@@ -333,6 +337,7 @@ static var_entry var_table[] = {
   { "caller_uid",          vtype_uid,         &real_uid },
   { "compile_date",        vtype_stringptr,   &version_date },
   { "compile_number",      vtype_stringptr,   &version_cnumber },
+  { "csa_status",          vtype_stringptr,   &csa_status },
 #ifdef WITH_OLD_DEMIME
   { "demime_errorlevel",   vtype_int,         &demime_errorlevel },
   { "demime_reason",       vtype_stringptr,   &demime_reason },
@@ -394,6 +399,7 @@ static var_entry var_table[] = {
   { "message_body_size",   vtype_int,         &message_body_size },
   { "message_headers",     vtype_msgheaders,  NULL },
   { "message_id",          vtype_stringptr,   &message_id },
+  { "message_linecount",   vtype_int,         &message_linecount },
   { "message_size",        vtype_int,         &message_size },
 #ifdef WITH_CONTENT_SCAN
   { "mime_anomaly_level",  vtype_int,         &mime_anomaly_level },
@@ -431,6 +437,9 @@ static var_entry var_table[] = {
   { "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 },
@@ -439,6 +448,7 @@ static var_entry var_table[] = {
   { "received_count",      vtype_int,         &received_count },
   { "received_for",        vtype_stringptr,   &received_for },
   { "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 },
@@ -463,6 +473,9 @@ static var_entry var_table[] = {
   { "sender_host_name",    vtype_host_lookup, NULL },
   { "sender_host_port",    vtype_int,         &sender_host_port },
   { "sender_ident",        vtype_stringptr,   &sender_ident },
+  { "sender_rate",         vtype_stringptr,   &sender_rate },
+  { "sender_rate_limit",   vtype_stringptr,   &sender_rate_limit },
+  { "sender_rate_period",  vtype_stringptr,   &sender_rate_period },
   { "sender_rcvhost",      vtype_stringptr,   &sender_rcvhost },
   { "sender_verify_failure",vtype_stringptr,  &sender_verify_failure },
   { "smtp_active_hostname", vtype_stringptr,  &smtp_active_hostname },
@@ -1268,31 +1281,31 @@ while (last > first)
 
     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
@@ -1358,7 +1371,7 @@ while (last > first)
     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);
@@ -1590,8 +1603,8 @@ if (name[0] == 0)
 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 != ':')
@@ -1616,8 +1629,8 @@ switch(cond_type)
       (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
     {
@@ -1629,8 +1642,7 @@ switch(cond_type)
         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;
@@ -2315,11 +2327,15 @@ if (*s == '}')
   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;
@@ -2331,8 +2347,8 @@ if (yes)
 
 /* If this is called from a lookup or an extract, we want to restore $value to
 what it was at the start of the item, so that it has this value during the
-second string expansion. For the call from "if" to this function, save_lookup
-is set to lookup_value, so that this statement does nothing. */
+second string expansion. For the call from "if" or "run" to this function,
+save_lookup is set to lookup_value, so that this statement does nothing. */
 
 lookup_value = save_lookup;
 
@@ -2409,8 +2425,6 @@ return rc;
 
 
 
-
-
 /*************************************************
 *    Handle MD5 or SHA-1 computation for HMAC    *
 *************************************************/
@@ -2456,6 +2470,110 @@ else
 
 
 
+/********************************************************
+* 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      *
 *************************************************/
@@ -3154,6 +3272,172 @@ while (*s != 0)
       }
     #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;
+
+          sscanf(CS now,"%u",&inow);
+          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:
@@ -3328,7 +3612,6 @@ while (*s != 0)
     case EITEM_RUN:
       {
       FILE *f;
-      uschar *old_lookup_value = NULL;
       uschar *arg;
       uschar **argv;
       pid_t pid;
@@ -3408,18 +3691,17 @@ while (*s != 0)
         in lookup_value). */
 
         f = fdopen(fd_out, "rb");
-        old_lookup_value = lookup_value;
         lookup_value = NULL;
         lookup_value = cat_file(f, lookup_value, &lsize, &lptr, NULL);
         fclose(f);
         }
 
-      /* Process the yes/no strings */
+      /* Process the yes/no strings; $value may be useful in both cases */
 
       switch(process_yesno(
                skipping,                     /* were previously skipping */
                runrc == 0,                   /* success/failure indicator */
-               old_lookup_value,             /* value to reset for string2 */
+               lookup_value,                 /* value to reset for string2 */
                &s,                           /* input pointer */
                &yield,                       /* output pointer */
                &size,                        /* output size */
@@ -3797,8 +4079,8 @@ while (*s != 0)
 
             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;
               }
 
@@ -3920,7 +4202,7 @@ while (*s != 0)
         {
         expand_string_message = string_sprintf("dlsym \"%s\" in \"%s\" failed: "
           "%s", argv[1], argv[0], dlerror());
-       log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
+        log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
         goto EXPAND_FAILED;
         }
 
@@ -4533,10 +4815,10 @@ while (*s != 0)
 
         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;
@@ -4762,7 +5044,6 @@ return -2;
 }
 
 
-
 /*************************************************
 **************************************************
 *             Stand-alone test program           *