--aM3YZ0Iwxop3KEKx Content-Type: multipart/mixed; boundary="FL5UXtIhxfXey3p5" Content-Disposition: inline --FL5UXtIhxfXey3p5 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Well, I had an epiphany over the US Thanksgiving holiday weekend (or maybe it was a brain aneurism) so I've spent some late nights and some time stolen from work and come up with a first pass at embedding Python in Exim. I imagine that embedding Python in Exim will be interesting to those folks writing virus scanners or for VERY tight integration of Mailman with Exim. You can also do all of those things that you can do with the Perl string expansions. Note: this code is VERY lightly tested. It compiles on my home hacked-up RedHat Linux system and works on a couple of simple tests that I've done, but no real e-mails have passed through this code yet. I'm tossing this out for public comment on the design and implementation... It's been a while since I've done a lot of C coding. Unfortunately, you must embed Python 2.0. This is because previous versions of Python used a hacked copy of PCRE as it's regular expression engine that conflicts with the copy of PCRE in Exim. Starting in Python 2.0 the default regular expression is a completely new one called SRE that supports Unicode. The PCRE-based regular expression engine is still included in Python 2.0 but I get around that by creating a private copy of the Python library and delete the offending object modules. You could do that in versions of Python prior to 2.0 but a lot of the standard library depends on regular expressions I didn't think that there was much point in trying to make the embedding work with 1.5.2 or 1.6. Well, on to the patch itself. The patch is made against Exim v3.20. After you've patched the source code, you need to set four variables in the Local/Makefile: EXIM_PYTHON=python.o PYTHON_INC=-I/usr/local/include/python2.0 PYTHON_LIB=/usr/local/lib/python2.0/config/libpython2.0.a PYTHON_EXTRA_LIBS=-lpthread -ldl -lutil Then build Exim as usual. There are three runtime directives that control the embedding, all of which are optional: python_at_start boolean - force startup of Python interpreter python_module_paths colon separated list of paths to append to sys.path python_initial_import colon separated list of modules to import at interpreter initialization time There are also two new command line switches -ys and -yd that will force an immediate startup of Python or delay startup, overriding python_at_start. Then you use it: ${python{<module>.<function>}{<arg1>}...} You must specify the module name and the function name. The named module will be imported automatically. And currently you must specify a function that returns a string (I'll look into adding accessing attributes, methods of classes, and converting non-strings into strings). There can be up to eight arguments. Each argument will be expanded by Exim before it's passed to the Python interpreter. Your python code has access to a module called "exim", which currently defines two things: exim.expand_string(s) which calls back to Exim to expand a string and exim.error which is an exception object used by expand_string One simple example: ${python{string.upper}{$local_part}} Error reporting isn't the best right now, I'm going to look into formatting exceptions and tracebacks for more meaningful error messages. Well, I think that's the gist of it. I'll work on patches to the documentation when this thing becomes more stable. As I said before I've only lightly tested this... beware! Comments, criticisms, suggestions, flames welcome... Jeff --FL5UXtIhxfXey3p5 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="exim-python.patch" Content-Transfer-Encoding: quoted-printable Index: exim/OS/Makefile-Base diff -u exim/OS/Makefile-Base:1.1.1.1 exim/OS/Makefile-Base:1.1.1.1.2.3 --- exim/OS/Makefile-Base:1.1.1.1 Mon Nov 27 08:18:15 2000 +++ exim/OS/Makefile-Base Tue Nov 28 16:49:25 2000 @@ -192,6 +192,16 @@ @chmod a+x ../util/convert4r3 @echo ">>> convert4r3 script built in util directory"; echo "" =20 +libpython.a: $(PYTHON_LIB) + if [ -n $(PYTHON_LIB) ] ;\ + then \ + cp $(PYTHON_LIB) libpython.a ;\ + ar d libpython.a pcremodule.o pypcre.o ;\ + ranlib libpython.a ;\ + else \ + ar cq libpython.a ;\ + ranlib libpython.a ;\ + fi =20 # Targets for final binaries; the main one has a build number which is # updated each time. We don't bother with that for the auxiliaries. @@ -201,11 +211,12 @@ header.o host.o log.o match.o moan.o os.o parse.o queue.o \ readconf.o retry.o rewrite.o \ route.o search.o smtp_in.o smtp_out.o spool_in.o spool_out.o \ - store.o string.o tls.o tod.o transport.o tree.o verify.o $(EXIM_PE= RL) + store.o string.o tls.o tod.o transport.o tree.o verify.o $(EXIM_PE= RL) \ + $(EXIM_PYTHON) =20 exim: libident/libident.a pcre/libpcre.a lookups/lookups.a auths/auths.a= \ directors/directors.a routers/routers.a transports/transports.a \ - $(OBJ_EXIM) version.c + $(OBJ_EXIM) version.c libpython.a awk '{ print ($$1+1) }' cnumber.h > cnumber.temp /bin/rm -f cnumber.h; mv cnumber.temp cnumber.h $(CC) -c $(CFLAGS) $(INCLUDE) $(IPV6_INCLUDE) $(TLS_INCLUDE) version.c @@ -215,7 +226,8 @@ routers/routers.a transports/transports.a lookups/lookups.a \ auths/auths.a \ $(LIBS) $(LIBS_EXIM) $(IPV6_LIBS) $(EXTRALIBS) $(EXTRALIBS_EXIM) \ - $(DBMLIB) $(LIBRESOLV) $(LOOKUP_LIBS) $(PERL_LIBS) $(TLS_LIBS) + $(DBMLIB) $(LIBRESOLV) $(LOOKUP_LIBS) $(PERL_LIBS) libpython.a $(PYTHON= _EXTRA_LIBS) \ + $(TLS_LIBS) $(EXIM_CHMOD) @echo " " @echo ">>> exim binary built" @@ -316,6 +328,11 @@ =20 perl.o: $(HDRS) perl.c $(PERL_CC) $(PERL_CCOPTS) $(CFLAGS) $(INCLUDE) -c perl.c + +# Build instructions for python.o for when EXIM_PYTHON is set + +python.o: $(HDRS) python.c + $(CC) -c $(CFLAGS) $(INCLUDE) $(PYTHON_INC) -I. python.c =20 # Dependencies for the "ordinary" exim modules =20 Index: exim/scripts/MakeLinks diff -u exim/scripts/MakeLinks:1.1.1.1 exim/scripts/MakeLinks:1.1.1.1.2.1 --- exim/scripts/MakeLinks:1.1.1.1 Mon Nov 27 08:18:16 2000 +++ exim/scripts/MakeLinks Mon Nov 27 08:27:46 2000 @@ -189,6 +189,7 @@ ln -s ../src/moan.c moan.c ln -s ../src/parse.c parse.c ln -s ../src/perl.c perl.c +ln -s ../src/python.c python.c ln -s ../src/queue.c queue.c ln -s ../src/readconf.c readconf.c ln -s ../src/retry.c retry.c Index: exim/src/EDITME diff -u exim/src/EDITME:1.1.1.2 exim/src/EDITME:1.1.1.2.2.4 --- exim/src/EDITME:1.1.1.2 Mon Nov 27 08:18:54 2000 +++ exim/src/EDITME Tue Nov 28 16:20:19 2000 @@ -260,6 +260,10 @@ # PERL_CCOPTS=3D # PERL_LIBS=3D =20 +# EXIM_PYTHON=3Dpython.o +# PYTHON_INC=3D-I/usr/local/include/python2.0 +# PYTHON_LIB=3D/usr/local/lib/python2.0/config/libpython2.0.a +# PYTHON_EXTRA_LIBS=3D-lpthread -ldl -lutil =20 # This parameter sets the maximum length of the header portion of a message # that Exim is prepared to process. The default setting is one megabyte. T= here Index: exim/src/config.h.defaults diff -u exim/src/config.h.defaults:1.1.1.1 exim/src/config.h.defaults:1.1.1= .1.2.1 --- exim/src/config.h.defaults:1.1.1.1 Mon Nov 27 08:18:16 2000 +++ exim/src/config.h.defaults Mon Nov 27 08:43:04 2000 @@ -36,6 +36,8 @@ =20 #define EXIM_PERL =20 +#define EXIM_PYTHON + #define HAVE_SA_LEN #define HEADER_MAXSIZE (1024*1024) =20 Index: exim/src/exim.c diff -u exim/src/exim.c:1.1.1.2 exim/src/exim.c:1.1.1.2.2.4 --- exim/src/exim.c:1.1.1.2 Mon Nov 27 08:18:54 2000 +++ exim/src/exim.c Tue Nov 28 00:46:52 2000 @@ -351,6 +351,9 @@ #ifdef EXIM_PERL int perl_start_option =3D 0; #endif +#ifdef EXIM_PYTHON +int python_start_option =3D 0; +#endif int recipients_arg =3D argc; int sender_address_domain =3D 0; int test_retry_arg =3D -1; @@ -583,6 +586,17 @@ simply recorded for checking and handling afterwards. Do a high-level swit= ch on the second character (the one after '-'), to save some effort. */ =20 +#ifdef EXIM_PYTHON +opt_python_original_argc =3D argc; +opt_python_original_argv =3D store_get(sizeof(char *) * (argc + 1)); +opt_python_original_argv[argc] =3D NULL; + +for (i =3D 0; i < argc; i++) + { + opt_python_original_argv[i] =3D string_copy(argv[i]); + } +#endif + for (i =3D 1; i < argc; i++) { BOOL badarg =3D FALSE; @@ -1521,6 +1535,17 @@ break; #endif =20 + /* -ys: force Python startup; -yd force delayed Python startup */ + + #ifdef EXIM_PYTHON + case 'y': + if (*argrest =3D=3D 's' && argrest[1] =3D=3D 0) python_start_option = =3D 1; + else + if (*argrest =3D=3D 'd' && argrest[1] =3D=3D 0) python_start_option = =3D -1; + else badarg =3D TRUE; + break; + #endif + =20 case 'q': =20 @@ -1967,6 +1992,27 @@ opt_perl_started =3D TRUE; } #endif /* EXIM_PERL */ + +/* Start up Python interpreter if Python support is configured and +there is a and the configuration or the command line specifies +initializing starting. Note that the global variables are actually +called opt_python_xxx. */ + +#ifdef EXIM_PYTHON +if (python_start_option !=3D 0) + opt_python_at_start =3D (python_start_option > 0); +if (opt_python_at_start) + { + char *errstr; + DEBUG(9) debug_printf("Starting Python interpreter\n"); + errstr =3D init_python(); + if (errstr !=3D NULL) + { + fprintf(stderr, "exim: error during Python startup: %s\n", errstr); + return EXIT_FAILURE; + } + } +#endif /* EXIM_PYTHON */ =20 /* Log the arguments of the call if the configuration file said so. This is a debugging feature for finding out what arguments certain MUAs actually u= se. Index: exim/src/expand.c diff -u exim/src/expand.c:1.1.1.2 exim/src/expand.c:1.1.1.2.2.3 --- exim/src/expand.c:1.1.1.2 Mon Nov 27 08:18:54 2000 +++ exim/src/expand.c Tue Nov 28 00:23:10 2000 @@ -1318,7 +1318,7 @@ =20 Operators: domain extracts domain from an address - escape escapes non-printing characters + escape escapes non-printing characters=20 expand expands the string one more time hash_<n>_<m> hash the string, making one that is of length n, using m (default 26) characters from hashcodes @@ -1865,6 +1865,100 @@ continue; } #endif /* EXIM_PERL */ + + /* If Perl support is configured, handle calling embedded perl subroutin= es, + unless locked out at this time. Syntax is ${perl{sub}} or ${perl{sub}{ar= g}} + or ${perl{sub}{arg1}{arg2}} or up to a maximum of EXIM_PERL_MAX_ARGS + arguments (defined below). */ + + #ifdef EXIM_PYTHON + #define EXIM_PYTHON_MAX_ARGS 8 + + if (strcmp(name, "python") =3D=3D 0) + { + int i =3D 0; + char *sub_name; + char *sub_arg[EXIM_PYTHON_MAX_ARGS + 1]; + char *new_yield; + + if (expand_forbid_python) + { + expand_string_message =3D "Python calls are not permitted"; + goto EXPAND_FAILED; + } + + while (isspace((uschar)*s)) s++; + if (*s !=3D '{') goto EXPAND_FAILED_CURLY; + sub_name =3D expand_string_internal(s+1, TRUE, &s, FALSE); + if (sub_name =3D=3D NULL) goto EXPAND_FAILED; + while (isspace((uschar)*s)) s++; + if (*s++ !=3D '}') goto EXPAND_FAILED_CURLY; + + while (isspace((uschar)*s)) s++; + + while (*s =3D=3D '{') + { + if (i =3D=3D EXIM_PYTHON_MAX_ARGS) + { + expand_string_message =3D + string_sprintf("Too many arguments passed to Python subroutine \= "%s\" " + "(max is %d)", sub_name, EXIM_PYTHON_MAX_ARGS); + goto EXPAND_FAILED; + } + sub_arg[i] =3D expand_string_internal(s+1, TRUE, &s, FALSE); + if (sub_arg[i++] =3D=3D NULL) goto EXPAND_FAILED; + while (isspace((uschar)*s)) s++; + if (*s++ !=3D '}') goto EXPAND_FAILED_CURLY; + while (isspace((uschar)*s)) s++; + } + + if (*s++ !=3D '}') goto EXPAND_FAILED_CURLY; + sub_arg[i] =3D 0; + + /* Start the interpreter if necessary */ + + if (!opt_python_started) + { + char *initerror; + DEBUG(9) debug_printf("Starting Python interpreter\n"); + initerror =3D init_python(); + if (initerror !=3D NULL) + { + expand_string_message =3D + string_sprintf("error during Python startup: %s\n", initerror); + goto EXPAND_FAILED; + } + } + + /* Call the function */ + + new_yield =3D call_python_cat(yield, &size, &ptr, &expand_string_messa= ge, + sub_name, sub_arg); + + /* NULL yield indicates failure; if the message pointer has been set to + NULL, the yield was undef, indicating a forced failure. Otherwise the + message will indicate some kind of Perl error. */ + + if (new_yield =3D=3D NULL) + { + if (expand_string_message =3D=3D NULL) + { + expand_string_message =3D + string_sprintf("Python subroutine \"%s\" returned undef to force= " + "failure", sub_name); + expand_string_forcedfail =3D TRUE; + } + goto EXPAND_FAILED; + } + + /* Yield succeeded. Ensure forcedfail is unset, just in case it got + set during a callback from Python. */ + + expand_string_forcedfail =3D FALSE; + yield =3D new_yield; + continue; + } + #endif /* EXIM_PYTHON */ =20 /* Handle character translation for "tr" */ =20 Index: exim/src/functions.h diff -u exim/src/functions.h:1.1.1.2 exim/src/functions.h:1.1.1.2.2.1 --- exim/src/functions.h:1.1.1.2 Mon Nov 27 08:18:54 2000 +++ exim/src/functions.h Mon Nov 27 10:13:25 2000 @@ -16,6 +16,12 @@ extern char *init_perl(char *); #endif =20 +#ifdef EXIM_PYTHON +extern char *call_python_cat(char *, int *, int *, char **, char *, char *= *); +extern void cleanup_python(void); +extern char *init_python(void); +#endif + #ifdef SUPPORT_TLS extern BOOL tls_client_start(int, host_item *, address_item *, char *, char *, char *, char *, char *, int); Index: exim/src/globals.c diff -u exim/src/globals.c:1.1.1.1 exim/src/globals.c:1.1.1.1.2.2 --- exim/src/globals.c:1.1.1.1 Mon Nov 27 08:18:16 2000 +++ exim/src/globals.c Mon Nov 27 11:10:15 2000 @@ -35,6 +35,15 @@ BOOL opt_perl_started =3D FALSE; #endif =20 +#ifdef EXIM_PYTHON +BOOL opt_python_at_start =3D FALSE; +char *opt_python_module_paths =3D NULL; +char *opt_python_initial_import =3D NULL; +BOOL opt_python_started =3D FALSE; +int opt_python_original_argc =3D 0; +char **opt_python_original_argv =3D NULL; +#endif + #ifdef HAVE_AUTH BOOL auth_always_advertise =3D TRUE; char *auth_hosts =3D NULL; @@ -310,6 +319,7 @@ BOOL expand_forbid_exists =3D FALSE; BOOL expand_forbid_lookup =3D FALSE; BOOL expand_forbid_perl =3D FALSE; +BOOL expand_forbid_python =3D FALSE; int expand_nlength[EXPAND_MAXN+1]; int expand_nmax =3D -1; char *expand_nstring[EXPAND_MAXN+1]; Index: exim/src/globals.h diff -u exim/src/globals.h:1.1.1.1 exim/src/globals.h:1.1.1.1.2.2 --- exim/src/globals.h:1.1.1.1 Mon Nov 27 08:18:16 2000 +++ exim/src/globals.h Mon Nov 27 11:10:15 2000 @@ -21,6 +21,15 @@ extern BOOL opt_perl_started; /* Set once interpreter started */ #endif =20 +#ifdef EXIM_PYTHON +extern BOOL opt_python_at_start; /* start Python at startup */ +extern char *opt_python_module_paths; /* list of paths to append to sys= .path */ +extern char *opt_python_initial_import; /* list of modules to import at i= nterpreter initialization time */ +extern BOOL opt_python_started; /* set once Python interpreter st= arted */ +extern int opt_python_original_argc; +extern char **opt_python_original_argv; +#endif + #ifdef HAVE_AUTH extern BOOL auth_always_advertise; /* If FALSE, advertise only when nee= ded */ extern char *auth_hosts; /* These must authenticate */ @@ -208,6 +217,7 @@ extern BOOL expand_forbid_exists; /* Lock out exists checking */ extern BOOL expand_forbid_lookup; /* Lock out lookups */ extern BOOL expand_forbid_perl; /* Lock out Perl calls */ +extern BOOL expand_forbid_python; /* Lock out Python calls */ extern int expand_nlength[]; /* Lengths of numbered strings */ extern int expand_nmax; /* Max numerical value */ extern char *expand_nstring[]; /* Numbered strings */ Index: exim/src/python.c diff -u /dev/null exim/src/python.c:1.1.2.11 --- /dev/null Tue Nov 28 16:50:15 2000 +++ exim/src/python.c Tue Nov 28 16:22:03 2000 @@ -0,0 +1,291 @@ +#include "exim.h" + +#include "Python.h" + +static void init_exim_module(void); + +char *init_python(void) +{ + if (opt_python_started) + { + return NULL; + } + + Py_SetProgramName(opt_python_original_argv[0]); + =20 + Py_Initialize(); + =20 + PySys_SetArgv(opt_python_original_argc, opt_python_original_argv); + + init_exim_module(); + + if (opt_python_module_paths) + { + char *listptr =3D opt_python_module_paths; + int separator =3D 0; + char buffer[256]; + PyObject *path; + PyObject *sys_module; + PyObject *sys_dict; + PyObject *sys_path; + PyObject *name; + + name =3D PyString_FromString("sys"); + if (name =3D=3D NULL) + { + PyErr_Clear(); + return "problem creating string \"sys\""; + } + + sys_module =3D PyImport_Import(name); + Py_DECREF(name); + + if (sys_module =3D=3D NULL) + { + PyErr_Clear(); + return "problem trying to import sys"; + } + + sys_dict =3D PyModule_GetDict(sys_module); + if (sys_dict =3D=3D NULL) + { + PyErr_Clear(); + return "problem trying get dictionary from module sys"; + } + + sys_path =3D PyDict_GetItemString(sys_dict, "path"); + if (sys_path =3D=3D NULL) + { + PyErr_Clear(); + return "problem trying to get sys.path"; + } + + while(string_nextinlist(&listptr, &separator, buffer, sizeof(buffer)= )) + { + path =3D PyString_FromString(buffer); + if (path =3D=3D NULL) + { + PyErr_Clear(); + return string_sprintf("problem while allocating Python string for \"%= s\"", buffer); + } + + if(!PyList_Append(sys_path, path)) + { + PyErr_Clear(); + return string_sprintf("problem while insering string \"%s\"into sys.p= ath", buffer); + } + } + } + + if (opt_python_initial_import) + { + char *listptr =3D opt_python_initial_import; + int separator =3D 0; + char buffer[256]; + PyObject *module; + PyObject *name; + + while(string_nextinlist(&listptr, &separator, buffer, sizeof(buffer)= )) + { + name =3D PyString_FromString(buffer); + if (name =3D=3D NULL) + { + PyErr_Clear(); + return string_sprintf("problem creating string \"%s\"", buffer); + } + + module =3D PyImport_Import(name); + Py_DECREF(name); + + if (module =3D=3D NULL) + { + PyErr_Clear(); + return string_sprintf("problem importing module %s", buffer); + } + } + } + + =20 + opt_python_started =3D TRUE; + return NULL; +} + +void cleanup_python(void) +{ + Py_Finalize(); +} + +static PyObject *find_function(char *name, char **errstrp) +{ + PyObject *module_name; + PyObject *function_name; + PyObject *module; + PyObject *dictionary; + PyObject *result; + + char *ptr; + + ptr =3D strrchr(name, '.'); + =20 + if (ptr =3D=3D NULL) + { + *errstrp =3D string_sprintf("function name must include module: <mod= ule>.<name>"); + return NULL; + } + + function_name =3D PyString_FromString(ptr + 1); + + if (function_name =3D=3D NULL) + { + PyErr_Clear(); + *errstrp =3D string_sprintf("error trying to allocate function name"= ); + return NULL; + } + + module_name =3D PyString_FromStringAndSize(name, ptr - name); + + if (function_name =3D=3D NULL) + { + PyErr_Clear(); + Py_DECREF(function_name); + *errstrp =3D string_sprintf("error trying to allocate module name"); + return NULL; + } + + module =3D PyImport_Import(module_name); + + Py_DECREF(module_name); + + if (module =3D=3D NULL) + { + PyErr_Clear(); + Py_DECREF(function_name); + *errstrp =3D string_sprintf("error trying to import module while try= ing to find %s", name); + return NULL; + } + + dictionary =3D PyModule_GetDict(module); + + result =3D PyDict_GetItem(dictionary, function_name); + + Py_DECREF(function_name); + + if (!PyCallable_Check(result)) + { + PyErr_Clear(); + *errstrp =3D string_sprintf("%s is not a callable object", name); + return NULL; + } + + return result; +} + +char *call_python_cat(char *yield, int *sizep, int *ptrp, char **errstrp, + char *name, char **arg) +{ + PyObject *function; + PyObject *arguments; + PyObject *result; + int index; + int count=3D0; + char **p; + char *buffer; + int length; + + function =3D find_function(name, errstrp); + + if (function =3D=3D NULL) + return NULL; + + p =3D arg; + + while(*p) + { + count++; + p++; + } + + arguments =3D PyTuple_New(count); + + index =3D 0; + while(index < count) + { + + PyTuple_SetItem(arguments, index, PyString_FromString(arg[index])); + index++; + } + + result =3D PyObject_CallObject(function, arguments); + + Py_DECREF(arguments); + + if (result =3D=3D NULL) + { + PyErr_Clear(); + *errstrp =3D string_sprintf("error in Python function %s", name); + return NULL; + } + + if(!PyString_Check(result)) + { + Py_DECREF(result); + *errstrp =3D string_sprintf("Python function %s returned non-string"= , name); + return NULL; + } + + if(PyString_AsStringAndSize(result, &buffer, &length)) + { + PyErr_Clear(); + Py_DECREF(result); + *errstrp =3D string_sprintf("could not extract the results of Python= function %s", name); + return NULL; + } + + yield =3D string_cat(yield, sizep, ptrp, buffer, length); + + Py_DECREF(result); + + return yield; +} + +static PyObject *exim_error =3D NULL; + +static PyObject *exim_expand_string(PyObject *self, PyObject *args) +{ + char *i; + char *o; + + if(!PyArg_ParseTuple(args, "s", i)) + return NULL; + =20 + o =3D expand_string(i); + =20 + if (o =3D=3D NULL) + { + PyErr_SetString(exim_error, expand_string_message); + return NULL; + } + =20 + return PyString_FromString(o); +} + +static PyMethodDef exim_methods[] =3D { + {"expand_string", exim_expand_string, 1}, + {NULL, NULL} /* sentinel */ +}; + +static void init_exim_module(void) +{ + PyObject *m; + PyObject *d; + PyObject *o; + + m =3D PyImport_AddModule("exim"); + d =3D PyModule_GetDict(m); + + Py_InitModule("exim", exim_methods); + + exim_error =3D PyErr_NewException("exim.error", NULL, NULL); + PyDict_SetItemString(d, "error", exim_error); +} + Index: exim/src/readconf.c diff -u exim/src/readconf.c:1.1.1.1 exim/src/readconf.c:1.1.1.1.2.2 --- exim/src/readconf.c:1.1.1.1 Mon Nov 27 08:18:16 2000 +++ exim/src/readconf.c Mon Nov 27 10:14:17 2000 @@ -160,6 +160,11 @@ { "perl_at_start", opt_bool, &opt_perl_at_start }, { "perl_startup", opt_stringptr, &opt_perl_startup }, #endif +#ifdef EXIM_PYTHON + { "python_at_start", opt_bool, &opt_python_at_start }, + { "python_module_paths", opt_stringptr, &opt_python_module_paths = }, + { "python_initial_import", opt_stringptr, &opt_python_initial_impor= t }, +#endif #ifdef LOOKUP_PGSQL { "pgsql_servers", opt_stringptr, &pgsql_servers }, #endif --FL5UXtIhxfXey3p5-- --aM3YZ0Iwxop3KEKx Content-Type: application/pgp-signature Content-Disposition: inline -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.0.4 (GNU/Linux) Comment: For info see http://www.gnupg.org iD8DBQE6JELjmCSL+rEqeRsRArY9AJ9iw1RL79TcwrKoRxLCFGSvJ/hPqgCglwRc JfzWMV388u0Hohv9S1xDxcE= =430v -----END PGP SIGNATURE----- --aM3YZ0Iwxop3KEKx--
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4