diff --git a/lib/ansible/runner/lookup_plugins/dig.py b/lib/ansible/runner/lookup_plugins/dig.py new file mode 100644 index 0000000000..a549a4a157 --- /dev/null +++ b/lib/ansible/runner/lookup_plugins/dig.py @@ -0,0 +1,212 @@ +# (c) 2015, Jan-Piet Mens +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +from ansible import utils, errors +import socket +HAVE_DNS=False +try: + import dns.resolver + import dns.reversename + from dns.rdatatype import * + from dns.exception import DNSException + HAVE_DNS=True +except ImportError: + pass + +def make_rdata_dict(rdata): + ''' While the 'dig' lookup plugin supports anything which dnspython supports + out of the box, the following supported_types list describes which + DNS query types we can convert to a dict. + + Note: adding support for RRSIG is hard work. :) + ''' + supported_types = { + A : ['address'], + AAAA : ['address'], + CNAME : ['target'], + DNAME : ['target'], + DLV : ['algorithm', 'digest_type', 'key_tag', 'digest'], + DNSKEY : ['flags', 'algorithm', 'protocol', 'key'], + DS : ['algorithm', 'digest_type', 'key_tag', 'digest'], + HINFO : ['cpu', 'os'], + LOC : ['latitude', 'longitude', 'altitude', 'size', 'horizontal_precision', 'vertical_precision'], + MX : ['preference', 'exchange'], + NAPTR : ['order', 'preference', 'flags', 'service', 'regexp', 'replacement'], + NS : ['target'], + NSEC3PARAM : ['algorithm', 'flags', 'iterations', 'salt'], + PTR : ['target'], + RP : ['mbox', 'txt'], + # RRSIG : ['algorithm', 'labels', 'original_ttl', 'expiration', 'inception', 'signature'], + SOA : ['mname', 'rname', 'serial', 'refresh', 'retry', 'expire', 'minimum'], + SPF : ['strings'], + SRV : ['priority', 'weight', 'port', 'target'], + SSHFP : ['algorithm', 'fp_type', 'fingerprint'], + TLSA : ['usage', 'selector', 'mtype', 'cert'], + TXT : ['strings'], + } + + rd = {} + + if rdata.rdtype in supported_types: + fields = supported_types[rdata.rdtype] + for f in fields: + val = rdata.__getattribute__(f) + + if type(val) == dns.name.Name: + val = dns.name.Name.to_text(val) + + if rdata.rdtype == DLV and f == 'digest': + val = dns.rdata._hexify(rdata.digest).replace(' ', '') + if rdata.rdtype == DS and f == 'digest': + val = dns.rdata._hexify(rdata.digest).replace(' ', '') + if rdata.rdtype == DNSKEY and f == 'key': + val = dns.rdata._base64ify(rdata.key).replace(' ', '') + if rdata.rdtype == NSEC3PARAM and f == 'salt': + val = dns.rdata._hexify(rdata.salt).replace(' ', '') + if rdata.rdtype == SSHFP and f == 'fingerprint': + val = dns.rdata._hexify(rdata.fingerprint).replace(' ', '') + if rdata.rdtype == TLSA and f == 'cert': + val = dns.rdata._hexify(rdata.cert).replace(' ', '') + + + rd[f] = val + + return rd + +# ============================================================== +# dig: Lookup DNS records +# +# -------------------------------------------------------------- + +class LookupModule(object): + + def __init__(self, basedir=None, **kwargs): + self.basedir = basedir + + if HAVE_DNS == False: + raise errors.AnsibleError("Can't LOOKUP(dig): module dns.resolver is not installed") + + def run(self, terms, inject=None, **kwargs): + + ''' + terms contains a string with things to `dig' for. We support the + following formats: + example.com # A record + example.com qtype=A # same + example.com/TXT # specific qtype + example.com qtype=txt # same + 192.168.1.2/PTR # reverse PTR + ^^ shortcut for 2.1.168.192.in-addr.arpa/PTR + example.net/AAAA @nameserver # query specified server + ^^^ can be comma-sep list of names/addresses + + ... flat=0 # returns a dict; default is 1 == string + ''' + terms = terms.split() + + # Create Resolver object so that we can set NS if necessary + myres = dns.resolver.Resolver() + edns_size = 4096 + myres.use_edns(0, ednsflags=dns.flags.DO, payload=edns_size) + + domain = None + qtype = 'A' + flat = True + + for t in terms: + if t.startswith('@'): # e.g. "@10.0.1.2,192.168.1.1" is ok. + nsset = t[1:].split(',') + nameservers = [] + for ns in nsset: + # Check if we have a valid IP address. If so, use that, otherwise + # try to resolve name to address using system's resolver. If that + # fails we bail out. + try: + socket.inet_aton(ns) + nameservers.append(ns) + except: + try: + nsaddr = dns.resolver.query(ns)[0].address + nameservers.append(nsaddr) + except Exception, e: + raise errors.AnsibleError("dns lookup NS: ", str(e)) + myres.nameservers = nameservers + continue + if '=' in t: + try: + opt, arg = t.split('=') + except: + pass + + if opt == 'qtype': + qtype = arg.upper() + elif opt == 'flat': + flat = int(arg) + + continue + + if '/' in t: + try: + domain, qtype = t.split('/') + except: + domain = t + else: + domain = t + + # print "--- domain = {0} qtype={1}".format(domain, qtype) + + ret = [] + + if qtype.upper() == 'PTR': + try: + n = dns.reversename.from_address(domain) + domain = n.to_text() + except dns.exception.SyntaxError: + pass + except Exception, e: + raise errors.AnsibleError("dns.reversename unhandled exception", str(e)) + + try: + answers = myres.query(domain, qtype) + for rdata in answers: + s = rdata.to_text() + if qtype.upper() == 'TXT': + s = s[1:-1] # Strip outside quotes on TXT rdata + + if flat: + ret.append(s) + else: + try: + rd = make_rdata_dict(rdata) + rd['owner'] = answers.canonical_name.to_text() + rd['type'] = dns.rdatatype.to_text(rdata.rdtype) + rd['ttl'] = answers.rrset.ttl + + ret.append(rd) + except Exception, e: + ret.append(str(e)) + + except dns.resolver.NXDOMAIN: + ret.append('NXDOMAIN') + except dns.resolver.NoAnswer: + ret.append("") + except dns.resolver.Timeout: + ret.append('') + except dns.exception.DNSException, e: + raise errors.AnsibleError("dns.resolver unhandled exception", e) + + return ret