spf-discuss
[Top] [All Lists]

python client implementation

2003-12-10 00:48:22
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;±¤Ö¤Íµø?¡


<Prev in Thread] Current Thread [Next in Thread>