diff --git a/TODO b/TODO index d2cca47ad369ad953c0ed7539f18d854da5b5d4c..22b3b40ad1def0dc136470c03a31129e679744c3 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,14 @@ +Generate DSNs according to RFC 3464 + +Parse incoming 3464 DSNs for "Action: failed" to recognize delayed +failures. This works regardless of Subject. + +Get temperror policy from access file. + When training with spam, REJECT after data so that mistakenly blacklisted senders at least get an error. -Reporting explanation for failure should show source of sender +Reporting explanation for failure should show source if sender provided explanation. Reports PROBATION even when rejecting message (works, but confusing in log). diff --git a/bms.py b/bms.py index fb392d24f6ad6b3c4ff0d9ebce01161326506cfb..accad5245af7cee76f9360f5fc37fc9ebaab56ee 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.68 2006/10/04 03:46:01 customdesigned +# Fix defaults. +# # Revision 1.67 2006/10/01 01:44:06 customdesigned # case_sensitive_localpart option, more delayed bounce heuristics, # optional smart_alias section. @@ -1278,13 +1281,19 @@ class bmsMilter(Milter.Milter): # if confirmed by finding our signed Message-ID, # original sender (encoded in Message-ID) is blacklisted + elif lname == 'from' and val.lower().startswith('postmaster@'): + # Yes, if From header comes last, this might not help much. + # But this is a heuristic - if MTAs would send proper DSNs in + # the first place, none of this would be needed. + self.is_bounce = True + # check for invalid message id - if lname == 'message-id' and len(val) < 4: + elif lname == 'message-id' and len(val) < 4: self.log('REJECT: %s: %s' % (name,val)) return Milter.REJECT # check for common bulk mailers - if lname == 'x-mailer': + elif lname == 'x-mailer': mailer = val.lower() if mailer in ('direct email','calypso','mail bomber') \ or mailer.find('optin') >= 0: diff --git a/milter.spec b/milter.spec index 96af07dcac92e16dd4f0e66e779a6f9a2a4eff73..c2b921762e5f6013b71fddf9a8b23fcc1f7a151b 100644 --- a/milter.spec +++ b/milter.spec @@ -92,7 +92,7 @@ cat >$RPM_BUILD_ROOT/etc/cron.daily/milter <<'EOF' find /var/log/milter/save -mtime +7 | xargs $R rm # work around memory leak -/etc/init.d/milter restart +/etc/init.d/milter condrestart EOF chmod a+x $RPM_BUILD_ROOT/etc/cron.daily/milter diff --git a/spf.py b/spf.py index 20d87ae39982de28f25916888516b117ec408de7..3fb15db402573accf1e6cbb5126eb12194cbdaec 100755 --- a/spf.py +++ b/spf.py @@ -47,124 +47,8 @@ For news, bugfixes, etc. visit the home page for this implementation at # Development taken over by Stuart Gathman <stuart@bmsi.com>. # # $Log$ -# Revision 1.105 2006/10/07 22:06:28 kitterma -# Pass strict status to DNSLookup - will be needed for TCP failover. -# -# Revision 1.104 2006/10/07 21:59:37 customdesigned -# long/empty label tests and fix. -# -# Revision 1.103 2006/10/07 18:16:20 customdesigned -# Add tests for and fix RE_TOPLAB. -# -# Revision 1.102 2006/10/05 13:57:15 customdesigned -# Remove isSPF and make missing space after version tag a warning. -# -# Revision 1.101 2006/10/05 13:39:11 customdesigned -# SPF version tag is case insensitive. -# -# Revision 1.100 2006/10/04 02:14:04 customdesigned -# Remove incomplete saving of result. Was messing up bmsmilter. Would -# be useful if done consistently - and disabled when passing spf= to check(). -# -# Revision 1.99 2006/10/03 21:00:26 customdesigned -# Correct fat fingered merge error. -# -# Revision 1.98 2006/10/03 17:35:45 customdesigned -# Provide python inet_ntop and inet_pton when not socket.has_ipv6 -# -# Revision 1.97 2006/10/02 17:10:13 customdesigned -# Test and fix for uppercase macros. -# -# Revision 1.96 2006/10/01 01:27:54 customdesigned -# Switch to pymilter lax processing convention: -# Always return strict result, extended result in q.perm_error.ext -# -# Revision 1.95 2006/09/30 22:53:44 customdesigned -# Fix getp to obey SHOULDs in RFC. -# -# Revision 1.94 2006/09/30 22:23:25 customdesigned -# p macro tests and fixes -# -# Revision 1.93 2006/09/30 20:57:06 customdesigned -# Remove generator expression for compatibility with python2.3. -# -# Revision 1.92 2006/09/30 19:52:52 customdesigned -# Removed redundant flag and unneeded global. -# -# Revision 1.91 2006/09/30 19:37:49 customdesigned -# Missing L -# -# Revision 1.90 2006/09/30 19:29:58 customdesigned -# pydns returns AAAA RR as binary string -# -# Revision 1.89 2006/09/29 20:23:11 customdesigned -# Optimize cidrmatch -# -# Revision 1.88 2006/09/29 19:44:10 customdesigned -# Fix ptr with ip6 for harsh mode. -# -# Revision 1.87 2006/09/29 19:26:53 customdesigned -# Add PTR tests and fix ip6 ptr -# -# Revision 1.86 2006/09/29 17:55:22 customdesigned -# Pass ip6 tests -# -# Revision 1.85 2006/09/29 15:58:02 customdesigned -# Pass self test on non IP6 python. -# PTR accepts no cidr. -# -# Revision 1.83 2006/09/27 18:09:40 kitterma -# Converted spf.check to return pre-MARID result codes for drop in -# compatibility with pySPF 1.6/1.7. Added new procedure, spf.check2 to -# return RFC4408 results in a two part answer (result, explanation). -# This is the external API for pySPF 2.0. No longer any need to branch -# for 'classic' and RFC compliant pySPF libraries. -# -# Revision 1.82 2006/09/27 18:02:21 kitterma -# Converted max MX limit to ambiguity warning for validator. -# -# Revision 1.81 2006/09/27 17:38:14 kitterma -# Updated initial comments and moved pre-1.7 changes to spf_changelog. -# -# Revision 1.80 2006/09/27 17:33:53 kitterma -# Fixed indentation error in check0. -# -# Revision 1.79 2006/09/26 18:05:44 kitterma -# Removed unused receiver policy definitions. -# -# Revision 1.78 2006/09/26 16:15:50 kitterma -# added additional IP4 and CIDR validation tests - no code changes. -# -# Revision 1.77 2006/09/25 19:42:32 customdesigned -# Fix unknown macro sentinel -# -# Revision 1.76 2006/09/25 19:10:40 customdesigned -# Fix exp= error and add another failing test. -# -# Revision 1.75 2006/09/25 02:02:30 kitterma -# Fixed redirect-cancels-exp test suite failure. -# -# Revision 1.74 2006/09/24 04:04:08 kitterma -# Implemented check for macro 'c' - Macro unimplimented. -# -# Revision 1.73 2006/09/24 02:08:35 kitterma -# Fixed invalid-macro-char test failure. -# -# Revision 1.72 2006/09/23 05:45:52 kitterma -# Fixed domain-name-truncation test failure -# -# Revision 1.71 2006/09/22 01:02:54 kitterma -# pySPF correction for nolocalpart in rfc4408-tests.yml failed, 4.3/2. -# Added comments to testspf.py on where to get YAML. -# -# Revision 1.70 2006/09/18 02:13:27 kitterma -# Worked through a large number of pylint issues - all 4 spaces, not a mix -# of 4 spaces, 2 spaces, and tabs. Caught a few minor errors in the process. -# All built in tests still pass. -# -# Revision 1.69 2006/09/17 18:44:25 kitterma -# Fixed validation mode only crash bug when rDNS check had no PTR record -# +# Revision 1.107 2006/11/04 21:58:12 customdesigned +# Prevent cache poisoning by bogus additional RRs in PTR DNS response. # # See spf_changelog.txt for earlier changes. @@ -303,8 +187,9 @@ MAX_CNAME = 10 # analogous interpretation to MAX_PTR MAX_RECURSION = 20 ALL_MECHANISMS = ('a', 'mx', 'ptr', 'exists', 'include', 'ip4', 'ip6', 'all') -COMMON_MISTAKES = { 'prt': 'ptr', 'ip': 'ip4', 'ipv4': 'ip4', 'ipv6': 'ip6' } - +COMMON_MISTAKES = { + 'prt': 'ptr', 'ip': 'ip4', 'ipv4': 'ip4', 'ipv6': 'ip6', 'all.': 'all' +} #If harsh processing, for the validator, is invoked, warn if results #likely deviate from the publishers intention. @@ -1163,6 +1048,22 @@ class query(object): """Get a list of domain names for an IP address.""" return self.dns('%s.%s.arpa'%(reverse_dots(i),self.v), 'PTR') + # We have to be careful which additional DNS RRs we cache. For + # instance, PTR records are controlled by the connecting IP, and they + # could poison our local cache with bogus A and MX records. + + SAFE2CACHE = { + ('MX','A'): None, + ('MX','MX'): None, + ('CNAME','A'): None, + ('CNAME','CNAME'): None, + ('A','A'): None, + ('AAAA','AAAA'): None, + ('PTR','PTR'): None, + ('TXT','TXT'): None, + ('SPF','SPF'): None + } + def dns(self, name, qtype, cnames=None): """DNS query. @@ -1179,11 +1080,14 @@ class query(object): """ result = self.cache.get( (name, qtype) ) cname = None + if not result: + safe2cache = query.SAFE2CACHE for k, v in DNSLookup(name, qtype, self.strict): if k == (name, 'CNAME'): cname = v - self.cache.setdefault(k, []).append(v) + if (qtype,k[1]) in safe2cache: + self.cache.setdefault(k, []).append(v) result = self.cache.get( (name, qtype), []) if not result and cname: if not cnames: