From david at porkrind.org Mon Mar 24 20:14:25 2014 From: david at porkrind.org (David Caldwell) Date: Mon, 24 Mar 2014 17:14:25 -0700 Subject: [Pymilter] [PATCH] Accept DNS names in iniplist Message-ID: <5330CA61.8080809@porkrind.org> The iniplist() function takes an ip address and reports whether it is in a list or not. This patch lets you use a DNS name in the list and resolves it when looking for the IP address. This lets settings like "trusted_relay" in the spf milter use a DNS name which is stable in the long term, where the IP address is not. -David -------------- next part -------------- Index: Milter/utils.py =================================================================== RCS file: /cvsroot/pymilter/pymilter/Milter/utils.py,v retrieving revision 1.13 diff -p -u -d -r1.13 utils.py --- Milter/utils.py 12 Mar 2013 01:46:08 -0000 1.13 +++ Milter/utils.py 24 Mar 2014 23:45:30 -0000 @@ -10,6 +10,7 @@ from fnmatch import fnmatchcase from email.Header import decode_header #import email.Utils import rfc822 +from Milter.dns import DNSLookup PAT_IP4 = r'\.'.join([r'(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])']*4) ip4re = re.compile(PAT_IP4+'$') @@ -26,6 +27,7 @@ ip6re = re.compile( '(?: 'ls32': r'(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|%s)'%PAT_IP4, 'hex4': r'[0-9a-f]{1,4}' }, re.IGNORECASE) +dnsre = re.compile(r'^[a-z][-a-z\d.]+$', re.IGNORECASE) # from spf.py def addr2bin(s): @@ -67,6 +69,10 @@ def iniplist(ipaddr,iplist): True >>> iniplist('192.168.0.45',['192.168.0.*']) True + >>> iniplist('4.2.2.2',['b.resolvers.Level3.net']) + True + >>> iniplist('4.2.2.2',['nothing.example.com']) + False >>> iniplist('2001:610:779:0:223:6cff:fe9a:9cf3',['127.0.0.1','172.20.1.0/24','2001:610:779::/48']) True >>> iniplist('2G01:610:779:0:223:6cff:fe9a:9cf3',['127.0.0.1','172.20.1.0/24','2001:610:779::/48']) @@ -96,6 +102,11 @@ def iniplist(ipaddr,iplist): n = 128 if cidr(bin2long6(inet_pton(p[0])),n,MASK6) == cidr(ipnum,n,MASK6): return True + elif dnsre.match(p[0]): + dns_resp = DNSLookup(p[0], 'A') + for r in dns_resp: + if addr2bin(r[1]) == ipnum: + return True elif fnmatchcase(ipaddr,pat): return True return False -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/pkcs7-signature Size: 4219 bytes Desc: S/MIME Cryptographic Signature URL: From stuart at bmsi.com Tue Mar 25 12:31:37 2014 From: stuart at bmsi.com (Stuart D Gathman) Date: Tue, 25 Mar 2014 12:31:37 -0400 (EDT) Subject: [Pymilter] [PATCH] Accept DNS names in iniplist In-Reply-To: <5330CA61.8080809@porkrind.org> References: <5330CA61.8080809@porkrind.org> Message-ID: On Mar 24, David Caldwell transmitted in part: > The iniplist() function takes an ip address and reports whether it is in > a list or not. This patch lets you use a DNS name in the list and > resolves it when looking for the IP address. > > This lets settings like "trusted_relay" in the spf milter use a DNS name > which is stable in the long term, where the IP address is not. This is a good idea. The patch does not handle cidr, however, and I'm pretty sure from eyeballing it that it doesn't work for IP6 either. I'll polish it up and add it, however. From stuart at bmsi.com Tue Mar 25 13:53:26 2014 From: stuart at bmsi.com (Stuart D Gathman) Date: Tue, 25 Mar 2014 13:53:26 -0400 Subject: [Pymilter] [PATCH] Accept DNS names in iniplist In-Reply-To: <5330CA61.8080809@porkrind.org> References: <5330CA61.8080809@porkrind.org> Message-ID: <5331C296.30805@bmsi.com> On 03/24/2014 08:14 PM, David Caldwell wrote: > The iniplist() function takes an ip address and reports whether it is in > a list or not. This patch lets you use a DNS name in the list and > resolves it when looking for the IP address. > > This lets settings like "trusted_relay" in the spf milter use a DNS name > which is stable in the long term, where the IP address is not. I've switched to using the ipaddr module in pyspf. I could have expand_iplist() return a list of IPNetwork from that module. The IPNetwork ctor automatically handles converting IP6 addresses from either 16 byte binary or string format, and collapse_address_list() will combine adjacent networks. A new in_address_list would check an ip against the output of expand_iplist(). This would prevent any new dependencies when using the old iniplist() function. What do you think? From david at porkrind.org Tue Mar 25 14:36:30 2014 From: david at porkrind.org (David Caldwell) Date: Tue, 25 Mar 2014 11:36:30 -0700 Subject: [Pymilter] [PATCH] Accept DNS names in iniplist In-Reply-To: <5331C296.30805@bmsi.com> References: <5330CA61.8080809@porkrind.org> <5331C296.30805@bmsi.com> Message-ID: <5331CCAE.1020302@porkrind.org> On 3/25/14, 10:53 AM, Stuart D Gathman wrote: > On 03/24/2014 08:14 PM, David Caldwell wrote: >> The iniplist() function takes an ip address and reports whether it is in >> a list or not. This patch lets you use a DNS name in the list and >> resolves it when looking for the IP address. >> >> This lets settings like "trusted_relay" in the spf milter use a DNS name >> which is stable in the long term, where the IP address is not. > > This is a good idea. The patch does not handle cidr, however, and > I'm pretty sure from eyeballing it that it doesn't work for IP6 either. > I'll polish it up and add it, however. I've never seen the cidr slash notation used with DNS names, so I explicitly chose not to support that. You're probably right about IPv6. I don't really have a way to test that and just hoped for the best. :-) Though I guess you'd need to look up both A and AAAA records, which I didn't do. > I've switched to using the ipaddr module in pyspf. I could have > expand_iplist() return a list of IPNetwork from that module. The > IPNetwork ctor automatically handles converting IP6 addresses from > either 16 byte binary or string format, and collapse_address_list() will > combine adjacent networks. A new in_address_list would check an ip > against the output of expand_iplist(). This would prevent any new > dependencies when using the old iniplist() function. What do you think? I don't have a huge opinion about how it should be in the code, just as long as I can specify stuff in the config file as DNS names. I trust you are more familiar with the code (and python itself) than I am, and so I'll defer to your judgement about the specifics. :-) -David -------------- next part -------------- A non-text attachment was scrubbed... Name: smime.p7s Type: application/pkcs7-signature Size: 4219 bytes Desc: S/MIME Cryptographic Signature URL: From stuart at bmsi.com Wed Mar 26 10:20:10 2014 From: stuart at bmsi.com (Stuart D Gathman) Date: Wed, 26 Mar 2014 10:20:10 -0400 Subject: [Pymilter] [PATCH] Accept DNS names in iniplist In-Reply-To: <5331CCAE.1020302@porkrind.org> References: <5330CA61.8080809@porkrind.org> <5331C296.30805@bmsi.com> <5331CCAE.1020302@porkrind.org> Message-ID: <5332E21A.9060604@bmsi.com> On 03/25/2014 02:36 PM, David Caldwell wrote: > >> This is a good idea. The patch does not handle cidr, however, and >> I'm pretty sure from eyeballing it that it doesn't work for IP6 either. >> I'll polish it up and add it, however. > I've never seen the cidr slash notation used with DNS names, so I > explicitly chose not to support that. You're probably right about IPv6. > I don't really have a way to test that and just hoped for the best. :-) > Though I guess you'd need to look up both A and AAAA records, which I > didn't do. Hmm, if expanding within iniplist(), like you did, it can lookup only A records when ip is IP4 and only AAAA records when ip is IP6. I can call recursively to handle the CIDR rather than duplicate the code. From stuart at bmsi.com Thu Mar 27 22:33:43 2014 From: stuart at bmsi.com (Stuart D Gathman) Date: Thu, 27 Mar 2014 22:33:43 -0400 (EDT) Subject: [Pymilter] [PATCH] Accept DNS names in iniplist In-Reply-To: <5332E21A.9060604@bmsi.com> References: <5330CA61.8080809@porkrind.org> <5331C296.30805@bmsi.com> <5331CCAE.1020302@porkrind.org> <5332E21A.9060604@bmsi.com> Message-ID: On Mar 26, Stuart D Gathman transmitted in part: > On 03/25/2014 02:36 PM, David Caldwell wrote: >> >>> This is a good idea. The patch does not handle cidr, however, and >>> I'm pretty sure from eyeballing it that it doesn't work for IP6 either. >>> I'll polish it up and add it, however. Here is my iteration. I kept dependencies to a minimum, and check the list recursively. It would likely be faster to repeat the cidrmatch code and use DNSLookup like you did (setting arec = 'A' or 'AAAA' depending on whether ipaddr is IP6), but this way has minimal code and dependencies. It handles IP6 and CIDR. Index: utils.py =================================================================== RCS file: /cvsroot/pymilter/pymilter/Milter/utils.py,v retrieving revision 1.13 diff -u -r1.13 utils.py --- utils.py 12 Mar 2013 01:46:08 -0000 1.13 +++ utils.py 28 Mar 2014 02:28:32 -0000 @@ -11,6 +11,7 @@ #import email.Utils import rfc822 +dnsre = re.compile(r'^[a-z][-a-z\d.]+$', re.IGNORECASE) PAT_IP4 = r'\.'.join([r'(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])']*4) ip4re = re.compile(PAT_IP4+'$') ip6re = re.compile( '(?:%(hex4)s:){6}%(ls32)s$' @@ -67,6 +68,12 @@ True >>> iniplist('192.168.0.45',['192.168.0.*']) True + >>> iniplist('4.2.2.2',['b.resolvers.Level3.net']) + True + >>> iniplist('2607:f8b0:4004:801::',['google.com/64']) + True + >>> iniplist('4.2.2.2',['nothing.example.com']) + False >>> iniplist('2001:610:779:0:223:6cff:fe9a:9cf3',['127.0.0.1','172.20.1.0/24','2001:610:779::/48']) True >>> iniplist('2G01:610:779:0:223:6cff:fe9a:9cf3',['127.0.0.1','172.20.1.0/24','2001:610:779::/48']) @@ -75,8 +82,10 @@ ValueError: Invalid ip syntax:2G01:610:779:0:223:6cff:fe9a:9cf3 """ if ip4re.match(ipaddr): + fam = socket.AF_INET ipnum = addr2bin(ipaddr) elif ip6re.match(ipaddr): + fam = socket.AF_INET6 ipnum = bin2long6(inet_pton(ipaddr)) else: raise ValueError('Invalid ip syntax:'+ipaddr) @@ -96,6 +105,13 @@ n = 128 if cidr(bin2long6(inet_pton(p[0])),n,MASK6) == cidr(ipnum,n,MASK6): return True + elif dnsre.match(p[0]): + try: + sfx = '/'.join(['']+p[1:]) + addrlist = [r[4][0]+sfx for r in socket.getaddrinfo(p[0],25,fam)] + if iniplist(ipaddr,addrlist): + return True + except socket.gaierror: pass elif fnmatchcase(ipaddr,pat): return True return False From stuart at bmsi.com Thu Mar 27 22:40:59 2014 From: stuart at bmsi.com (Stuart D Gathman) Date: Thu, 27 Mar 2014 22:40:59 -0400 Subject: [Pymilter] [PATCH] Accept DNS names in iniplist In-Reply-To: References: <5330CA61.8080809@porkrind.org> <5331C296.30805@bmsi.com> <5331CCAE.1020302@porkrind.org> <5332E21A.9060604@bmsi.com> Message-ID: <5334E13B.2050400@bmsi.com> On 03/27/2014 10:33 PM, Stuart D Gathman wrote: > > @@ -96,6 +105,13 @@ > n = 128 > if cidr(bin2long6(inet_pton(p[0])),n,MASK6) == > cidr(ipnum,n,MASK6): > return True > + elif dnsre.match(p[0]): > + try: > + sfx = '/'.join(['']+p[1:]) > + addrlist = [r[4][0]+sfx for r in > socket.getaddrinfo(p[0],25,fam)] > + if iniplist(ipaddr,addrlist): > + return True > + except socket.gaierror: pass > elif fnmatchcase(ipaddr,pat): > return True > return False Sorry - forgot to run tabnanny.