Make $value usable in the "else" part of ${run.
[exim.git] / src / src / expand.c
index afe57f9b00449cf3cbcb86a52cb01501df28be72..49686555e3d0eb2048f4022bd3b6c867da58cc16 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/expand.c,v 1.2 2004/10/19 13:40:39 ph10 Exp $ */
+/* $Cambridge: exim/src/src/expand.c,v 1.20 2005/04/28 13:29:27 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2005 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 
@@ -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,
@@ -283,7 +281,12 @@ enum {
   vtype_reply,          /* value not used; get reply from headers */
   vtype_pid,            /* value not used; result is pid */
   vtype_host_lookup,    /* value not used; get host name */
-  vtype_load_avg        /* value not used; result is int from os_getloadavg */
+  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
   };
 
 /* This table must be kept in alphabetical order. */
@@ -316,6 +319,12 @@ static var_entry var_table[] = {
   { "authenticated_id",    vtype_stringptr,   &authenticated_id },
   { "authenticated_sender",vtype_stringptr,   &authenticated_sender },
   { "authentication_failed",vtype_int,        &authentication_failed },
+#ifdef EXPERIMENTAL_BRIGHTMAIL
+  { "bmi_alt_location",    vtype_stringptr,   &bmi_alt_location },
+  { "bmi_base64_tracker_verdict", vtype_stringptr, &bmi_base64_tracker_verdict },
+  { "bmi_base64_verdict",  vtype_stringptr,   &bmi_base64_verdict },
+  { "bmi_deliver",         vtype_int,         &bmi_deliver },
+#endif
   { "body_linecount",      vtype_int,         &body_linecount },
   { "body_zerocount",      vtype_int,         &body_zerocount },
   { "bounce_recipient",    vtype_stringptr,   &bounce_recipient },
@@ -324,6 +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 },
+#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 },
   { "dnslist_value",       vtype_stringptr,   &dnslist_value },
