From b0286bff226a843d76c466f5adf50079c2039722 Mon Sep 17 00:00:00 2001 From: Stuart Gathman Date: Thu, 4 Aug 2005 21:21:33 +0000 Subject: [PATCH] Treat fail like softfail for selected (braindead) domains. Treat mail according to extended processing results, but report any PermError that would officially result via DSN. --- Milter/dsn.py | 12 +++++++----- bms.py | 38 +++++++++++++++++++++++++++----------- milter.cfg | 3 +++ milter.spec | 3 +++ permerror.txt | 31 +++++++++++++++++++++++++++++++ softfail.txt | 2 +- 6 files changed, 72 insertions(+), 17 deletions(-) create mode 100644 permerror.txt diff --git a/Milter/dsn.py b/Milter/dsn.py index 961858e..469715c 100644 --- a/Milter/dsn.py +++ b/Milter/dsn.py @@ -103,7 +103,7 @@ def send_dsn(mailfrom,receiver,msg=None): q = spf.query(None,None,None) mxlist = q.dns(domain,'MX') if not mxlist: - mxlist = (0,domain), + mxlist = (0,domain), # fallback to A record when no MX else: mxlist.sort() smtp = smtplib.SMTP() @@ -151,13 +151,13 @@ def create_msg(q,rcptlist,origmsg=None,template=None): connectip = q.i receiver = q.r sender_domain = q.o + result = q.result + perm_error = q.perm_error rcpt = '\n\t'.join(rcptlist) try: subject = origmsg['Subject'] except: subject = '(none)' try: spf_result = origmsg['Received-SPF'] - if not spf_result.startswith('softfail'): - spf_result = None except: spf_result = None msg = Message() @@ -168,8 +168,10 @@ def create_msg(q,rcptlist,origmsg=None,template=None): msg.set_type('text/plain') if not template: - if spf_result: template = softfail_msg - else: template = nospf_msg + if spf_result and spf_result.startswith('softfail'): + template = softfail_msg + else: + template = nospf_msg hdrs,body = template.split('\n',1) for ln in hdrs.splitlines(): name,val = ln.split(':',1) diff --git a/bms.py b/bms.py index 9c6d74e..05e01d4 100644 --- a/bms.py +++ b/bms.py @@ -1,6 +1,9 @@ #!/usr/bin/env python # A simple milter that has grown quite a bit. # $Log$ +# Revision 1.20 2005/08/02 18:04:35 customdesigned +# Keep screened honeypot mail, but optionally discard honeypot only mail. +# # Revision 1.19 2005/07/20 03:30:04 customdesigned # Check pydspam version for honeypot, include latest pyspf changes. # @@ -312,6 +315,7 @@ srs_reject_spoofed = False srs_domain = None spf_reject_neutral = () spf_accept_softfail = () +spf_accept_fail = () spf_best_guess = False spf_reject_noptr = False multiple_bounce_recipients = True @@ -466,11 +470,12 @@ def read_config(list): # spf section global spf_reject_neutral,spf_best_guess,SRS,spf_reject_noptr - global spf_accept_softfail + global spf_accept_softfail,spf_accept_fail if spf: spf.DELEGATE = cp.getdefault('spf','delegate') spf_reject_neutral = cp.getlist('spf','reject_neutral') spf_accept_softfail = cp.getlist('spf','accept_softfail') + spf_accept_fail = cp.getlist('spf','accept_fail') spf_best_guess = cp.getboolean('spf','best_guess') spf_reject_noptr = cp.getboolean('spf','reject_noptr') srs_config = cp.getdefault('srs','config') @@ -698,11 +703,17 @@ class bmsMilter(Milter.Milter): t = parse_addr(self.mailfrom) if len(t) == 2: t[1] = t[1].lower() receiver = self.receiver - q = spf.query(self.connectip,'@'.join(t),self.hello_name,receiver=receiver) + q = spf.query(self.connectip,'@'.join(t),self.hello_name,receiver=receiver, + strict=False) q.set_default_explanation( 'SPF fail: see http://openspf.com/why.html?sender=%s&ip=%s' % (q.s,q.i)) res,code,txt = q.check() - if res in ('none', 'softfail'): + if res == 'unknown' and q.perm_error: + q.result = res + self.cbv_needed = q # report SPF syntax error to sender + res,code,txt = q.perm_error.ext # extended (lax processing) result + txt = 'EXT: ' + txt + if res in ('none','softfail','deny','fail'): if self.mailfrom != '<>': # check hello name via spf h = spf.query(self.connectip,'',self.hello_name,receiver=receiver) @@ -724,7 +735,6 @@ class bmsMilter(Milter.Milter): #self.log('SPF: no record published, guessing') q.set_default_explanation( 'SPF guess: see http://spf.pobox.com/why.html') - q.strict = False # best_guess should not result in fail if self.missing_ptr: # ignore dynamic PTR for best guess @@ -749,12 +759,16 @@ class bmsMilter(Milter.Milter): q.result = res self.cbv_needed = q if res in ('deny', 'fail'): - self.log('REJECT: SPF %s %i %s' % (res,code,txt)) - self.setreply(str(code),'5.7.1',txt) - # A proper SPF fail error message would read: - # forger.biz [1.2.3.4] is not allowed to send mail with the domain - # "forged.org" in the sender address. Contact . - return Milter.REJECT + if hres == 'pass' and q.o in spf_accept_fail: + q.result = res + self.cbv_needed = q + else: + self.log('REJECT: SPF %s %i %s' % (res,code,txt)) + self.setreply(str(code),'5.7.1',txt) + # A proper SPF fail error message would read: + # forger.biz [1.2.3.4] is not allowed to send mail with the domain + # "forged.org" in the sender address. Contact . + return Milter.REJECT if res == 'softfail' and not q.o in spf_accept_softfail: if self.missing_ptr and hres != 'pass': if spf_reject_noptr or q.o in spf_reject_neutral: @@ -1206,8 +1220,10 @@ class bmsMilter(Milter.Milter): else: self.log('CBV:',sender) try: - if q.result == 'softfail': + if q.result in ('softfail','fail','deny'): template = file('softfail.txt').read() + elif q.result == 'unknown': + template = file('permerror.txt').read() else: template = file('strike3.txt').read() except IOError: template = None diff --git a/milter.cfg b/milter.cfg index 6326a1b..5dc2f69 100644 --- a/milter.cfg +++ b/milter.cfg @@ -85,6 +85,9 @@ reject_spoofed = 0 ;reject_noptr = 0 # always accept softfail from these domains, or send DSN otherwise ;accept_softfail = bounces.amazon.com +# treat fail from these domains like softfail: because their SPF record +# or an important sender is screwed up. Must have valid HELO, however. +;accept_fail = custhelp.com # features intended to clean up outgoing mail [scrub] diff --git a/milter.spec b/milter.spec index a0d30ed..7efa55b 100644 --- a/milter.spec +++ b/milter.spec @@ -166,6 +166,9 @@ rm -rf $RPM_BUILD_ROOT /usr/share/sendmail-cf/hack/rhsbl.m4 %changelog +* Fri Jul 15 2005 Stuart Gathman 0.8.3-1 +- Keep screened honeypot mail, but optionally discard honeypot only mail. +- spf_accept_fail option for braindead SPF senders * Fri Jul 15 2005 Stuart Gathman 0.8.2-4 - Limit each CNAME chain independently like PTR and MX * Fri Jul 15 2005 Stuart Gathman 0.8.2-3 diff --git a/permerror.txt b/permerror.txt new file mode 100644 index 0000000..fc60aec --- /dev/null +++ b/permerror.txt @@ -0,0 +1,31 @@ +Subject: Critical SPF 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 + +Your spf record has a permanent error. The error was: + + %(perm_error)s + +We will reinterpret your record using "lax" processing heuristics +which may result in your mail being accepted anyway. But you or your +mail administrator need to fix your SPF record as soon as possible. + +We are sending you this message to alert you to the fact that +you have problems with your email configuration. + +If you need further assistance, please do not hesitate to +contact me again. + +Kind regards, + +postmaster@%(receiver)s diff --git a/softfail.txt b/softfail.txt index 263cecf..f4c740a 100644 --- a/softfail.txt +++ b/softfail.txt @@ -1,4 +1,4 @@ -Subject: SPF softfail (POSSIBLE FORGERY) +Subject: SPF %(result)s (POSSIBLE FORGERY) This is an automatically generated Delivery Status Notification.