Release 4.60py1
21st January, 2006
© 2004-2006 David Wilson <dw@botanicus.net>
All rights reserved except those specifically granted under the accompanying
license. Please see the file LICENSE
from the source distribution.
Exim is a message transfer agent (MTA) developed at the University of Cambridge for use on Unix systems connected to the Internet. There is a great deal of flexibility in the way mail can be routed, and there are extensive facilities for checking incoming mail.
Python is an interpreted, interactive, object-oriented programming language. It is often compared to Tcl, Perl, Scheme or Java. Python combines remarkable power with very clear syntax. It has modules, classes, exceptions, very high level dynamic data types, and dynamic typing. There are interfaces to many system calls and libraries.
Exim-Python is the name for an extension to Exim which adds the ability to execute compiled Python functions and methods from within Exim configuration file string expansions. Since Exim uses string expansions heavily throughout it's operation, this feature allows powerful added control to an already very flexible mail server.
Exim alone is an extremely powerful mail server, approaching as feature complete as one can expect from any mail server software. It is also fairly easy to extend Exim using native code, but to experiment with new feature implementation like this takes a lot of time.
This extension allows you to prototype a new feature for Exim quickly and easily. To say it is useful only as a prototyping tool would be incorrect, indeed the performance of the extension and the code running beneath it should suffice for all but the highest volume mail servers.
Other potential uses of the extension include data source abstraction, for example, using the Python DB-API, you can make your Exim installation database-independant by calling a layer of functions that perform lookups using DB-API compatible interfaces rather than directly themselves.
If you have questions regarding the usage or behaviour of this extension, please contact the author at the address at the top of this document.
You must be using a version of Python that supports the boolean protocol. This means that a minimum version of Python 2.2.1 is required for operation of the extension.
The extension is distributed in two versions - a patch and a pre-patched tarball. It is recommended that you use the pre-patched tarball unless you need to further extend Exim with other third party patches.
Note: the pre-patched tarball also includes the latest version of the exiscan-acl patch, available from http://duncanthrax.net/exiscan-acl/.
This has little impact on the operation of Exim if you don't use it's features, however it is a very common requirement in modern Exim installations, and as such, is included for ease of installation. The usefulness of this extension is also greatly increased by the presence of the extra ACLs.
Download and extract the latest Exim tarball from http://www.exim.org/, and the accompanying patch from http://botanicus.net/dw/:
$ cd /usr/srcEnter the Exim source directory, and apply the patch:
$ cd exim-4.32Follow the Exim installation procedure outlined in the specification: http://www.exim.org/exim-html-4.30/doc/html/spec_4.html.
In Local/Makefile
, you will additionally need to specify the
EXIM_PYTHON
, PYTHON_CC
,
PYTHON_CCOPTS
, and PYTHON_LIBS
variables. The
defaults should suffice, although you may need to adjust the included paths
if you have installed Python to a non-standard location.
In order to enable the extension, you must tell it the name of a Python
source file to load and compile at startup. Any modules, packages,
functions, and classes that you wish to use should be defined in this file,
or imported into it using the Python import
statement.
The boolean python_at_start
parameter indicates to Exim
whether or not it should load the extension at startup. It defaults to
false
, to help reduce daemon memory usage slightly. Setting it
to true
will increase the performance of the server when
Python calls are made later on. This is recommended for production servers.
The string python_startup
parameter specifies the name of the
Python source file to load and compile at startup. This parameter must be
set to a valid (existing, and syntactically valid) source file pathname in
order for the extension to be used.
At startup, the extension will attempt to read in the contents of the file
specified by the python_startup
configuration parameter,
compile it, and execute it as a module.
When the extension executes your code, it sets the module-level variable
__name__
to __exim__
. The usage semantics of
__name__
are aligned with how Python uses it.
By testing __name__
, it is possible (although impractical in
many cases) to write a single Python source file that will operate as a
standard Python module, an Exim-Python module, and as a command-line tool.
Here is an example:
When the extension encounters a ${python {<fn>} ...}
expansion, it resolves the name <fn>
. In the module
loaded previously. ...
may be one or more string arguments
that will be passed to the Python function, encapsulated in braces.
Several rules are applied to the result of your Python function, and it is very important you understand them. Note: it is an error if your Python function does not return a value!
None
(implicit default if
no return
with value executed), then the expansion is
forced to fail.sys.stderr
, a log will be made in the paniclog,
and the expansion will be forced to fail.True
or False
(the boolean singletons), these are converted to the strings
true
and false
respectively.Exim is a rather complex piece of software, and the execution path it takes can be quite confusing at times. For this reason, it is desirable to be as careful as possible when initialising and using resources.
Exim forks quite regularly, and much of it's operation happens inside a subprocess. When Exim performs a fork, any objects existing in the Python interpreter at the time of fork will now be duplicated.
This can lead to dangerous and incorrect use of, for example, database connections, which will now have essentially two clients connected to only one client's connection.
The extension as yet does not provide any automatic means for handling objects that should be destroyed or reinitialised at fork time, so it is up to you to do this.
Here, our dangerous resource is created and destroyed every time the function is entered. This avoids the problem in almost all cases.
def get_db():Every feature of the Python environment is not available from within Exim. Certain limitations are placed on your code to avoid destablizing the Exim server process. Here they are:
Or, do so at your own risk. There are many complications associated with the use of threads, and you can almost certainly accomplish your goal without using them.
At this time, it is currently unknown how Exim would deal with restarting
system calls in the case of a signal delivery handled by the extension. It
is recommended that you avoid all use of the signal
module
until usage documentation appears in a future release of this extension.
fcntl.FD_CLOEXEC
flag on them, in order to enhance
security, and keep the files within their required scope.
sys.stderr
object in your module, directed to a file in
/tmp
. A better solution to this is in the works.
This example combines the extension with Exim's built-in access control lists. Here, we prevent connections to the mail server unless they originate a country that is specifically authorised to send e-mail to us. This is an effective, if dangerous, anti-spam measure for English speakers.
It requires the GeoIP client library, a copy of the GeoIP database, and the GeoIP Python bindings. See http://www.maxmind.com/app/python.
obj.subobj.subobj.callable
resolution.python_resolve()
.exim -d -bd
, pressing the interrupt key will not shut Exim
down. This is evidence that Python is still further damaging signals or
some other behaviour, and requires investigation.save_signals()
and load_signals()
in python.c
are working effectively.PyOS_AfterFork()
is called only when
necessary.while ()
predicates in
python.c:python_resolve()
.python.c:call_python_cat()
in the
arg tuple build.python.c:save_signals()
.
#undef HAVE_SETEUID
in
python.c
. Find out if it is Exim or Python being
ignorant.${pythonkw {keywordlist} [{arg1} [{argn}
..]]}
for calling objects that can accept keyword arguments.
keywordlist will be of the exim form key=val key2=val2
and
so on.key=val
strings on return from a function.
(<parent>,
<object_name>,
<constructor>,
<constructor_args>,
<constructor_kwargs>,
<reinit_where>)
6-tuple?python
router, transport, authenticator,
and lookup. This might be useful to some, for example, for SQL-backed
e-mail storage.exim
module, to allow certain internal Exim
functions be called from Python. This would also encapsulate the
run-time reconfiguration wishlist item.