elspy
-----

elspy is a layer of glue code that enables you to write Python code to
scan email messages at SMTP-time with the Exim MTA (mail transport
agent).  elspy also includes a small Python library with common
mail-scanning tools, including an interface to SpamAssassin and a
simple-but-effective virus detector.

Exim is a highly flexible and customizable MTA (mail transport agent)
for Unix Internet email servers.  One of the new features in Exim 4.0
was the local_scan() API, which allows sites to supply custom code for
scanning messages at SMTP time, accepting or rejecting them based on
message content.  Normally, you have to write the local_scan() function
in C.

Python is a high-level, extensible, embeddable, object-oriented
programming language.  Scanning email is a great example of something
that you can do in any decent programming language, but do a lot more
easily in Python than in C.

The core of elspy is a C local_scan() function that embeds a Python
interpreter in Exim, allowing you to write your own local_scan()
function in Python instead of C.


REQUIREMENTS
------------

elspy requires Exim 4.0 or greater, and Python 2.0 or greater.  (I only
test it with Python 2.1 and 2.2; let me know how it goes if you try it
with Python 2.0.)

Make sure that you have the Python C library (libpython2.x.a or
libpython2.x.so) and header files (Python.h et. al.) installed -- if you
installed Python from source yourself, this should not be a problem.  If
you're using a pre-packaged Python installation, you might not have them
by default.  Most Linux distributions have a separate "python-dev" or
"python-devel" package that you'll need to install.

You're probably better having an Exim built from source, since you'll
have to rebuild Exim to get elspy working.


BUILDING/INSTALLING
-------------------

First, you should install elspy's Python library:

  python setup.py install

Without this library, the C component that you'll link into Exim is
useless.

Next, you'll have to rebuild Exim with elspy.c linked into it.  (If you
are using David Woodhouse's patch to allow Exim to dynamically load its
local_scan() function, you're on your own -- I haven't got this working
with elspy yet.  [Although I haven't tried very hard, either.])

  1) copy elspy.c to your Exim source tree, eg.
       cp elspy.c /tmp/exim-4.05/Local

  2) edit Exim's Local/Makefile so your Exim binary includes elspy
     and (possibly) Python.

     On Debian Linux, using the standard Python 2.1 or 2.2 packages
     (which include /usr/lib/libpython2.x.so), this works for me:

       LOCAL_SCAN_SOURCE = Local/elspy.c
       CFLAGS = -I/usr/include/python2.x
       EXTRALIBS = -lpython2.x

     This is a nice way to do it because Exim is dynamically linked
     against Python, so you don't bloat your Exim binary with a complete
     Python interpreter.

     On another Linux system, where Python was built from source and
     lives in /usr/local, I use this:

       LOCAL_SCAN_SOURCE = Local/elspy.c
       INCLUDE = -I/usr/local/include/python2.2
       EXTRALIBS = -L/usr/local/lib/python2.2/config \
                   -rdynamic -lm -ldl -lpthread -lutil -lpython2.2

     This links Exim with the static Python C library, so you'll get a
     fairly large Exim binary (especially if your Python build has a lot
     of extensions built-in).  If this bothers you, you should figure
     out how to make a libpython2.x.so shared library for your system.

  3) rebuild Exim:
       make

At this point, you have a ready-to-install Exim binary with elspy's
local_scan() function linked in.  It should be safe to install this
binary and "kill -HUP" your running Exim daemon, because elspy is
inactive at this point.  If you don't trust me (and why would you?),
then you should test your just-built Exim without installing it -- see
"TESTING" below.

However, it should be safe to start running your elspy-enabled Exim
binary immediately, because whenever Exim invokes elspy's local_scan(),
the following will happen:

  * elspy will initialize the Python interpreter and its
    own internal Python module ('_elspy').  (Actually, this only happens
    once per process -- so if a single Exim process handles multiple
    incoming messages, this will only happen once.)

  * elspy will then attempt to import the 'exim_local_scan' module.
    Since this almost certainly does not already exist on your system,
    the import will fail.  elspy logs a warning message to Exim's main
    log file, and accepts the message.


TESTING
-------

Put a sample SMTP session into a file, say smtp.txt:

  # cat > smtp.txt
  ehlo host.example.com  
  mail from:<user@example.com>
  rcpt to:<you@example.com>
  data
  Subject: hello there

  test message
  .
  quit
  ^D

(using real email addresses in place of user@example.com and
you@example.com, of course).

Now run this as root:

  # ./build-<os>/exim -v -bs < smtp.txt

(You need to run as root because the exim binary hasn't been installed,
so isn't setuid root yet.  If you prefer, you could also make it setuid
root for testing, and run the tests as an ordinary user.)

Next put a sample message in a file, say msg.txt:

  # cat > msg.txt
  Subject: hello again

  test message 2
  ^D

And run this as root (again, substitute your real email address):

  ./build-<os>/exim -v -f user@example.com you@example.com < msg.txt
     
In both cases, elspy should write an "error importing module" message to
Exim's main log.  Since you ran "exim -v", you'll also get that log
message on stdout.  Since elspy just reports and ignores Python import
errors, you should receive both messages.  (Assuming your email address
really is you@example.com -- adjust the above recipe to taste, of
course.)

If this worked, you can safely install your rebuilt Exim and "kill -HUP"
any running daemon.  The only downside is that your Exim main log will
have lots of elspy-generated "error importing module" messages until you
write and install that exim_local_scan module.


ACTIVATING ELSPY
----------------

Until the 'exim_local_scan' module exists somewhere on Python's search
path, elspy is inactive -- for every message received, it will attempt
to import that module, fail, and accept the message.  To activate elspy,
you must create exim_local_scan.py, define a local_scan() function in
it, and put it in Python's search path.

The simplest possible Python local_scan() function is:

  def local_scan (fd, headers, info):
      return

which accepts all messages.  For more examples, see:

  examples/simple.py      trivial unconditional local_scan() functions
  examples/acl_equiv.py   stuff that could be coded with Exim ACLs --
                          just provided to show how you might do
                          certain things in Python
  examples/chain.py       how to chain together a series of local_scan()s

  lib/execontent_simple.py  reject messages that appear to have Windows
                            executable content -- probably viruses
  lib/spamassassin.py     front-end to SpamAssassin's spamd to reject spam

See doc/API.txt for reference documentation of elspy's interface, ie. how
to write your own local_scan() functions in Python.  There's no tutorial
documentation yet -- read the examples and ask for help on
elspy-users@python.net if you get stuck.


WEB PAGE & MAILING LIST
-----------------------

You can find the latest version of elspy here:

  http://elspy.sourceforge.net/

To report bugs, submit patches, browse the CVS, etc., please use
the SourceForge project page:

  http://sourceforge.net/projects/elspy

For help using elspy, or to discuss future development ideas, please
join the elspy-users mailing list:

  http://starship.python.net/mailman/listinfo/elspy-users

You can post to the list by sending mail to elspy-users@python.net.


AUTHOR, COPYRIGHT, & LICENSE
----------------------------

elspy was written by Greg Ward <gward@python.net>.

Copyright (c) 2002 Gregory P. Ward.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

  1. Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
    
  3. Neither the name of the author nor the names of its
     contributors may be used to endorse or promote products derived from
     this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


$Id: README.txt,v 1.7 2002/10/21 22:00:56 gward Exp $