@@ -332,10 +358,14 @@ static var_entry var_table[] = {
   { "exim_gid",            vtype_gid,         &exim_gid },
   { "exim_path",           vtype_stringptr,   &exim_path },
   { "exim_uid",            vtype_uid,         &exim_uid },
+#ifdef WITH_OLD_DEMIME
+  { "found_extension",     vtype_stringptr,   &found_extension },
+#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 },
@@ -352,14 +382,38 @@ static var_entry var_table[] = {
   { "local_user_gid",      vtype_gid,         &local_user_gid },
   { "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 },
   { "mailstore_basename",  vtype_stringptr,   &mailstore_basename },
+#ifdef WITH_CONTENT_SCAN
+  { "malware_name",        vtype_stringptr,   &malware_name },
+#endif
   { "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_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 },
+  { "mime_anomaly_text",   vtype_stringptr,   &mime_anomaly_text },
+  { "mime_boundary",       vtype_stringptr,   &mime_boundary },
+  { "mime_charset",        vtype_stringptr,   &mime_charset },
+  { "mime_content_description", vtype_stringptr, &mime_content_description },
+  { "mime_content_disposition", vtype_stringptr, &mime_content_disposition },
+  { "mime_content_id",     vtype_stringptr,   &mime_content_id },
+  { "mime_content_size",   vtype_int,         &mime_content_size },
+  { "mime_content_transfer_encoding",vtype_stringptr, &mime_content_transfer_encoding },
+  { "mime_content_type",   vtype_stringptr,   &mime_content_type },
+  { "mime_decoded_filename", vtype_stringptr, &mime_decoded_filename },
+  { "mime_filename",       vtype_stringptr,   &mime_filename },
+  { "mime_is_coverletter", vtype_int,         &mime_is_coverletter },
+  { "mime_is_multipart",   vtype_int,         &mime_is_multipart },
+  { "mime_is_rfc822",      vtype_int,         &mime_is_rfc822 },
+  { "mime_part_count",     vtype_int,         &mime_part_count },
+#endif
   { "n0",                  vtype_filter_int,  &filter_n[0] },
   { "n1",                  vtype_filter_int,  &filter_n[1] },
   { "n2",                  vtype_filter_int,  &filter_n[2] },
@@ -386,9 +440,14 @@ 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 },
   { "recipients_count",    vtype_int,         &recipients_count },
+#ifdef WITH_CONTENT_SCAN
+  { "regex_match_string",  vtype_stringptr,   &regex_match_string },
+#endif
   { "reply_address",       vtype_reply,       NULL },
   { "return_path",         vtype_stringptr,   &return_path },
   { "return_size_limit",   vtype_int,         &bounce_return_size_limit },
@@ -407,6 +466,8 @@ static var_entry var_table[] = {
   { "sender_host_port",    vtype_int,         &sender_host_port },
   { "sender_ident",        vtype_stringptr,   &sender_ident },
   { "sender_rcvhost",      vtype_stringptr,   &sender_rcvhost },
+  { "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] },
@@ -418,7 +479,29 @@ static var_entry var_table[] = {
   { "sn7",                 vtype_filter_int,  &filter_sn[7] },
   { "sn8",                 vtype_filter_int,  &filter_sn[8] },
   { "sn9",                 vtype_filter_int,  &filter_sn[9] },
+#ifdef WITH_CONTENT_SCAN
+  { "spam_bar",            vtype_stringptr,   &spam_bar },
+  { "spam_report",         vtype_stringptr,   &spam_report },
+  { "spam_score",          vtype_stringptr,   &spam_score },
+  { "spam_score_int",      vtype_stringptr,   &spam_score_int },
+#endif
+#ifdef EXPERIMENTAL_SPF
+  { "spf_header_comment",  vtype_stringptr,   &spf_header_comment },
+  { "spf_received",        vtype_stringptr,   &spf_received },
+  { "spf_result",          vtype_stringptr,   &spf_result },
+  { "spf_smtp_comment",    vtype_stringptr,   &spf_smtp_comment },
+#endif
   { "spool_directory",     vtype_stringptr,   &spool_directory },
+  { "spool_inodes",        vtype_pinodes,     (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 },
+  { "srs_orig_recipient",  vtype_stringptr,   &srs_orig_recipient },
+  { "srs_orig_sender",     vtype_stringptr,   &srs_orig_sender },
+  { "srs_recipient",       vtype_stringptr,   &srs_recipient },
+  { "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 },
@@ -1171,6 +1254,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;
@@ -1308,6 +1436,22 @@ while (last > first)
       s[ptr] = 0;     /* string_cat() leaves room */
       }
     return s;
+
+    case vtype_pspace:
+      {
+      int 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(var_table[middle].value == (void *)TRUE, &inodes);
+      sprintf(CS var_buffer, "%d", inodes);
+      }
+    return var_buffer;
     }
   }
 
@@ -2151,17 +2295,24 @@ uschar *s = *sptr;    /* Local value */
 uschar *sub1, *sub2;
 
 /* If there are no following strings, we substitute the contents of $value for
-lookups and for extractions in the success case. In the fail case, nothing is
-substituted. In the case of "if", lack of following strings is an error. */
+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
+items. */
 
 while (isspace(*s)) s++;
 if (*s == '}')
   {
-  if (type[0] == 'i') goto FAILED_CURLY;
-  if (yes && lookup_value != NULL)
-    *yieldptr = string_cat(*yieldptr, sizeptr, ptrptr, lookup_value,
-      Ustrlen(lookup_value));
-  lookup_value = save_lookup;
+  if (type[0] == 'i')
+    {
+    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));
+    lookup_value = save_lookup;
+    }
   s++;
   goto RETURN;
   }
@@ -2182,8 +2333,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;
 
@@ -2921,10 +3072,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;
@@ -3174,7 +3330,6 @@ while (*s != 0)
     case EITEM_RUN:
       {
       FILE *f;
-      uschar *old_lookup_value = NULL;
       uschar *arg;
       uschar **argv;
       pid_t pid;
@@ -3254,18 +3409,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 */
@@ -3696,6 +3850,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
@@ -3927,7 +4181,7 @@ while (*s != 0)
         /* Convert to masked textual format and add to output. */
 
         yield = string_cat(yield, &size, &ptr, buffer,
-          host_nmtoa(count, binary, mask, buffer));
+          host_nmtoa(count, binary, mask, buffer, '.'));
         continue;
         }
 
@@ -4059,7 +4313,7 @@ while (*s != 0)
 
       case EOP_RFC2047:
         {
-        uschar buffer[1024];
+        uschar buffer[2048];
         uschar *string = parse_quote_2047(sub, Ustrlen(sub), headers_charset,
           buffer, sizeof(buffer));
         yield = string_cat(yield, &size, &ptr, string, Ustrlen(string));