From 6048fe6e8cfdc312fa2d3c091ec372f593ea9993 Mon Sep 17 00:00:00 2001 From: Stuart Gathman <stuart@gathman.org> Date: Mon, 24 Sep 2007 20:13:26 +0000 Subject: [PATCH] Remove explicit spf dependency. --- Milter/dns.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++ Milter/dsn.py | 14 +++++++----- 2 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 Milter/dns.py diff --git a/Milter/dns.py b/Milter/dns.py new file mode 100644 index 0000000..21941c2 --- /dev/null +++ b/Milter/dns.py @@ -0,0 +1,62 @@ +# provide a higher level interface to pydns + +import DNS +from DNS import DNSError + +MAX_CNAME = 10 + +def DNSLookup(name, qtype): + try: + req = DNS.DnsRequest(name, qtype=qtype) + resp = req.req() + #resp.show() + # key k: ('wayforward.net', 'A'), value v + # FIXME: pydns returns AAAA RR as 16 byte binary string, but + # A RR as dotted quad. For consistency, this driver should + # return both as binary string. + return [((a['name'], a['typename']), a['data']) for a in resp.answers] + except IOError, x: + raise DNSError, str(x) + +class Session(object): + """A Session object has a simple cache with no TTL that is valid + for a single "session", for example an SMTP conversation.""" + def __init__(self): + self.cache = {} + + def dns(self, name, qtype, cnames=None): + """DNS query. + + If the result is in cache, return that. Otherwise pull the + result from DNS, and cache ALL answers, so additional info + is available for further queries later. + + CNAMEs are followed. + + If there is no data, [] is returned. + + pre: qtype in ['A', 'AAAA', 'MX', 'PTR', 'TXT', 'SPF'] + post: isinstance(__return__, types.ListType) + """ + 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 + 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: + cnames = {} + elif len(cnames) >= MAX_CNAME: + #return result # if too many == NX_DOMAIN + raise DNSError('Length of CNAME chain exceeds %d' % MAX_CNAME) + cnames[name] = cname + if cname in cnames: + raise DNSError, 'CNAME loop' + result = self.dns(cname, qtype, cnames=cnames) + return result diff --git a/Milter/dsn.py b/Milter/dsn.py index efdeef9..259fb13 100644 --- a/Milter/dsn.py +++ b/Milter/dsn.py @@ -5,6 +5,9 @@ # Send DSNs, do call back verification, # and generate DSN messages from a template # $Log$ +# Revision 1.14 2007/03/03 18:19:40 customdesigned +# Handle DNS error sending DSN. +# # Revision 1.13 2007/01/04 18:01:11 customdesigned # Do plain CBV when template missing. # @@ -19,22 +22,22 @@ # import smtplib -import spf import socket from email.Message import Message import Milter import time +import dns -def send_dsn(mailfrom,receiver,msg=None,timeout=600): +def send_dsn(mailfrom,receiver,msg=None,timeout=600,session=None): """Send DSN. If msg is None, do callback verification. Mailfrom is original sender we are sending DSN or CBV to. Receiver is the MTA sending the DSN. Return None for success or (code,msg) for failure.""" user,domain = mailfrom.split('@') + if not session: session = dns.Session() try: - q = spf.query(None,None,None) - mxlist = q.dns(domain,'MX') - except spf.TempError: + mxlist = session.dns(domain,'MX') + except dns.DNSError: return (450,'DNS Timeout: %s MX'%domain) # temp error if not mxlist: mxlist = (0,domain), # fallback to A record when no MX @@ -124,6 +127,7 @@ def create_msg(q,rcptlist,origmsg=None,template=None): return msg if __name__ == '__main__': + import spf q = spf.query('192.168.9.50', 'SRS0=pmeHL=RH==stuart@example.com', 'red.example.com',receiver='mail.example.com') -- GitLab