1 Description: Allow one to use and switch between different local_scan functions
2 without recompiling exim.
3 http://marc.merlins.org/linux/exim/files/sa-exim-current/ Original patch from
4 David Woodhouse, modified first by Derrick 'dman' Hudson and then by Marc
5 MERLIN for SA-Exim and minor/major API version tracking
6 Author: David Woodhouse, Derrick 'dman' Hudson, Marc MERLIN
7 Origin: other, http://marc.merlins.org/linux/exim/files/sa-exim-current/
8 Forwarded: https://bugs.exim.org/show_bug.cgi?id=2671
9 Last-Update: 2023-09-09
13 @@ -902,10 +902,25 @@ HEADERS_CHARSET="ISO-8859-1"
14 # as the traditional crypt() function.
15 # *** WARNING *** WARNING *** WARNING *** WARNING *** WARNING ***
18 #------------------------------------------------------------------------------
19 +# On systems which support dynamic loading of shared libraries, Exim can
20 +# load a local_scan function specified in its config file instead of having
21 +# to be recompiled with the desired local_scan function. For a full
22 +# description of the API to this function, see the Exim specification.
24 +DLOPEN_LOCAL_SCAN=yes
26 +# If you set DLOPEN_LOCAL_SCAN, then you need to include -rdynamic in the
27 +# linker flags. Without it, the loaded .so won't be able to access any
28 +# functions from exim.
31 +CFLAGS += -fvisibility=hidden
33 +#------------------------------------------------------------------------------
34 # The default distribution of Exim contains only the plain text form of the
35 # documentation. Other forms are available separately. If you want to install
36 # the documentation in "info" format, first fetch the Texinfo documentation
37 # sources from the ftp directory and unpack them, which should create files
38 # with the extension "texinfo" in the doc directory. You may find that the
39 --- a/src/config.h.defaults
40 +++ b/src/config.h.defaults
41 @@ -31,10 +31,12 @@ Do not put spaces between # and the 'def
47 +#define DLOPEN_LOCAL_SCAN
51 #define CONFIGURE_FILE
52 #define CONFIGURE_FILE_USE_EUID
53 #define CONFIGURE_FILE_USE_NODE
56 @@ -116,10 +116,14 @@ tls_support tls_out = {
57 uschar *dsn_envid = NULL;
59 const pcre2_code *regex_DSN = NULL;
60 uschar *dsn_advertise_hosts = NULL;
62 +#ifdef DLOPEN_LOCAL_SCAN
63 +uschar *local_scan_path = NULL;
67 BOOL gnutls_compat_mode = FALSE;
68 BOOL gnutls_allow_auto_pkcs11 = FALSE;
69 uschar *hosts_require_alpn = NULL;
70 uschar *openssl_options = NULL;
73 @@ -155,10 +155,14 @@ extern uschar *tls_advertise_hosts; /
74 extern uschar *dsn_envid; /* DSN envid string */
75 extern int dsn_ret; /* DSN ret type*/
76 extern const pcre2_code *regex_DSN; /* For recognizing DSN settings */
77 extern uschar *dsn_advertise_hosts; /* host for which TLS is advertised */
79 +#ifdef DLOPEN_LOCAL_SCAN
80 +extern uschar *local_scan_path; /* Path to local_scan() library */
83 /* Input-reading functions for messages, so we can use special ones for
86 extern int (*lwr_receive_getc)(unsigned);
87 extern uschar * (*lwr_receive_getbuf)(unsigned *);
88 --- a/src/local_scan.c
89 +++ b/src/local_scan.c
91 /* Copyright (c) The Exim Maintainers 2021 */
92 /* See the file NOTICE for conditions of use and distribution. */
93 /* SPDX-License-Identifier: GPL-2.0-or-later */
96 -/******************************************************************************
97 -This file contains a template local_scan() function that just returns ACCEPT.
98 -If you want to implement your own version, you should copy this file to, say
99 -Local/local_scan.c, and edit the copy. To use your version instead of the
100 -default, you must set
103 -LOCAL_SCAN_SOURCE=Local/local_scan.c
105 -in your Local/Makefile. This makes it easy to copy your version for use with
106 -subsequent Exim releases.
108 -For a full description of the API to this function, see the Exim specification.
109 -******************************************************************************/
112 /* This is the only Exim header that you should include. The effect of
113 including any other Exim header is not defined, and may change from release to
114 release. Use only the documented interface! */
116 #include "local_scan.h"
119 -/* This is a "do-nothing" version of a local_scan() function. The arguments
122 - fd The file descriptor of the open -D file, which contains the
123 - body of the message. The file is open for reading and
124 - writing, but modifying it is dangerous and not recommended.
126 - return_text A pointer to an unsigned char* variable which you can set in
127 - order to return a text string. It is initialized to NULL.
129 -The return values of this function are:
132 - The message is to be accepted. The return_text argument is
133 - saved in $local_scan_data.
136 - The message is to be rejected. The returned text is used
137 - in the rejection message.
139 - LOCAL_SCAN_TEMPREJECT
140 - This specifies a temporary rejection. The returned text
141 - is used in the rejection message.
143 +#ifdef DLOPEN_LOCAL_SCAN
146 +static int (*local_scan_fn)(int fd, uschar **return_text) = NULL;
147 +static int load_local_scan_library(void);
151 local_scan(int fd, uschar **return_text)
153 -return LOCAL_SCAN_ACCEPT;
155 +#ifdef DLOPEN_LOCAL_SCAN
156 +/* local_scan_path is defined AND not the empty string */
157 +if (local_scan_path && *local_scan_path)
159 + if (!local_scan_fn)
161 + if (!load_local_scan_library())
163 + char *base_msg , *error_msg , *final_msg ;
164 + int final_length = -1 ;
166 + base_msg=US"Local configuration error - local_scan() library failure\n";
167 + error_msg = dlerror() ;
169 + final_length = strlen(base_msg) + strlen(error_msg) + 1 ;
170 + final_msg = (char*)malloc( final_length*sizeof(char) ) ;
171 + *final_msg = '\0' ;
173 + strcat( final_msg , base_msg ) ;
174 + strcat( final_msg , error_msg ) ;
176 + *return_text = final_msg ;
177 + return LOCAL_SCAN_TEMPREJECT;
180 + return local_scan_fn(fd, return_text);
184 + return LOCAL_SCAN_ACCEPT;
187 +#ifdef DLOPEN_LOCAL_SCAN
189 +static int load_local_scan_library(void)
191 +/* No point in keeping local_scan_lib since we'll never dlclose() anyway */
192 +void *local_scan_lib = NULL;
193 +int (*local_scan_version_fn)(void);
197 +local_scan_lib = dlopen(local_scan_path, RTLD_NOW);
198 +if (!local_scan_lib)
200 + log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library open failed - "
201 + "message temporarily rejected");
205 +local_scan_version_fn = dlsym(local_scan_lib, "local_scan_version_major");
206 +if (!local_scan_version_fn)
208 + dlclose(local_scan_lib);
209 + log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain "
210 + "local_scan_version_major() function - message temporarily rejected");
214 +/* The major number is increased when the ABI is changed in a non
215 + backward compatible way. */
216 +vers_maj = local_scan_version_fn();
218 +local_scan_version_fn = dlsym(local_scan_lib, "local_scan_version_minor");
219 +if (!local_scan_version_fn)
221 + dlclose(local_scan_lib);
222 + log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain "
223 + "local_scan_version_minor() function - message temporarily rejected");
227 +/* The minor number is increased each time a new feature is added (in a
228 + way that doesn't break backward compatibility) -- Marc */
229 +vers_min = local_scan_version_fn();
232 +if (vers_maj != LOCAL_SCAN_ABI_VERSION_MAJOR)
234 + dlclose(local_scan_lib);
235 + local_scan_lib = NULL;
236 + log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() has an incompatible major"
237 + "version number, you need to recompile your module for this version"
238 + "of exim (The module was compiled for version %d.%d and this exim provides"
239 + "ABI version %d.%d)", vers_maj, vers_min, LOCAL_SCAN_ABI_VERSION_MAJOR,
240 + LOCAL_SCAN_ABI_VERSION_MINOR);
243 +else if (vers_min > LOCAL_SCAN_ABI_VERSION_MINOR)
245 + dlclose(local_scan_lib);
246 + local_scan_lib = NULL;
247 + log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() has an incompatible minor"
248 + "version number, you need to recompile your module for this version"
249 + "of exim (The module was compiled for version %d.%d and this exim provides"
250 + "ABI version %d.%d)", vers_maj, vers_min, LOCAL_SCAN_ABI_VERSION_MAJOR,
251 + LOCAL_SCAN_ABI_VERSION_MINOR);
255 +local_scan_fn = dlsym(local_scan_lib, "local_scan");
258 + dlclose(local_scan_lib);
259 + log_write(0, LOG_MAIN|LOG_REJECT, "local_scan() library doesn't contain "
260 + "local_scan() function - message temporarily rejected");
266 +#endif /* DLOPEN_LOCAL_SCAN */
268 /* End of local_scan.c */
269 --- a/src/local_scan.h
270 +++ b/src/local_scan.h
271 @@ -26,10 +26,11 @@ store.c
272 /* Some basic types that make some things easier, the Exim configuration
273 settings, and the store functions. */
276 #include <sys/types.h>
277 +#pragma GCC visibility push(default)
283 @@ -175,10 +176,13 @@ extern const uschar *headers_charset; /
284 extern header_line *header_last; /* Final header */
285 extern header_line *header_list; /* First header */
286 extern BOOL host_checking; /* Set when checking a host */
287 extern uschar *interface_address; /* Interface for incoming call */
288 extern int interface_port; /* Port number for incoming call */
289 +#ifdef DLOPEN_LOCAL_SCAN
290 +extern uschar *local_scan_path;
292 extern uschar *message_id; /* Internal id of message being handled */
293 extern uschar *received_protocol; /* Name of incoming protocol */
294 extern int recipients_count; /* Number of recipients */
295 extern recipient_item *recipients_list;/* List of recipient addresses */
296 extern const unsigned char *sender_address; /* Sender address */
297 @@ -245,6 +249,8 @@ extern uschar * string_copy_taint_functi
298 extern pid_t child_open_exim_function(int *, const uschar *);
299 extern pid_t child_open_exim2_function(int *, uschar *, uschar *, const uschar *);
300 extern pid_t child_open_function(uschar **, uschar **, int, int *, int *, BOOL, const uschar *);
303 +#pragma GCC visibility pop
305 /* End of local_scan.h */
308 @@ -214,10 +214,13 @@ static optionlist optionlist_config[] =
310 { "local_from_check", opt_bool, {&local_from_check} },
311 { "local_from_prefix", opt_stringptr, {&local_from_prefix} },
312 { "local_from_suffix", opt_stringptr, {&local_from_suffix} },
313 { "local_interfaces", opt_stringptr, {&local_interfaces} },
314 +#ifdef DLOPEN_LOCAL_SCAN
315 + { "local_scan_path", opt_stringptr, &local_scan_path },
317 #ifdef HAVE_LOCAL_SCAN
318 { "local_scan_timeout", opt_time, {&local_scan_timeout} },
320 { "local_sender_retain", opt_bool, {&local_sender_retain} },
321 { "localhost_number", opt_stringptr, {&host_number_string} },
324 @@ -435,10 +435,11 @@ return ss;
328 #if (defined(HAVE_LOCAL_SCAN) || defined(EXPAND_DLFUNC)) \
329 && !defined(MACRO_PREDEF) && !defined(COMPILE_UTILITY)
330 +#pragma GCC visibility push(default)
331 /*************************************************
332 * Copy and save string *
333 *************************************************/
336 @@ -480,10 +481,11 @@ Returns: copy of string in new store
338 string_copyn_function(const uschar * s, int n)
340 return string_copyn(s, n);
342 +#pragma GCC visibility pop
346 /*************************************************
347 * Copy and save string in malloc'd store *