[exim-dev] ${dlfunc

Top Page
Delete this message
Reply to this message
Author: Tony Finch
Date:  
To: exim-dev
Subject: [exim-dev] ${dlfunc
I need a little more control over string expansion failure than ${readsock
can provide, however I don't want the weight of Perl or ${run. In
particular it's awkward to get a non-forced expansion failure out of
${readsock, which is needed for sensible error handling with options like
errors_to. This patch implements a ${dlfunc string expansion hook for C
programmers. Flames and/or adulation welcome.

Tony.
--
<fanf@???> <dot@???> http://dotat.at/ ${sg{\N${sg{\
N\}{([^N]*)(.)(.)(.*)}{\$1\$3\$2\$1\$3\n\$2\$3\$4\$3\n\$3\$2\$4}}\
\N}{([^N]*)(.)(.)(.*)}{\$1\$3\$2\$1\$3\n\$2\$3\$4\$3\n\$3\$2\$4}}--- src/EDITME    17 Feb 2005 11:58:25 -0000    1.8
+++ src/EDITME    18 Feb 2005 21:15:40 -0000
@@ -690,6 +690,15 @@
 
 
 #------------------------------------------------------------------------------
+# Support for dynamically-loaded string expansion functions via ${dlfunc. If
+# you are using gcc the dynamically-loaded object must be compiled with the
+# -shared option, and you will need to add -export-dynamic to EXTRALIBS so
+# that the local_scan API is made available by the linker.
+
+# EXPAND_DLFUNC=yes
+
+
+#------------------------------------------------------------------------------
 # Exim has support for PAM (Pluggable Authentication Modules), a facility
 # which is available in the latest releases of Solaris and in some GNU/Linux
 # distributions (see http://ftp.kernel.org/pub/linux/libs/pam/). The Exim
--- src/config.h.defaults    4 Jan 2005 10:00:42 -0000    1.4
+++ src/config.h.defaults    18 Feb 2005 21:15:40 -0000
@@ -45,6 +45,8 @@
 
 #define EXIM_PERL
 
+#define EXPAND_DLFUNC
+
 /* Both uid and gid are triggered by this */
 #define EXIM_UID
 
--- src/exim.h    4 Jan 2005 10:00:42 -0000    1.7
+++ src/exim.h    18 Feb 2005 21:15:40 -0000
@@ -410,7 +410,7 @@
 #include <iconv.h>
 #endif
 
-#ifdef USE_READLINE
+#if defined(USE_READLINE) || defined(EXPAND_DLFUNC)
 #include <dlfcn.h>
 #endif
 
--- src/expand.c    17 Feb 2005 11:58:26 -0000    1.13
+++ src/expand.c    18 Feb 2005 21:15:40 -0000
@@ -48,6 +48,9 @@
 alphabetical order. */
 
 static uschar *item_table[] = {
+  #ifdef EXPAND_DLFUNC
+    US"dlfunc",
+  #endif
   US"extract",
   US"hash",
   US"hmac",
@@ -66,6 +69,9 @@
   US"tr" };
 
 enum {
+  #ifdef EXPAND_DLFUNC
+    EITEM_DLFUNC,
+  #endif
   EITEM_EXTRACT,
   EITEM_HASH,
   EITEM_HMAC,
@@ -3786,6 +3793,100 @@
 
       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). */
+
+    #ifdef EXPAND_DLFUNC
+    #define EXPAND_DLFUNC_MAX_ARGS 8
+
+    case EITEM_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 function 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(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, 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 = "";
+        yield = string_cat(yield, &size, &ptr, result, Ustrlen(result));
+        continue;
+        }
+      else
+        {
+        expand_string_message = result == NULL ? US"(no message)" : result;
+        if(status == FAIL) expand_string_forcedfail = TRUE;
+          else if(status != DEFER)
+            log_write(0, LOG_MAIN|LOG_PANIC, "dlfunc{%s}{%s} failed: %s",
+              argv[0], argv[1], expand_string_message);
+        goto EXPAND_FAILED;
+        }
+      }
+    #endif /* EXPAND_DLFUNC */
     }
 
   /* Control reaches here if the name is not recognized as one of the more
--- src/globals.c    17 Feb 2005 11:58:26 -0000    1.17
+++ src/globals.c    18 Feb 2005 21:15:40 -0000
@@ -62,6 +62,10 @@
 BOOL    opt_perl_started       = FALSE;
 #endif
 
+#ifdef EXPAND_DLFUNC
+tree_node *dlobj_anchor        = NULL;
+#endif
+
 #ifdef LOOKUP_IBASE
 uschar *ibase_servers          = NULL;
 #endif
--- src/globals.h    25 Jan 2005 14:16:33 -0000    1.11
+++ src/globals.h    18 Feb 2005 21:15:40 -0000
@@ -26,6 +26,10 @@
 extern BOOL    opt_perl_started;       /* Set once interpreter started */
 #endif
 
