Skip to content
Snippets Groups Projects
Commit 5d6ceaef authored by Stuart Gathman's avatar Stuart Gathman
Browse files

Support configurable templates for DSNs.

parent 1d10bb17
Branches
Tags
No related merge requests found
...@@ -3,7 +3,9 @@ import spf ...@@ -3,7 +3,9 @@ import spf
import socket import socket
from email.Message import Message 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. THIS IS A WARNING MESSAGE ONLY.
...@@ -65,11 +67,12 @@ If you need further assistance, please do not hesitate to ...@@ -65,11 +67,12 @@ If you need further assistance, please do not hesitate to
contact me again. contact me again.
Kind regards, Kind regards,
Stuart D. Gathman
postmaster@%(receiver)s postmaster@%(receiver)s
""" """
softfail_msg = """ softfail_msg = """Subject: SPF softfail (POSSIBLE FORGERY)
This is an automatically generated Delivery Status Notification. This is an automatically generated Delivery Status Notification.
THIS IS A WARNING MESSAGE ONLY. THIS IS A WARNING MESSAGE ONLY.
...@@ -131,7 +134,8 @@ def send_dsn(mailfrom,receiver,msg=None): ...@@ -131,7 +134,8 @@ def send_dsn(mailfrom,receiver,msg=None):
smtp.close() smtp.close()
return (450,'No MX servers available') # temp error 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 heloname = q.h
sender = q.s sender = q.s
connectip = q.i connectip = q.i
...@@ -145,24 +149,30 @@ def create_msg(q,rcptlist,origmsg): ...@@ -145,24 +149,30 @@ def create_msg(q,rcptlist,origmsg):
if not spf_result.startswith('softfail'): if not spf_result.startswith('softfail'):
spf_result = None spf_result = None
except: spf_result = None except: spf_result = None
msg = Message() msg = Message()
msg.add_header('To',sender) msg.add_header('To',sender)
msg.add_header('From','postmaster@%s'%receiver) msg.add_header('From','postmaster@%s'%receiver)
msg.add_header('Auto-Submitted','auto-generated (configuration error)') msg.add_header('Auto-Submitted','auto-generated (configuration error)')
msg.set_type('text/plain') msg.set_type('text/plain')
if spf_result:
msg.add_header('Subject','SPF softfail (POSSIBLE FORGERY)') if not template:
msg.set_payload(softfail_msg % locals()) if spf_result: template = softfail_msg
else: else: template = nospf_msg
msg.add_header('Subject','Critical mail server configuration error') hdrs,body = template.split('\n',1)
msg.set_payload(nospf_msg % locals()) for ln in hdrs.splitlines():
name,val = ln.split(':',1)
msg.add_header(name,(val % locals()).strip())
msg.set_payload(body % locals())
return msg return msg
if __name__ == '__main__': if __name__ == '__main__':
q = spf.query('192.168.9.50', q = spf.query('192.168.9.50',
'SRS0=pmeHL=RH=bmsi.com=stuart@bmsi.com', 'SRS0=pmeHL=RH=bmsi.com=stuart@bmsi.com',
'bmsred.bmsi.com',receiver='mail.bmsi.com') 'bmsred.bmsi.com',receiver='mail.bmsi.com')
msg = create_msg(q,'charlie@jsconnor.com') msg = create_msg(q,['charlie@jsconnor.com'],None,None)
#print msg.as_string() print msg.as_string()
# print send_dsn(f,msg.as_string()) # print send_dsn(f,msg.as_string())
print send_dsn(q.s,'mail.bmsi.com',msg.as_string()) print send_dsn(q.s,'mail.bmsi.com',msg.as_string())
Here is a history of user visible changes to Python milter. Here is a history of user visible changes to Python milter.
0.8.0 Move Milter module to subpackage.
DSN support for Three strikes rule and SPF SOFTFAIL
Move /*mime*/ and dynip to Milter subpackage
Fix SPF unknown mechanism list not cleared
0.7.3 Experimental release with python2.4 support
0.7.2 Return unknown for invalid ip address in mechanism 0.7.2 Return unknown for invalid ip address in mechanism
Recognize dynamic PTR names, and don't count them as authentication. Recognize dynamic PTR names, and don't count them as authentication.
Three strikes and yer out rule. Three strikes and yer out rule.
......
#!/usr/bin/env python #!/usr/bin/env python
# A simple milter. # A simple milter that has grown quite a bit.
# $Log$ # $Log$
#
# Revision 1.134 2005/05/25 15:36:43 stuart # Revision 1.134 2005/05/25 15:36:43 stuart
# Use dynip module. # Use dynip module.
# Support smart aliasing of wiretap destination. # Support smart aliasing of wiretap destination.
...@@ -252,6 +253,7 @@ spf_reject_neutral = () ...@@ -252,6 +253,7 @@ spf_reject_neutral = ()
spf_accept_softfail = () spf_accept_softfail = ()
spf_best_guess = False spf_best_guess = False
spf_reject_noptr = False spf_reject_noptr = False
multiple_bounce_recipients = True
timeout = 600 timeout = 600
cbv_cache = {} cbv_cache = {}
try: try:
...@@ -264,7 +266,7 @@ class MilterConfigParser(ConfigParser.ConfigParser): ...@@ -264,7 +266,7 @@ class MilterConfigParser(ConfigParser.ConfigParser):
def getlist(self,sect,opt): def getlist(self,sect,opt):
if self.has_option(sect,opt): if self.has_option(sect,opt):
return [q.strip() for q in self.get(sect,opt).split(',')] return [q.strip() for q in self.get(sect,opt).split(',')]
return () return []
def getaddrset(self,sect,opt): def getaddrset(self,sect,opt):
if not self.has_option(sect,opt): if not self.has_option(sect,opt):
...@@ -637,6 +639,7 @@ class bmsMilter(Milter.Milter): ...@@ -637,6 +639,7 @@ class bmsMilter(Milter.Milter):
) )
return Milter.REJECT return Milter.REJECT
if self.mailfrom != '<>': if self.mailfrom != '<>':
q.result = res
self.cbv_needed = q self.cbv_needed = q
if res in ('deny', 'fail'): if res in ('deny', 'fail'):
self.log('REJECT: SPF %s %i %s' % (res,code,txt)) self.log('REJECT: SPF %s %i %s' % (res,code,txt))
...@@ -658,6 +661,7 @@ class bmsMilter(Milter.Milter): ...@@ -658,6 +661,7 @@ class bmsMilter(Milter.Milter):
) )
return Milter.REJECT return Milter.REJECT
if self.mailfrom != '<>': if self.mailfrom != '<>':
q.result = res
self.cbv_needed = q self.cbv_needed = q
if res == 'neutral' and q.o in spf_reject_neutral: if res == 'neutral' and q.o in spf_reject_neutral:
self.log('REJECT: SPF neutral for',q.s) self.log('REJECT: SPF neutral for',q.s)
...@@ -695,7 +699,7 @@ class bmsMilter(Milter.Milter): ...@@ -695,7 +699,7 @@ class bmsMilter(Milter.Milter):
user,domain = t user,domain = t
if self.mailfrom == '<>' or self.canon_from.startswith('postmaster@') \ if self.mailfrom == '<>' or self.canon_from.startswith('postmaster@') \
or self.canon_from.startswith('mailer-daemon@'): or self.canon_from.startswith('mailer-daemon@'):
if self.recipients: if self.recipients and not multiple_bounce_recipients:
self.data_allowed = False self.data_allowed = False
if srs and domain == srs_fwdomain: if srs and domain == srs_fwdomain:
oldaddr = '@'.join(parse_addr(to)) oldaddr = '@'.join(parse_addr(to))
...@@ -843,8 +847,9 @@ class bmsMilter(Milter.Milter): ...@@ -843,8 +847,9 @@ class bmsMilter(Milter.Milter):
# copy headers to a temp file for scanning the body # copy headers to a temp file for scanning the body
headers = self.fp.getvalue() headers = self.fp.getvalue()
self.fp.close() self.fp.close()
self.tempname = fname = tempfile.mktemp(".defang") fd,fname = tempfile.mkstemp(".defang")
self.fp = open(fname,"w+b") self.tempname = fname
self.fp = os.fdopen(fd,"w+b")
self.fp.write(headers) # IOError (e.g. disk full) causes TEMPFAIL self.fp.write(headers) # IOError (e.g. disk full) causes TEMPFAIL
# check if headers are really spammy # check if headers are really spammy
if dspam_dict and not self.internal_connection: if dspam_dict and not self.internal_connection:
...@@ -1048,14 +1053,21 @@ class bmsMilter(Milter.Milter): ...@@ -1048,14 +1053,21 @@ class bmsMilter(Milter.Milter):
self.addheader(name,val) self.addheader(name,val)
if self.cbv_needed: if self.cbv_needed:
sender = self.cbv_needed.s q = self.cbv_needed
sender = q.s
cached = cbv_cache.has_key(sender) cached = cbv_cache.has_key(sender)
if cached: if cached:
self.log('CBV:',sender,'(cached)') self.log('CBV:',sender,'(cached)')
res = cbv_cache[sender] res = cbv_cache[sender]
else: else:
self.log('CBV:',sender) self.log('CBV:',sender)
m = dsn.create_msg(self.cbv_needed,self.recipients,msg) try:
if q.result == 'softfail':
template = file('softfail.txt').read()
else:
template = file('strike3.txt').read()
except IOError: template = None
m = dsn.create_msg(q,self.recipients,msg,template)
m = m.as_string() m = m.as_string()
print >>open('last_dsn','w'),m print >>open('last_dsn','w'),m
res = dsn.send_dsn(sender,self.receiver,m) res = dsn.send_dsn(sender,self.receiver,m)
......
...@@ -104,6 +104,8 @@ blind = 1 ...@@ -104,6 +104,8 @@ blind = 1
# additional copies can be added # additional copies can be added
;walter1 = cust@othercorp.com,walter@bigcorp.com,boss@bigcorp.com, ;walter1 = cust@othercorp.com,walter@bigcorp.com,boss@bigcorp.com,
; walter@bigcorp.com ; walter@bigcorp.com
;bulk = soruce@telex.com,bob@jsconnor.com
;bulk = soruce@telex.com,larry@jsconnor.com
# See http://bmsi.com/python/dspam.html # See http://bmsi.com/python/dspam.html
[dspam] [dspam]
......
%define name milter %define name milter
%define version 0.8.0 %define version 0.8.0
%define release 2.EL3 %define release 3.RH7
# Redhat 7.x and earlier (multiple ps lines per thread) # what version of RH are we building for?
#define sysvinit milter.rc7 %define redhat9 0 # and Enterprise Linux
# RH9, other systems (single ps line per process) %define redhat7 1
%define redhat6 0
# Options for Redhat version 6.x:
# rpm -ba|--rebuild --define "rh6 1"
%{?rh6:%define redhat7 0}
%{?rh6:%define redhat6 1}
# some systems dont have initrddir defined
%{?_initrddir:%define _initrddir /etc/rc.d/init.d}
%if %{redhat9}
%define sysvinit milter.rc %define sysvinit milter.rc
%else # Redhat 7.x and earlier (multiple ps lines per thread)
%define sysvinit milter.rc7
%endif
# RH9, other systems (single ps line per process)
%ifos Linux %ifos Linux
%define python python2.4 %define python python2.4
%else %else
...@@ -25,7 +40,7 @@ Vendor: Stuart D. Gathman <stuart@bmsi.com> ...@@ -25,7 +40,7 @@ Vendor: Stuart D. Gathman <stuart@bmsi.com>
Packager: Stuart D. Gathman <stuart@bmsi.com> Packager: Stuart D. Gathman <stuart@bmsi.com>
Url: http://www.bmsi.com/python/milter.html Url: http://www.bmsi.com/python/milter.html
Requires: %{python} >= 2.4, sendmail >= 8.12.10 Requires: %{python} >= 2.4, sendmail >= 8.12.10
%ifnos aix4.1 %ifos Linux
Requires: chkconfig Requires: chkconfig
%endif %endif
BuildRequires: %{python}-devel >= 2.2.2, sendmail-devel >= 8.12.10 BuildRequires: %{python}-devel >= 2.2.2, sendmail-devel >= 8.12.10
...@@ -149,8 +164,13 @@ rm -rf $RPM_BUILD_ROOT ...@@ -149,8 +164,13 @@ rm -rf $RPM_BUILD_ROOT
/usr/share/sendmail-cf/hack/rhsbl.m4 /usr/share/sendmail-cf/hack/rhsbl.m4
%changelog %changelog
* Wed May 25 2005 Stuart Gathman <stuart@bmsi.com> 0.8.0-1
- Move Milter module to subpackage.
- DSN support for Three strikes rule and SPF SOFTFAIL
- Move /*mime*/ and dynip to Milter subpackage
- Fix SPF unknown mechanism list not cleared
* Tue Feb 08 2005 Stuart Gathman <stuart@bmsi.com> 0.7.3-1.EL3 * Tue Feb 08 2005 Stuart Gathman <stuart@bmsi.com> 0.7.3-1.EL3
- Compile for EL3 and Python4 - Support EL3 and Python2.4 (some scanning/defang support broken)
* Mon Aug 30 2004 Stuart Gathman <stuart@bmsi.com> 0.7.2-1 * Mon Aug 30 2004 Stuart Gathman <stuart@bmsi.com> 0.7.2-1
- Fix various SPF bugs - Fix various SPF bugs
- Recognize dynamic PTR names, and don't count them as authentication. - Recognize dynamic PTR names, and don't count them as authentication.
......
...@@ -2,3 +2,4 @@ ...@@ -2,3 +2,4 @@
python=python2 python=python2
doc_files=README NEWS TODO doc_files=README NEWS TODO
packager=Stuart D. Gathman <stuart@bmsi.com> packager=Stuart D. Gathman <stuart@bmsi.com>
release=2.4
Subject: SPF softfail (POSSIBLE FORGERY)
This is an automatically generated Delivery Status Notification.
THIS IS A WARNING MESSAGE ONLY.
YOU DO *NOT* NEED TO RESEND YOUR MESSAGE.
Delivery to the following recipients has been delayed.
%(rcpt)s
Subject: %(subject)s
Received-SPF: %(spf_result)s
Your sender policy indicated that the above email was likely forged and that
feedback was desired.
If you need further assistance, please do not hesitate to contact me.
Kind regards,
postmaster@%(receiver)s
Subject: Critical mail server configuration error
This is an automatically generated Delivery Status Notification.
THIS IS A WARNING MESSAGE ONLY.
YOU DO *NOT* NEED TO RESEND YOUR MESSAGE.
Delivery to the following recipients has been delayed.
%(rcpt)s
Subject: %(subject)s
Someone at IP address %(connectip)s sent an email claiming
to be from %(sender)s.
If that wasn't you, then your domain, %(sender_domain)s,
was forged - i.e. used without your knowlege or authorization by
someone attempting to steal your mail identity. This is a very
serious problem, and you need to provide authentication for your
SMTP (email) servers to prevent criminals from forging your
domain. The simplest step is usually to publish an SPF record
with your Sender Policy.
For more information, see: http://spfhelp.net
I hate to annoy you with a DSN (Delivery Status
Notification) from a possibly forged email, but since you
have not published a sender policy, there is no other way
of bringing this to your attention.
If it *was* you that sent the email, then your email domain
or configuration is in error. If you don't know anything
about mail servers, then pass this on to your SMTP (mail)
server administrator. We have accepted the email anyway, in
case it is important, but we couldn't find anything about
the mail submitter at %(connectip)s to distinguish it from a
zombie (compromised/infected computer - usually a Windows
PC). There was no PTR record for its IP address (PTR names
that contain the IP address don't count). RFC2821 requires
that your hello name be a FQN (Fully Qualified domain Name,
i.e. at least one dot) that resolves to the IP address of
the mail sender. In addition, just like for PTR, we don't
accept a helo name that contains the IP, since this doesn't
help to identify you. The hello name you used,
%(heloname)s, was invalid.
Furthermore, there was no SPF record for the sending domain
%(sender_domain)s. We even tried to find its IP in any A or
MX records for your domain, but that failed also. We really
should reject mail from anonymous mail clients, but in case
it is important, we are accepting it anyway.
We are sending you this message to alert you to the fact that
Either - Someone is forging your domain.
Or - You have problems with your email configuration.
Or - Possibly both.
If you need further assistance, please do not hesitate to
contact me again.
Kind regards,
postmaster@%(receiver)s
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment