On Wed, Dec 10, 2003 at 01:50:43AM -0500, Terence Way wrote:
|
| I've written a small (190 line) Python program that handles SPF, except
| PTRs and macros, neither of which I understand all that well.
|
That's a very good start, nice and tidy.
It needs to be fleshed out with include and redirects.
The following might help with PTRs. I don't really know Python and I
don't have pydns so it'll probably need a bit of fixing before it
actually runs.
ESR, if you're listening, this would be a good time to jump in :)
--- spf.py.1 2003-12-10 01:47:44.000000000 -0500
+++ spf.py 2003-12-10 02:46:55.000000000 -0500
@@ -91,21 +91,17 @@
if result[0]:
return result
- if m[0] == '+':
- result = (True, 200, 'sender validated via SPF')
+ if m[0] == '-':
m = m[1:]
- elif m[0] == '-':
- result = (False, 500, 'access denied')
+ result = (False, 550, 'access denied')
+ elif m[0] == '?' or m[0] == '~':
m = m[1:]
- elif m[0] == '~':
- result = (False, 200,
- 'SPF flagging your email as spam')
+ result = (False, 250, 'SPF unknown')
+ elif m[0] == '+':
m = m[1:]
- elif m[0] == '?':
- result = (False, 400, 'Unknown SPF behavior')
- m = m[1:]
- else:
- result = (True, 200, 'sender validated via SPF')
+ result = (True, 250, 'sender validated via SPF')
+ else: # assume + by default
+ result = (True, 250, 'sender validated via SPF')
if m == 'all':
return result
@@ -117,15 +113,22 @@
m = parse_mechanism(m, domainname)
if m[0] == 'a':
- if match(ipaddr, get_a(m[1]), m[2]):
+ if cidrmatch(ipaddr, get_a(m[1]), m[2]):
return result
elif m[0] == 'mx':
- if match(ipaddr, get_mx(m[1]), m[2]):
+ if cidrmatch(ipaddr, get_mx(m[1]), m[2]):
return result
elif m[0] == 'ip4' and m[1] != domainname:
- if match(ipaddr, [m[1]], m[2]):
+ if cidrmatch(ipaddr, [m[1]], m[2]):
+ return result
+ elif m[0] == 'ptr':
+ if domainmatch(validated_ptrdnames(ipaddr), m[1]):
return result
+ # unknown mechanisms cause immediate unknown abort result.
+ result = (False, 250, 'SPF unknown')
+ return result;
+
def get_a(domainname):
"""Get a list of IP addresses for a domainname."""
req = DNS.DnsRequest(domainname)
@@ -133,6 +136,19 @@
return [a['data'] for a in resp.answers]
+def get_ptr(ipaddr):
+ """Get a list of domain names for an IP address."""
+ req = DNS.DnsRequest(myreverse(ipaddr) + ".in-addr.arpa", qtype='ptr')
+ resp = req.req()
+
+ return [a['data'] for a in resp.answers]
+
+def myreverse(thingy):
+ """ why couldn't python's reverse() just return a value just like any
other function? """
+ mythingy = thingy
+ mythingy.reverse()
+ return mythingy
+
def get_mx(domainname):
"""Get a list of IP addresses for all MX exchanges for a domainname."""
req = DNS.DnsRequest(domainname, qtype='mx')
@@ -144,15 +160,50 @@
return result
-def match(ipaddr, ipaddrs, cidr_length = 32):
+def domainmatch(ptrdnames, domainsuffix):
+ """grep for a given domain suffix against a list of validated PTR
domain names.
+
+ Examples:
+ >>> domainmatch(['foo.com'], 'foo.com')
+ True
+ >>> domainmatch(['moo.foo.com',] 'foo.com')
+ True
+ >>> domainmatch(['moo.bar.com'], 'foo.com')
+ False
+
+ """
+ for ptrdname in ptrdnames:
+ if ptrdname.lower() == domainsuffix.lower(): return True
+ if ptrdname.lower.endswith("." + domainsuffix.lower()): return True
+
+ return False
+
+def validated_ptrdnames(ipaddr):
+ """ Figure out the validated PTR domain names for a given IP address.
+
+ @ptrdnames = ptr_lookup(ipaddr);
+ @validated = grep { ipaddr in a_lookup($_) } @ptrdnames;
+
+ """
+
+ ptrdnames = get_ptr(ipaddr)
+ validated = []
+ for ptrdname in ptrdnames:
+ ips = get_a(ptrdname)
+ if ipaddr in ips:
+ validated += ptrdname
+ return validated
+
+
+def cidrmatch(ipaddr, ipaddrs, cidr_length = 32):
"""Match an IP address against a list of other IP addresses.
Examples:
- >>> match('192.168.0.45', ['192.168.0.44', '192.168.0.45'])
+ >>> cidrmatch('192.168.0.45', ['192.168.0.44', '192.168.0.45'])
True
- >>> match('192.168.0.43', ['192.168.0.44', '192.168.0.45'])
+ >>> cidrmatch('192.168.0.43', ['192.168.0.44', '192.168.0.45'])
False
- >>> match('192.168.0.43', ['192.168.0.44', '192.168.0.45'], 24)
+ >>> cidrmatch('192.168.0.43', ['192.168.0.44', '192.168.0.45'], 24)
True
"""
c = cidr(ipaddr, cidr_length)
-------
Sender Permitted From: http://spf.pobox.com/
Archives at http://archives.listbox.com/spf-discuss/current/
Latest draft at http://spf.pobox.com/draft-mengwong-spf-02.9.txt
To unsubscribe, change your address, or temporarily deactivate your
subscription,
please go to
http://v2.listbox.com/member/?listname(_at_)©#«Mo\¯HÝÜîU;±¤Ö¤Íµø?¡