From 5d6ceaefe48fb50e3a155ac3422939d2672a5e9d Mon Sep 17 00:00:00 2001 From: Stuart Gathman <stuart@gathman.org> Date: Thu, 2 Jun 2005 01:00:37 +0000 Subject: [PATCH] Support configurable templates for DSNs. --- Milter/dsn.py | 34 ++++++++++++++++---------- NEWS | 5 ++++ bms.py | 26 ++++++++++++++------ milter.cfg | 2 ++ milter.spec | 32 ++++++++++++++++++++----- setup.cfg | 1 + softfail.txt | 23 ++++++++++++++++++ strike3.txt | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 164 insertions(+), 25 deletions(-) create mode 100644 softfail.txt create mode 100644 strike3.txt diff --git a/Milter/dsn.py b/Milter/dsn.py index 1865689..c66e130 100644 --- a/Milter/dsn.py +++ b/Milter/dsn.py @@ -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()) diff --git a/NEWS b/NEWS index 5ec2771..13f961f 100644 --- a/NEWS +++ b/NEWS @@ -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. diff --git a/bms.py b/bms.py index 946056b..a526e2e 100644 --- a/bms.py +++ b/bms.py @@ -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) diff --git a/milter.cfg b/milter.cfg index 43f0e53..6204f57 100644 --- a/milter.cfg +++ b/milter.cfg @@ -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] diff --git a/milter.spec b/milter.spec index f71ef4a..354c588 100644 --- a/milter.spec +++ b/milter.spec @@ -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. diff --git a/setup.cfg b/setup.cfg index 1b9f4f1..7503af5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,3 +2,4 @@ python=python2 doc_files=README NEWS TODO packager=Stuart D. Gathman <stuart@bmsi.com> +release=2.4 diff --git a/softfail.txt b/softfail.txt new file mode 100644 index 0000000..263cecf --- /dev/null +++ b/softfail.txt @@ -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 diff --git a/strike3.txt b/strike3.txt new file mode 100644 index 0000000..f76416c --- /dev/null +++ b/strike3.txt @@ -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 -- GitLab