Support configurable templates for DSNs.
This commit is contained in:
+22
-12
@@ -3,7 +3,9 @@ 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 +67,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.
|
||||
@@ -131,7 +134,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 +149,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())
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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
|
||||
Recognize dynamic PTR names, and don't count them as authentication.
|
||||
Three strikes and yer out rule.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# A simple milter.
|
||||
# A simple milter that has grown quite a bit.
|
||||
# $Log$
|
||||
#
|
||||
# Revision 1.134 2005/05/25 15:36:43 stuart
|
||||
# Use dynip module.
|
||||
# Support smart aliasing of wiretap destination.
|
||||
@@ -252,6 +253,7 @@ spf_reject_neutral = ()
|
||||
spf_accept_softfail = ()
|
||||
spf_best_guess = False
|
||||
spf_reject_noptr = False
|
||||
multiple_bounce_recipients = True
|
||||
timeout = 600
|
||||
cbv_cache = {}
|
||||
try:
|
||||
@@ -264,7 +266,7 @@ class MilterConfigParser(ConfigParser.ConfigParser):
|
||||
def getlist(self,sect,opt):
|
||||
if self.has_option(sect,opt):
|
||||
return [q.strip() for q in self.get(sect,opt).split(',')]
|
||||
return ()
|
||||
return []
|
||||
|
||||
def getaddrset(self,sect,opt):
|
||||
if not self.has_option(sect,opt):
|
||||
@@ -637,6 +639,7 @@ class bmsMilter(Milter.Milter):
|
||||
)
|
||||
return Milter.REJECT
|
||||
if self.mailfrom != '<>':
|
||||
q.result = res
|
||||
self.cbv_needed = q
|
||||
if res in ('deny', 'fail'):
|
||||
self.log('REJECT: SPF %s %i %s' % (res,code,txt))
|
||||
@@ -658,6 +661,7 @@ class bmsMilter(Milter.Milter):
|
||||
)
|
||||
return Milter.REJECT
|
||||
if self.mailfrom != '<>':
|
||||
q.result = res
|
||||
self.cbv_needed = q
|
||||
if res == 'neutral' and q.o in spf_reject_neutral:
|
||||
self.log('REJECT: SPF neutral for',q.s)
|
||||
@@ -695,7 +699,7 @@ class bmsMilter(Milter.Milter):
|
||||
user,domain = t
|
||||
if self.mailfrom == '<>' or self.canon_from.startswith('postmaster@') \
|
||||
or self.canon_from.startswith('mailer-daemon@'):
|
||||
if self.recipients:
|
||||
if self.recipients and not multiple_bounce_recipients:
|
||||
self.data_allowed = False
|
||||
if srs and domain == srs_fwdomain:
|
||||
oldaddr = '@'.join(parse_addr(to))
|
||||
@@ -843,8 +847,9 @@ class bmsMilter(Milter.Milter):
|
||||
# copy headers to a temp file for scanning the body
|
||||
headers = self.fp.getvalue()
|
||||
self.fp.close()
|
||||
self.tempname = fname = tempfile.mktemp(".defang")
|
||||
self.fp = open(fname,"w+b")
|
||||
fd,fname = tempfile.mkstemp(".defang")
|
||||
self.tempname = fname
|
||||
self.fp = os.fdopen(fd,"w+b")
|
||||
self.fp.write(headers) # IOError (e.g. disk full) causes TEMPFAIL
|
||||
# check if headers are really spammy
|
||||
if dspam_dict and not self.internal_connection:
|
||||
@@ -1048,14 +1053,21 @@ class bmsMilter(Milter.Milter):
|
||||
self.addheader(name,val)
|
||||
|
||||
if self.cbv_needed:
|
||||
sender = self.cbv_needed.s
|
||||
q = self.cbv_needed
|
||||
sender = q.s
|
||||
cached = cbv_cache.has_key(sender)
|
||||
if cached:
|
||||
self.log('CBV:',sender,'(cached)')
|
||||
res = cbv_cache[sender]
|
||||
else:
|
||||
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()
|
||||
print >>open('last_dsn','w'),m
|
||||
res = dsn.send_dsn(sender,self.receiver,m)
|
||||
|
||||
@@ -104,6 +104,8 @@ blind = 1
|
||||
# additional copies can be added
|
||||
;walter1 = cust@othercorp.com,walter@bigcorp.com,boss@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
|
||||
[dspam]
|
||||
|
||||
+26
-6
@@ -1,10 +1,25 @@
|
||||
%define name milter
|
||||
%define version 0.8.0
|
||||
%define release 2.EL3
|
||||
# Redhat 7.x and earlier (multiple ps lines per thread)
|
||||
#define sysvinit milter.rc7
|
||||
# RH9, other systems (single ps line per process)
|
||||
%define release 3.RH7
|
||||
# what version of RH are we building for?
|
||||
%define redhat9 0 # and Enterprise Linux
|
||||
%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
|
||||
%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
|
||||
%define python python2.4
|
||||
%else
|
||||
@@ -25,7 +40,7 @@ Vendor: Stuart D. Gathman <stuart@bmsi.com>
|
||||
Packager: Stuart D. Gathman <stuart@bmsi.com>
|
||||
Url: http://www.bmsi.com/python/milter.html
|
||||
Requires: %{python} >= 2.4, sendmail >= 8.12.10
|
||||
%ifnos aix4.1
|
||||
%ifos Linux
|
||||
Requires: chkconfig
|
||||
%endif
|
||||
BuildRequires: %{python}-devel >= 2.2.2, sendmail-devel >= 8.12.10
|
||||
@@ -149,8 +164,13 @@ rm -rf $RPM_BUILD_ROOT
|
||||
/usr/share/sendmail-cf/hack/rhsbl.m4
|
||||
|
||||
%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
|
||||
- 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
|
||||
- Fix various SPF bugs
|
||||
- Recognize dynamic PTR names, and don't count them as authentication.
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
python=python2
|
||||
doc_files=README NEWS TODO
|
||||
packager=Stuart D. Gathman <stuart@bmsi.com>
|
||||
release=2.4
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
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
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
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
|
||||
Reference in New Issue
Block a user