Fix ${def: bug which treated "0" as false. Also diagnose syntax error of
[exim.git] / src / src / expand.c
index f809fe5172dfb44dd2760054c0946c87c556bf15..9daf77d0bd39d88d445e343ec2c9f62fa3b7ab41 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/expand.c,v 1.10 2005/01/04 10:00:42 ph10 Exp $ */
+/* $Cambridge: exim/src/src/expand.c,v 1.23 2005/06/07 10:41:27 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -48,6 +48,7 @@ static uschar *expand_string_internal(uschar *, BOOL, uschar **, BOOL);
 alphabetical order. */
 
 static uschar *item_table[] = {
+  US"dlfunc",
   US"extract",
   US"hash",
   US"hmac",
@@ -55,9 +56,7 @@ static uschar *item_table[] = {
   US"length",
   US"lookup",
   US"nhash",
-  #ifdef EXIM_PERL
-    US"perl",
-  #endif
+  US"perl",
   US"readfile",
   US"readsocket",
   US"run",
@@ -66,6 +65,7 @@ static uschar *item_table[] = {
   US"tr" };
 
 enum {
+  EITEM_DLFUNC,
   EITEM_EXTRACT,
   EITEM_HASH,
   EITEM_HMAC,
@@ -73,9 +73,7 @@ enum {
   EITEM_LENGTH,
   EITEM_LOOKUP,
   EITEM_NHASH,
-  #ifdef EXIM_PERL
-    EITEM_PERL,
-  #endif
+  EITEM_PERL,
   EITEM_READFILE,
   EITEM_READSOCK,
   EITEM_RUN,
@@ -285,7 +283,10 @@ enum {
   vtype_host_lookup,    /* value not used; get host name */
   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 */  
+  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
   };
 
 /* This table must be kept in alphabetical order. */
@@ -332,9 +333,23 @@ 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 },
+#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 },
 #endif
   { "dnslist_domain",      vtype_stringptr,   &dnslist_domain },
   { "dnslist_text",        vtype_stringptr,   &dnslist_text },
@@ -346,11 +361,12 @@ static var_entry var_table[] = {
   { "exim_uid",            vtype_uid,         &exim_uid },
 #ifdef WITH_OLD_DEMIME
   { "found_extension",     vtype_stringptr,   &found_extension },
-#endif 
+#endif
   { "home",                vtype_stringptr,   &deliver_home },
   { "host",                vtype_stringptr,   &deliver_host },
   { "host_address",        vtype_stringptr,   &deliver_host_address },
   { "host_data",           vtype_stringptr,   &host_data },
+  { "host_lookup_deferred",vtype_int,         &host_lookup_deferred },
   { "host_lookup_failed",  vtype_int,         &host_lookup_failed },
   { "inode",               vtype_ino,         &deliver_inode },
   { "interface_address",   vtype_stringptr,   &interface_address },
@@ -368,7 +384,7 @@ static var_entry var_table[] = {
   { "local_user_uid",      vtype_uid,         &local_user_uid },
   { "localhost_number",    vtype_int,         &host_number },
   { "log_inodes",          vtype_pinodes,     (void *)FALSE },
-  { "log_space",           vtype_pspace,      (void *)FALSE },  
+  { "log_space",           vtype_pspace,      (void *)FALSE },
   { "mailstore_basename",  vtype_stringptr,   &mailstore_basename },
 #ifdef WITH_CONTENT_SCAN
   { "malware_name",        vtype_stringptr,   &malware_name },
@@ -379,6 +395,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 },
@@ -424,8 +441,9 @@ 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 }, 
+  { "recipient_verify_failure",vtype_stringptr,&recipient_verify_failure },
   { "recipients",          vtype_recipients,  NULL },
   { "recipients_count",    vtype_int,         &recipients_count },
 #ifdef WITH_CONTENT_SCAN
@@ -448,9 +466,12 @@ 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 }, 
+  { "sender_verify_failure",vtype_stringptr,  &sender_verify_failure },
+  { "smtp_active_hostname", vtype_stringptr,  &smtp_active_hostname },
   { "smtp_command_argument", vtype_stringptr, &smtp_command_argument },
   { "sn0",                 vtype_filter_int,  &filter_sn[0] },
   { "sn1",                 vtype_filter_int,  &filter_sn[1] },
@@ -476,7 +497,7 @@ static var_entry var_table[] = {
 #endif
   { "spool_directory",     vtype_stringptr,   &spool_directory },
   { "spool_inodes",        vtype_pinodes,     (void *)TRUE },
-  { "spool_space",         vtype_pspace,      (void *)TRUE },  
+  { "spool_space",         vtype_pspace,      (void *)TRUE },
 #ifdef EXPERIMENTAL_SRS
   { "srs_db_address",      vtype_stringptr,   &srs_db_address },
   { "srs_db_key",          vtype_stringptr,   &srs_db_key },
@@ -1237,6 +1258,51 @@ while (last > first)
     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
+
     case vtype_int:
     sprintf(CS var_buffer, "%d", *(int *)(var_table[middle].value)); /* Integer */
     return var_buffer;
@@ -1374,19 +1440,19 @@ while (last > first)
       s[ptr] = 0;     /* string_cat() leaves room */
       }
     return s;
-    
+
     case vtype_pspace:
       {
       int inodes;
-      sprintf(CS var_buffer, "%d", 
-        receive_statvfs((BOOL)(var_table[middle].value), &inodes));  
+      sprintf(CS var_buffer, "%d",
+        receive_statvfs(var_table[middle].value == (void *)TRUE, &inodes));
       }
     return var_buffer;
-    
+
     case vtype_pinodes:
       {
       int inodes;
-      (void) receive_statvfs((BOOL)(var_table[middle].value), &inodes);  
+      (void) receive_statvfs(var_table[middle].value == (void *)TRUE, &inodes);
       sprintf(CS var_buffer, "%d", inodes);
       }
     return var_buffer;
@@ -1530,8 +1596,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 != ':')
@@ -1556,8 +1622,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
     {
@@ -1569,8 +1635,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;
@@ -2234,7 +2299,7 @@ uschar *sub1, *sub2;
 
 /* If there are no following strings, we substitute the contents of $value for
 lookups and for extractions in the success case. For the ${if item, the string
-"true" is substituted. In the fail case, nothing is substituted for all three 
+"true" is substituted. In the fail case, nothing is substituted for all three
 items. */
 
 while (isspace(*s)) s++;
@@ -2242,10 +2307,10 @@ if (*s == '}')
   {
   if (type[0] == 'i')
     {
-    if (yes) *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, US"true", 4); 
+    if (yes) *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, US"true", 4);
     }
   else
-    {      
+    {
     if (yes && lookup_value != NULL)
       *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value,
         Ustrlen(lookup_value));
@@ -2255,11 +2320,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;
@@ -2271,8 +2340,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;
 
@@ -3010,10 +3079,15 @@ while (*s != 0)
     or ${perl{sub}{arg1}{arg2}} or up to a maximum of EXIM_PERL_MAX_ARGS
     arguments (defined below). */
 
-    #ifdef EXIM_PERL
     #define EXIM_PERL_MAX_ARGS 8
 
     case EITEM_PERL:
+    #ifndef EXIM_PERL
+    expand_string_message = US"\"${perl\" encountered, but this facility "
+      "is not included in this binary";
+    goto EXPAND_FAILED;
+
+    #else   /* EXIM_PERL */
       {
       uschar *sub_arg[EXIM_PERL_MAX_ARGS + 2];
       uschar *new_yield;
@@ -3263,7 +3337,6 @@ while (*s != 0)
     case EITEM_RUN:
       {
       FILE *f;
-      uschar *old_lookup_value = NULL;
       uschar *arg;
       uschar **argv;
       pid_t pid;
@@ -3343,18 +3416,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 */
@@ -3785,6 +3857,106 @@ while (*s != 0)
 
       continue;
       }
+
+
+    /* If ${dlfunc support is configured, handle calling dynamically-loaded
+    functions, unless locked out at this time. Syntax is ${dlfunc{file}{func}}
+    or ${dlfunc{file}{func}{arg}} or ${dlfunc{file}{func}{arg1}{arg2}} or up to
+    a maximum of EXPAND_DLFUNC_MAX_ARGS arguments (defined below). */
+
+    #define EXPAND_DLFUNC_MAX_ARGS 8
+
+    case EITEM_DLFUNC:
+    #ifndef EXPAND_DLFUNC
+    expand_string_message = US"\"${dlfunc\" encountered, but this facility "
+      "is not included in this binary";
+    goto EXPAND_FAILED;
+
+    #else   /* EXPAND_DLFUNC */
+      {
+      tree_node *t;
+      exim_dlfunc_t *func;
+      uschar *result;
+      int status, argc;
+      uschar *argv[EXPAND_DLFUNC_MAX_ARGS + 3];
+
+      if ((expand_forbid & RDO_DLFUNC) != 0)
+        {
+        expand_string_message =
+          US"dynamically-loaded functions are not permitted";
+        goto EXPAND_FAILED;
+        }
+
+      switch(read_subs(argv, EXPAND_DLFUNC_MAX_ARGS + 2, 2, &s, skipping,
+           TRUE, US"dlfunc"))
+        {
+        case 1: goto EXPAND_FAILED_CURLY;
+        case 2:
+        case 3: goto EXPAND_FAILED;
+        }
+
+      /* If skipping, we don't actually do anything */
+
+      if (skipping) continue;
+
+      /* Look up the dynamically loaded object handle in the tree. If it isn't
+      found, dlopen() the file and put the handle in the tree for next time. */
+
+      t = tree_search(dlobj_anchor, argv[0]);
+      if (t == NULL)
+        {
+        void *handle = dlopen(CS argv[0], RTLD_LAZY);
+        if (handle == NULL)
+          {
+          expand_string_message = string_sprintf("dlopen \"%s\" failed: %s",
+            argv[0], dlerror());
+          log_write(0, LOG_MAIN|LOG_PANIC, "%s", expand_string_message);
+          goto EXPAND_FAILED;
+          }
+        t = store_get_perm(sizeof(tree_node) + Ustrlen(argv[0]));
+        Ustrcpy(t->name, argv[0]);
+        t->data.ptr = handle;
+        (void)tree_insertnode(&dlobj_anchor, t);
+        }
+
+      /* Having obtained the dynamically loaded object handle, look up the
+      function pointer. */
+
+      func = (exim_dlfunc_t *)dlsym(t->data.ptr, CS argv[1]);
+      if (func == NULL)
+        {
+        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);
+        goto EXPAND_FAILED;
+        }
+
+      /* Call the function and work out what to do with the result. If it
+      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. */
+
+      result = NULL;
+      for (argc = 0; argv[argc] != NULL; argc++);
+      status = func(&result, argc - 2, &argv[2]);
+      if(status == OK)
+        {
+        if (result == NULL) result = US"";
+        yield = string_cat(yield, &size, &ptr, result, Ustrlen(result));
+        continue;
+        }
+      else
+        {
+        expand_string_message = result == NULL ? US"(no message)" : result;
+        if(status == FAIL_FORCED) expand_string_forcedfail = TRUE;
+          else if(status != FAIL)
+            log_write(0, LOG_MAIN|LOG_PANIC, "dlfunc{%s}{%s} failed (%d): %s",
+              argv[0], argv[1], status, expand_string_message);
+        goto EXPAND_FAILED;
+        }
+      }
+    #endif /* EXPAND_DLFUNC */
     }
 
   /* Control reaches here if the name is not recognized as one of the more