This commit was manufactured by cvs2svn to create tag 'milter-0_8_1'.

Sprout from bmsi 2005-05-31 18:23:49 UTC Stuart Gathman <stuart@gathman.org> 'Development changes since 0.7.2'
Cherrypick from master 2005-06-17 02:23:34 UTC Stuart Gathman <stuart@gathman.org> 'Release 0.8.1':
    COPYING
    MANIFEST.in
    Milter/__init__.py
    Milter/dsn.py
    Milter/dynip.py
    NEWS
    TODO
    bms.py
    faq.html
    milter.cfg
    milter.html
    milter.spec
    miltermodule.c
    mime.py
    setup.cfg
    setup.py
    softfail.txt
    spf.py
    spfquery.py
    strike3.txt
    test/zip1
    test/ziploop
    testmime.py
This commit is contained in:
cvs2svn
2005-06-17 02:23:35 +00:00
parent 9fb3ad70d4
commit 3cef83f59b
23 changed files with 947 additions and 423 deletions
+25 -13
View File
@@ -1,7 +1,8 @@
# Author: Stuart D. Gathman <stuart@bmsi.com>
# Copyright 2001 Business Management Systems, Inc.
# This code is under GPL. See COPYING for details.
# This code is under the GNU General Public License. See COPYING for details.
# A thin OO wrapper for the milter module
import os
import milter
@@ -140,16 +141,28 @@ def closecallback(ctx):
m._setctx(None) # release milterContext
return rc
def dictfromlist(args):
"Convert ESMTP parm list to keyword dictionary."
kw = {}
for s in args:
pos = s.find('=')
if pos > 0:
kw[s[:pos].upper()] = s[pos+1:]
return kw
def envcallback(c,args):
"""Convert ESMTP parms to keyword parameters.
"""Call function c with ESMTP parms converted to keyword parameters.
Can be used in the envfrom and/or envrcpt callbacks to process
ESMTP parameters as python keyword parameters."""
kw = {}
pargs = [args[0]]
for s in args[1:]:
pos = s.find('=')
if pos > 0:
kw[s[:pos]] = s[pos+1:]
return apply(c,args,kw)
kw[s[:pos].upper()] = s[pos+1:]
else:
pargs.append(s)
return c(*pargs,**kw)
def runmilter(name,socketname,timeout = 0):
# This bit is here on the assumption that you will be starting this filter
@@ -176,14 +189,13 @@ def runmilter(name,socketname,timeout = 0):
# milter.set_flags(milter.ADDHDRS)
milter.set_connect_callback(connectcallback)
milter.set_helo_callback(lambda ctx, host: ctx.getpriv().hello(host))
milter.set_envfrom_callback(lambda ctx,*str:
ctx.getpriv().envfrom(*str))
# envcallback(ctx.getpriv().envfrom,str))
milter.set_envrcpt_callback(lambda ctx,*str:
ctx.getpriv().envrcpt(*str))
# envcallback(ctx.getpriv().envrcpt,str))
milter.set_header_callback(lambda ctx,fld,val:
ctx.getpriv().header(fld,val))
# For envfrom and envrcpt, we would like to convert ESMTP parms to keyword
# parms, but then all existing users would have to include **kw to accept
# arbitrary keywords without crashing. We do provide envcallback and
# dictfromlist to make parsing the ESMTP args convenient.
milter.set_envfrom_callback(lambda ctx,*str: ctx.getpriv().envfrom(*str))
milter.set_envrcpt_callback(lambda ctx,*str: ctx.getpriv().envrcpt(*str))
milter.set_header_callback(lambda ctx,fld,val: ctx.getpriv().header(fld,val))
milter.set_eoh_callback(lambda ctx: ctx.getpriv().eoh())
milter.set_body_callback(lambda ctx,chunk: ctx.getpriv().body(chunk))
milter.set_eom_callback(lambda ctx: ctx.getpriv().eom())
+37 -17
View File
@@ -1,9 +1,18 @@
# Author: Stuart D. Gathman <stuart@bmsi.com>
# Copyright 2005 Business Management Systems, Inc.
# This code is under the GNU General Public License. See COPYING for details.
# Send DSNs, do call back verification,
# and generate DSN messages from a template
import smtplib
import spf
import socket
from email.Message import Message
nospf_msg = """This is an automatically generated Delivery Status Notification.
nospf_msg = """Subject: Critical mail server configuration error
This is an automatically generated Delivery Status Notification.
THIS IS A WARNING MESSAGE ONLY.
@@ -65,11 +74,12 @@ If you need further assistance, please do not hesitate to
contact me again.
Kind regards,
Stuart D. Gathman
postmaster@%(receiver)s
"""
softfail_msg = """
softfail_msg = """Subject: SPF softfail (POSSIBLE FORGERY)
This is an automatically generated Delivery Status Notification.
THIS IS A WARNING MESSAGE ONLY.
@@ -85,7 +95,10 @@ Received-SPF: %(spf_result)s
"""
def send_dsn(mailfrom,receiver,msg=None):
"Send DSN. If msg is None, do callback verification."
"""Send DSN. If msg is None, do callback verification.
Mailfrom is original sender we are sending DSN or CBV to.
Receiver is the MTA sending the DSN.
Return None for success or (code,msg) for failure."""
user,domain = mailfrom.split('@')
q = spf.query(None,None,None)
mxlist = q.dns(domain,'MX')
@@ -102,7 +115,7 @@ def send_dsn(mailfrom,receiver,msg=None):
if resp.split()[0] == receiver:
return (553,'Fraudulent MX for %s' % domain)
if not (200 <= code <= 299):
raise SMTPHeloError(code, resp)
raise smtplib.SMTPHeloError(code, resp)
if msg:
try:
smtp.sendmail('<>',mailfrom,msg)
@@ -112,7 +125,7 @@ def send_dsn(mailfrom,receiver,msg=None):
else: # CBV
code,resp = smtp.docmd('MAIL FROM: <>')
if code != 250:
raise SMTPSenderRefused(code, resp, '<>')
raise smtplib.SMTPSenderRefused(code, resp, '<>')
code,resp = smtp.rcpt(mailfrom)
if code not in (250,251):
return (code,resp) # permanent error
@@ -121,9 +134,9 @@ def send_dsn(mailfrom,receiver,msg=None):
except smtplib.SMTPRecipientsRefused,x:
return x.recipients[mailfrom] # permanent error
except smtplib.SMTPSenderRefused,x:
return x # does not accept DSN
return x.args[:2] # does not accept DSN
except smtplib.SMTPDataError,x:
return x # permanent error
return x.args # permanent error
except smtplib.SMTPException:
pass # any other error, try next MX
except socket.error:
@@ -131,7 +144,8 @@ def send_dsn(mailfrom,receiver,msg=None):
smtp.close()
return (450,'No MX servers available') # temp error
def create_msg(q,rcptlist,origmsg):
def create_msg(q,rcptlist,origmsg=None,template=None):
"Create a DSN message from a template. Template must be '\n' separated."
heloname = q.h
sender = q.s
connectip = q.i
@@ -145,24 +159,30 @@ def create_msg(q,rcptlist,origmsg):
if not spf_result.startswith('softfail'):
spf_result = None
except: spf_result = None
msg = Message()
msg.add_header('To',sender)
msg.add_header('From','postmaster@%s'%receiver)
msg.add_header('Auto-Submitted','auto-generated (configuration error)')
msg.set_type('text/plain')
if spf_result:
msg.add_header('Subject','SPF softfail (POSSIBLE FORGERY)')
msg.set_payload(softfail_msg % locals())
else:
msg.add_header('Subject','Critical mail server configuration error')
msg.set_payload(nospf_msg % locals())
if not template:
if spf_result: template = softfail_msg
else: template = nospf_msg
hdrs,body = template.split('\n',1)
for ln in hdrs.splitlines():
name,val = ln.split(':',1)
msg.add_header(name,(val % locals()).strip())
msg.set_payload(body % locals())
return msg
if __name__ == '__main__':
q = spf.query('192.168.9.50',
'SRS0=pmeHL=RH=bmsi.com=stuart@bmsi.com',
'bmsred.bmsi.com',receiver='mail.bmsi.com')
msg = create_msg(q,'charlie@jsconnor.com')
#print msg.as_string()
msg = create_msg(q,['charlie@jsconnor.com'],None,None)
print msg.as_string()
# print send_dsn(f,msg.as_string())
print send_dsn(q.s,'mail.bmsi.com',msg.as_string())
+6
View File
@@ -1,3 +1,9 @@
# Author: Stuart D. Gathman <stuart@bmsi.com>
# Copyright 2005 Business Management Systems, Inc.
# This code is under the GNU General Public License. See COPYING for details.
# Heuristically determine whether a domain name is for a dynamic IP.
# examples we don't yet recognize:
#
# wiley-268-8196.roadrunner.nf.net at ('205.251.174.46', 4810)