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),