From a3618f619e46a2da4e0a7891bc62afe6b0cd86a3 Mon Sep 17 00:00:00 2001
From: Jan-Piet Mens <jpmens@gmail.com>
Date: Sat, 21 Feb 2015 12:18:33 +0100
Subject: [PATCH] New lookup-plugin: `dig' for performing DNS lookups add
 support for argument qtype=XXX remove spurious print add dict returns typo
 Add dict returns for all major DNS types Finish adding more types. For now.
 Until somebody complains. I forgot to add NAPTR; here it is.

---
 lib/ansible/runner/lookup_plugins/dig.py | 212 +++++++++++++++++++++++
 1 file changed, 212 insertions(+)
 create mode 100644 lib/ansible/runner/lookup_plugins/dig.py

diff --git a/lib/ansible/runner/lookup_plugins/dig.py b/lib/ansible/runner/lookup_plugins/dig.py
new file mode 100644
index 00000000000..a549a4a1573
--- /dev/null
+++ b/lib/ansible/runner/lookup_plugins/dig.py
@@ -0,0 +1,212 @@
+# (c) 2015, Jan-Piet Mens <jpmens(at)gmail.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+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