+#ifdef EXPAND_DLFUNC
+extern tree_node *dlobj_anchor;        /* Tree of dynamically-loaded objects */
+#endif
+
 #ifdef LOOKUP_IBASE
 extern uschar *ibase_servers;
 #endif
--- src/local_scan.h    4 Jan 2005 10:00:42 -0000    1.3
+++ src/local_scan.h    18 Feb 2005 21:15:40 -0000
@@ -9,7 +9,9 @@
 
 /* This file is the header that is the only Exim header to be included in the
 source for the local_scan.c() function. It contains definitions that are made
-available for use in that function, and which are documented. */
+available for use in that function, and which are documented.
+
+This API is also used for functions called by the ${dlfunc expansion item. */
 
 
 /* Some basic types that make some things easier, and the store functions. */
@@ -32,6 +34,15 @@
   LOCAL_SCAN_TEMPREJECT,          /* Temporary rejection */
   LOCAL_SCAN_TEMPREJECT_NOLOGHDR  /* Temporary rejection, no log header */
 };
+
+
+/* Functions called by ${dlfunc{file}{func}{arg}...} return one of the four
+status codes defined immediately below. The function's first argument is either
+the result of expansion, or the error message in case of failure. The second and
+third arguments are standard argument count and vector, comprising the {arg}
+values specified in the expansion item. */
+
+typedef int exim_dlfunc_t(uschar **yield, int argc, uschar *argv[]);
 
 
 /* Return codes from the support functions lss_match_xxx(). */
--- src/macros.h    17 Feb 2005 11:58:26 -0000    1.9
+++ src/macros.h    18 Feb 2005 21:15:40 -0000
@@ -489,15 +489,16 @@
 #define RDO_READFILE     0x00001000  /* Forbid "readfile" in exp in filter */
 #define RDO_READSOCK     0x00002000  /* Forbid "readsocket" in exp in filter */
 #define RDO_RUN          0x00004000  /* Forbid "run" in expansion in filter */
-#define RDO_REALLOG      0x00008000  /* Really do log (not testing/verifying) */
-#define RDO_REWRITE      0x00010000  /* Rewrite generated addresses */
-#define RDO_EXIM_FILTER  0x00020000  /* Forbid Exim filters */
-#define RDO_SIEVE_FILTER 0x00040000  /* Forbid Sieve filters */
+#define RDO_DLFUNC       0x00008000  /* Forbid "dlfunc" in expansion in filter */
+#define RDO_REALLOG      0x00010000  /* Really do log (not testing/verifying) */
+#define RDO_REWRITE      0x00020000  /* Rewrite generated addresses */
+#define RDO_EXIM_FILTER  0x00040000  /* Forbid Exim filters */
+#define RDO_SIEVE_FILTER 0x00080000  /* Forbid Sieve filters */
 
 /* This is the set that apply to expansions in filters */
 
 #define RDO_FILTER_EXPANSIONS \
-  (RDO_EXISTS|RDO_LOOKUP|RDO_PERL|RDO_READFILE|RDO_READSOCK|RDO_RUN)
+  (RDO_EXISTS|RDO_LOOKUP|RDO_PERL|RDO_READFILE|RDO_READSOCK|RDO_RUN|RDO_DLFUNC)
 
 /* As well as the RDO bits themselves, we need the bit numbers in order to
 access (most of) the individual bits as separate options. This could be
@@ -505,7 +506,7 @@
 
 enum { RDON_BLACKHOLE, RDON_DEFER, RDON_EACCES, RDON_ENOTDIR, RDON_EXISTS,
   RDON_FAIL, RDON_FILTER, RDON_FREEZE, RDON_INCLUDE, RDON_LOG, RDON_LOOKUP,
-  RDON_PERL, RDON_READFILE, RDON_READSOCK, RDON_RUN, RDON_REALLOG,
+  RDON_PERL, RDON_READFILE, RDON_READSOCK, RDON_RUN, RDON_DLFUNC, RDON_REALLOG,
   RDON_REWRITE, RDON_EXIM_FILTER, RDON_SIEVE_FILTER };
 
 /* Results of filter or forward file processing. Some are only from a filter;
--- src/routers/redirect.c    17 Feb 2005 11:58:27 -0000    1.5
+++ src/routers/redirect.c    18 Feb 2005 21:15:40 -0000
@@ -45,6 +45,10 @@
       (void *)offsetof(redirect_router_options_block, bit_options) },
   { "forbid_file",        opt_bool,
       (void *)offsetof(redirect_router_options_block, forbid_file) },
+  #ifdef EXPAND_DLFUNC
+  { "forbid_filter_dlfunc", opt_bit | (RDON_DLFUNC << 16),
+      (void *)offsetof(redirect_router_options_block, bit_options) },
+  #endif
   { "forbid_filter_existstest",  opt_bit | (RDON_EXISTS << 16),
       (void *)offsetof(redirect_router_options_block, bit_options) },
   { "forbid_filter_logwrite",opt_bit | (RDON_LOG << 16),