Here is the recommended placement of the authentication check in a typical mail flow:

Note: checkID has an option "Quick pre-screen for heavily-spamming IPs", which can be used to
reject mail that isn't worth even one Registry query. Currently, it uses the Spamhaus IP blacklist
for these rejects. The IP Blacklist block uses other, more
aggressive blacklists.
|
Sendmail |
Postfix |
Exchange |
|
Exim |
Qmail |
|
We have a Python function checkID() that does all the authentication work. We need an interface to each MTA that will allow the Python script to be edited, without having to change or re-compile the MTA.

Sendmail already has a Python milter interface, which we could adapt. http://sourceforge.net/projects/pymilter The others might be done with a C-wrapper that can be compiled into any MTA written in C. I'm thinking of something like Example 20-6 in Programming Python, 2nd ed.
The functions we need
in the MTA are:
1) Support for a new ID command, following the rules for SMTP extensions.
2) Calling checkID with the connection IP address and the strings from the HELO, ID, and MAIL FROM commands.
3) Implementation of the actions ACCEPT, REJECT, etc.
4) Sending the SMTP_reply returned by checkID to the SMTP client (sender).
5) Prepending the headers returned by checkID to any forwarded message.
Here is the interface in
Python:
def checkID( IP=None, ID=None, H=None, S=None, R=[], Hdrs=[], Body=None ):
'''
Check that ID authorizes sending email from a machine with address IP.
H, S, and R are the rest of the envelope information, whatever is known at the
time of this call.
ID should be left None if there is no declared ID.
H and S are the names from the HELO/EHLO and MAIL FROM commands.
R is a list of addresses from the RCPT TO command.
Returns ( action, SMTP_reply, headers )
action: 'ACCEPT', 'REJECT', 'FILTER', 'CONTINUE', 'TEMPFAIL', 'DISCARD'
see http://www.milter.org/milter_api/api.html
SMTP_reply = ( SMTP_code, Xcode, explanation )
SMTP_code: SMTP Reply Code per RFC-2821
Xcode: Enhanced Mail System Status Code per RFC-3463
headers = [header0, header1, ...]
header0 = {'label': 'Authent:',
'text': <standard authentication parameters> }
headerN = {'label': <method-specific label:>,
'text': <method-specific text> }
Headers to be pre-pended in the order listed. 'header0' is the very first
header from this Administrative Domain, 'header1' is above that, etc. Other
headers may be inserted in this list if required by a method, e.g. SPF
requires that the 'Received:' header for the border MTA be below its header.
Text strings have no line breaks. Folding white space
should be added before prepending a long header.
>>> checkID('192.0.2.1', 'example.net')
('REJECT', (550, '5.7.1', "Sending MTA '192.0.2.1' not
authorized by \
'example.net'"), [])
>>> checkID('192.0.2.1', 'example.com')
('ACCEPT', (250, '', 'Sender OK'), \
[{'text': '192.0.2.1 example.com SPF1 PASS ratings=S1:A,M2:A,H1:B', \
'label': 'Authent:'}])
'''