pymta is a library to build a custom SMTP server in Python. This is useful if you want to...
pymta is just a Python library which uses setuptools so it does not require a special setup. The only direct dependency is repoze.workflow (0.2dev). Currently pymta is only tested with Python 2.5 but probably 2.4 works too. The goal is to make pymta compatible with Python 2.3-2.6. Python 2.3 may require a custom version of asyncore.
repoze.workflow is not available in pypi (yet?) so you have to install it directly from the svn:
easy_install http://svn.repoze.org/repoze.workflow/trunk/
repoze.workflow requires zope.interface which available via pypi (and installable via the package manager for most Linux distributions).
The main goal of pymta is to provide a basic SMTP server for unit tests. It must be easy to inject custom behavior (policy checks) for every SMTP command. Furthermore the library should come with an extensive set of tests to ensure that does the right thing(tm).
Currently (12/2008, version 0.2) the library only implements basic SMTP with very few extensions (e.g. PLAIN authentication). ‘Advanced’ features which are necessary for any decent MTA like TLS and pipelining are not implemented yet. Currently pymta is used only in the unit tests for TurboMail. Therefore it should be considered as beta software.
pytma uses asynchronous programming to handle multiple connections at the same time and is based on Python’s asyncore. There are two state machines: One for the SMTP command parsing mode (single-line commands or data) in the SMTPCommandParser and another much bigger state machine in the SMTPSession to control the correct order of commands sent by the SMTP client.
The main idea of pymta was to make it easy adding custom behavior which is considered configuration for ‘real’ SMTP servers like Exim. Therefore you should look at the DefaultMTAPolicy to add restrictions on certain SMTP commands (check recipient addresses, scan the message’s content for spam before accepting it). In order to authenticate SMTP clients (check username and password) you may implement the IAuthenticator interface.
pymta consists of several main components (classes) which may be important to know.
The PythonMTA is the main server component which listens on a certain port for new connections. There should be only one instance of this object. When a new connection is received, the PythonMTA spawns a new SMTPCommand parser which will handle the complete SMTP session. If a message was submitted successfully, the new_message_received() method of PythonMTA will be called so the MTA is in charge of actually doing something with the message.
You can instantiate a new server like that:
import asyncore
from pymta import PythonMTA
mta = PythonMTA('localhost', 25)
try:
asyncore.loop()
except KeyboardInterrupt:
pass
Interface
The policy is asked after every SMTP command if the command should be accepted or not. You can use this to check the parameter of the command or make some features only available for selected clients.
All methods in the policy should return a boolean value to express if a command should be accepted. If you need more control you can return a tuple containing the boolean decision and either a single-line response as string or list/tuple containing the response code and response text:
def accept_helo(self, helo_string, message):
# pymta will return the default error message for the given command if
# you just return False
return False
# This will send out a '553 Bad helo string' and the command is
# rejected. pymta won't send any additional reply because you did that
# already.
return (False, (553, 'Bad helo string'))
# This is basically the same as above but now it will trigger a
# multi-line SMTP response:
# 553-Bad helo string
# 553 Evil IP
return (False, (553, ('Bad helo string', 'Evil IP'))
In the default policy you don’t have to care if the commands were given in the correct order (the state machines will take care of that). The only thing is that the message object passed into many policy methods does not contain all data at certain stages (e.g. accept_mail_from can not access the recipients list because that was not submitted yet).
Interface
Authenticators check if the user’s credentials are actually correct. This may involve some checking against external subsystems (e.g. a database or a LDAP directory).
Interface
The Message is a data object contains all information about a message sent by a client. This includes not only the actual RFC822 message contents but also information about the SMTP envelope, the peer and the helo string used. The information is filled as the client sends some commands so not all information may be available at any time (e.g. the msg_data not available before the client actually sent the RFC822 message).
The Peer is another data object which contains the remote host ip address and the remote port.
This class actually implements the most complicated part of the SMTP state machine and is responsible for calling the policy. If you want to extend the functionality or need to implement some custom behavior which is beyond what you can do using Policies, check this class.