def __load_from_dns(repo_url: str) -> Dict[str, str]: repo_url = repo_url if repo_url is not None else "repomd.example.com" KEYS = ['alg', 'hash', 'ts', 'val'] ctx = unbound.ub_ctx() ctx.config("/vagrant/unbound/libunbound.conf") ctx.add_ta_file("/vagrant/root-server/root.zone.signed") status, result = ctx.resolve(repo_url, unbound.RR_TYPE_TXT, unbound.RR_CLASS_IN) if status != 0: print("error communicating with DNS server") else: data = result.data.as_raw_data() structured_result = {} for d in data: key_val = d.decode('ascii') # print(key_val) key, val = key_val.split('=') print(key, val) for k in KEYS: if key.endswith(k): structured_result[k] = val print(structured_result) return structured_result
def test_ns_connectivity(ip, port, domain): # NS connectivity is first tried with TCP (in test_connectivity). # If that fails, maybe the NS is not doing TCP. As a last resort # (expensive) we initiate an unbound context that will ask the NS a # question he can't refuse. def ub_callback(data, status, result): if status != 0: data['result'] = False elif result.rcode == 2: # SERVFAIL data['result'] = False else: data['result'] = True data['done'] = True ctx = ub_ctx() # XXX: Remove for now; inconsistency with applying settings on celery. # ctx.set_async(True) ctx.set_fwd(ip) cb_data = dict(done=False) try: retval, async_id = ctx.resolve_async( domain, cb_data, ub_callback, RR_TYPE_NS, RR_CLASS_IN) while retval == 0 and not cb_data['done']: time.sleep(0.1) retval = ctx.process() except SoftTimeLimitExceeded: if async_id: ctx.cancel(async_id) raise return cb_data['result']
def _cache_miss(input_key): # type: (KeyInfo) -> Validity """ In case the key was not found in the cache, create an Unbound context and contact the DNS system """ try: import unbound except ImportError as e: msg = _("Configuration option 'gpgkey_dns_verification' requires " "python3-unbound ({})".format(e)) raise dnf.exceptions.Error(msg) ctx = unbound.ub_ctx() if ctx.set_option("verbosity:", "0") != 0: logger.debug("Unbound context: Failed to set verbosity") if ctx.set_option("qname-minimisation:", "yes") != 0: logger.debug("Unbound context: Failed to set qname minimisation") if ctx.resolvconf() != 0: logger.debug("Unbound context: Failed to read resolv.conf") if ctx.add_ta_file("/var/lib/unbound/root.key") != 0: logger.debug("Unbound context: Failed to add trust anchor file") status, result = ctx.resolve(email2location(input_key.email), RR_TYPE_OPENPGPKEY, unbound.RR_CLASS_IN) if status != 0: logger.debug("Communication with DNS servers failed") return Validity.ERROR if result.bogus: logger.debug("DNSSEC signatures are wrong ({})".format( result.why_bogus)) return Validity.BOGUS_RESULT if not result.secure: logger.debug("Result is not secured with DNSSEC") return Validity.RESULT_NOT_SECURE if result.nxdomain or (result.rcode == unbound.RCODE_NOERROR and not result.havedata): logger.debug("Non-existence of this record was proven by DNSSEC") return Validity.PROVEN_NONEXISTENCE if not result.havedata: # TODO: This is weird result, but there is no way to perform validation, so just return # an error # Should handle only SERVFAIL, REFUSED and similar rcodes logger.debug("Unknown error in DNS communication: {}".format( result.rcode_str)) return Validity.ERROR else: data = result.data.as_raw_data()[0] dns_data_b64 = base64.b64encode(data) if dns_data_b64 == input_key.key: return Validity.VALID else: # In case it is different, print the keys for further examination in debug mode logger.debug("Key from DNS: {}".format(dns_data_b64)) logger.debug("Input key : {}".format(input_key.key)) return Validity.REVOKED
def __init__(self, task_queue, ta_file): self.task_queue = task_queue threading.Thread.__init__(self) self.resolver = ub_ctx() #self.resolver.resolvconf("/etc/resolv.conf") #self.resolver.set_fwd("127.0.0.1") self.resolver.add_ta_file(ta_file) #read public keys for DNSSEC verification
def resolve(self, name, qtype): ctx = ub_ctx() ctx.resolvconf(self.resolv_conf) if not os.path.isfile(self.dnssec_root_key): raise Exception('Trust anchor is missing or inaccessible') else: ctx.add_ta_file(self.dnssec_root_key) status, result = ctx.resolve(name, rdatatype.from_text(qtype), RR_CLASS_IN) if status != 0: raise WalletNameLookupError if not result.secure or result.bogus: raise WalletNameLookupInsecureError elif not result.havedata: return None else: # We got data txt = result.data.as_domain_list() # Reference implementation for serving BIP32 and BIP70 requests try: # BIP32/BIP70 URL will be base64 encoded. Some wallet addresses fail decode. # If it fails, assume wallet address or unknown and return b64txt = b64decode(txt[0]) except: return txt[0] # Fully qualified bitcoin URI, return as is if b64txt.startswith('bitcoin:'): return b64txt elif re.match(r'^https?:\/\/', b64txt): try: # Identify localhost or link_local/multicast/private IPs and return without issuing a GET. lookup_url, return_data = WalletNameResolver.get_endpoint_host( b64txt) if return_data: return return_data # Try the URL. Returning response.text and expect a Bitcoin URI as delivered from Addressimo. response = requests.get(lookup_url, headers={ 'X-Forwarded-For': '%s' % request.access_route[0] }) return response.text except Exception: # Return base64 decoded value if we cannot perform a GET on the URL to allow requester to handle. return b64txt else: # If you made it this far, assume wallet address and return return txt[0]
def _cache_miss(input_key): # type: (KeyInfo) -> Validity """ In case the key was not found in the cache, create an Unbound context and contact the DNS system """ try: import unbound except ImportError as e: raise RuntimeError("Configuration option 'gpgkey_dns_verification' requires\ libunbound ({})".format(e)) ctx = unbound.ub_ctx() if ctx.set_option("verbosity:", "0") != 0: logger.debug("Unbound context: Failed to set verbosity") if ctx.set_option("qname-minimisation:", "yes") != 0: logger.debug("Unbound context: Failed to set qname minimisation") if ctx.resolvconf() != 0: logger.debug("Unbound context: Failed to read resolv.conf") if ctx.add_ta_file("/var/lib/unbound/root.key") != 0: logger.debug("Unbound context: Failed to add trust anchor file") status, result = ctx.resolve(email2location(input_key.email), RR_TYPE_OPENPGPKEY, unbound.RR_CLASS_IN) if status != 0: logger.debug("Communication with DNS servers failed") return Validity.ERROR if result.bogus: logger.debug("DNSSEC signatures are wrong") return Validity.BOGUS_RESULT if not result.secure: logger.debug("Result is not secured with DNSSEC") return Validity.RESULT_NOT_SECURE if result.nxdomain: logger.debug("Non-existence of this record was proven by DNSSEC") return Validity.PROVEN_NONEXISTENCE if not result.havedata: # TODO: This is weird result, but there is no way to perform validation, so just return # an error logger.debug("Unknown error in DNS communication") return Validity.ERROR else: data = result.data.as_raw_data()[0] dns_data_b64 = base64.b64encode(data) if dns_data_b64 == input_key.key: return Validity.VALID else: # In case it is different, print the keys for further examination in debug mode logger.debug("Key from DNS: {}".format(dns_data_b64)) logger.debug("Input key : {}".format(input_key.key)) return Validity.REVOKED
def create_context(config_file="ub.lookup.conf", asyncflag=False): """ Create an unbound context to use for testing. """ ctx = unbound.ub_ctx() status = ctx.config(config_file) if status != 0: print("read config failed with status: {}".format(status)) sys.exit(1) ctx.set_async(asyncflag) return ctx
def ub_ctx(self): if self._ub_ctx is None: self._ub_ctx = unbound.ub_ctx() self._ub_ctx.add_ta_file( os.path.join(os.getcwd(), settings.DNS_ROOT_KEY)) self._ub_ctx.set_option( "cache-max-ttl:", str(settings.CACHE_TTL*0.9)) self._ub_ctx.set_async(True) if settings.ENABLE_BATCH and settings.CENTRAL_UNBOUND: self._ub_ctx.set_fwd("{}".format(settings.CENTRAL_UNBOUND)) return self._ub_ctx
def _cache_miss(input_key): # type: (KeyInfo) -> Validity """ In case the key was not found in the cache, create an Unbound context and contact the DNS system """ try: import unbound except ImportError as e: raise RuntimeError( "Configuration option 'gpgkey_dns_verification' requires\ libunbound ({})".format(e)) ctx = unbound.ub_ctx() if ctx.set_option("verbosity:", "0") != 0: logger.debug("Unbound context: Failed to set verbosity") if ctx.set_option("qname-minimisation:", "yes") != 0: logger.debug("Unbound context: Failed to set qname minimisation") if ctx.resolvconf() != 0: logger.debug("Unbound context: Failed to read resolv.conf") if ctx.add_ta_file("/var/lib/unbound/root.key") != 0: logger.debug("Unbound context: Failed to add trust anchor file") status, result = ctx.resolve(email2location(input_key.email), RR_TYPE_OPENPGPKEY, unbound.RR_CLASS_IN) if status != 0: return Validity.ERROR if result.bogus: return Validity.BOGUS_RESULT if not result.secure: return Validity.RESULT_NOT_SECURE if result.nxdomain: return Validity.PROVEN_NONEXISTENCE if not result.havedata: # TODO: This is weird result, but there is no way to perform validation, so just return # an error return Validity.ERROR else: data = result.data.as_raw_data()[0] dns_data_b64 = base64.b64encode(data) if dns_data_b64 == input_key.key: return Validity.VALID else: return Validity.REVOKED
def ub_ctx(self): if self._ub_ctx is None: self._ub_ctx = unbound.ub_ctx() if (hasattr(settings, 'ENABLE_INTEGRATION_TEST') and settings.ENABLE_INTEGRATION_TEST): self._ub_ctx.debuglevel(2) self._ub_ctx.config(settings.IT_UNBOUND_CONFIG_PATH) self._ub_ctx.set_fwd(settings.IT_UNBOUND_FORWARD_IP) else: self._ub_ctx.add_ta_file( os.path.join(os.getcwd(), settings.DNS_ROOT_KEY)) self._ub_ctx.set_option("cache-max-ttl:", str(settings.CACHE_TTL * 0.9)) self._ub_ctx.set_async(True) if settings.ENABLE_BATCH and settings.CENTRAL_UNBOUND: self._ub_ctx.set_fwd("{}".format(settings.CENTRAL_UNBOUND)) return self._ub_ctx
def resolve(self, name, qtype): ctx = ub_ctx() ctx.resolvconf(self.resolv_conf) if not os.path.isfile(self.dnssec_root_key): raise Exception('Trust anchor is missing or inaccessible') else: ctx.add_ta_file(self.dnssec_root_key) status, result = ctx.resolve(name, rdatatype.from_text(qtype), RR_CLASS_IN) if status != 0: raise WalletNameLookupError if not result.secure or result.bogus: raise WalletNameLookupInsecureError elif not result.havedata: return None else: # We got data txt = result.data.as_domain_list() return txt[0]
def __init__(self, taskQueue, taFile, rrScanners, dbQueue, opts, prefix): """Create scanning thread. @param taskQueue: Queue.Queue containing domains to scan as strings @param taFile: trust anchor file for libunbound @param rrScanners: list of subclasses of RRTypeParser to use for scan. NSParser and DSParser are always on and shouldn't be present in the list. @param dbQueue: Queue.Queue for passing things to store to StorageThread @param opts: instance of DnsConfigOptions @param prefix: prefix of schema """ self.taskQueue = taskQueue self.rrScanners = rrScanners self.dbQueue = dbQueue self.opts = opts self.prefix = prefix threading.Thread.__init__(self) self.resolver = ub_ctx() if opts.forwarder: self.resolver.set_fwd(opts.forwarder) self.resolver.add_ta_file(taFile) #read public keys for DNSSEC verification
def __cache_miss(input_key: KeyInfo) -> Validity: RR_TYPE_OPENPGPKEY = 61 ctx = unbound.ub_ctx() ctx.config("/vagrant/unbound/libunbound.conf") ctx.add_ta_file("/vagrant/root-server/root.zone.signed") status, result = ctx.resolve(email2location(input_key.email), RR_TYPE_OPENPGPKEY, unbound.RR_CLASS_IN) if status != 0: return Validity.ERROR if result.bogus: return Validity.BOGUS_RESULT if not result.secure: return Validity.RESULT_NOT_SECURE if result.nxdomain: return Validity.PROVEN_NONEXISTENCE if not result.havedata: # TODO: what kind of result is this??? return Validity.ERROR else: data = result.data.as_raw_data()[0] dns_data_b64 = base64.b64encode(data) if dns_data_b64 == input_key.key: return Validity.VALID else: return Validity.REVOKED
from timeit import default_timer as timer import yaml from celery import shared_task from django.core.cache import cache from django.conf import settings from django.db import connection from django.http import HttpResponseRedirect from django.shortcuts import render from django.utils import timezone from django.utils.translation import ugettext as _ import unbound from checks import redis_id ub_ctx = unbound.ub_ctx() if (hasattr(settings, 'ENABLE_INTEGRATION_TEST') and settings.ENABLE_INTEGRATION_TEST): ub_ctx.debuglevel(2) ub_ctx.config(settings.IT_UNBOUND_CONFIG_PATH) ub_ctx.set_fwd(settings.IT_UNBOUND_FORWARD_IP) ub_ctx.set_async(True) if settings.ENABLE_BATCH and settings.CENTRAL_UNBOUND: ub_ctx.set_fwd("{}".format(settings.CENTRAL_UNBOUND)) # See: https://stackoverflow.com/a/53875771 for a good summary of the various # RFCs and other rulings that combine to define what is a valid domain name. # Of particular note are xn-- which is used for internationalized TLDs, and # the rejection of digits in the TLD if not xn--. Digits in the last label # were legal under the original RFC-1035 but not according to the "ICANN # Application Guidebook for new TLDs (June 2012)" which stated that "The
print("Resolving domain", domain) s, r = resolver.resolve(domain) print("status: %s, secure: %s, rcode: %s, havedata: %s, answer_len; %s" % (s, r.secure, r.rcode_str, r.havedata, r.answer_len)) s, pkt = ldns.ldns_wire2pkt(r.packet) if s != 0: raise RuntimeError("Error parsing DNS packet") rrsigs = pkt.rr_list_by_type(RR_TYPE_RRSIG, ldns.LDNS_SECTION_ANSWER) print("RRSIGs from answer:", sorted(rrsigs)) rrsigs = pkt.rr_list_by_type(RR_TYPE_RRSIG, ldns.LDNS_SECTION_AUTHORITY) print("RRSIGs from authority:", sorted(rrsigs)) nsecs = pkt.rr_list_by_type(RR_TYPE_NSEC, ldns.LDNS_SECTION_AUTHORITY) print("NSECs:", sorted(nsecs)) nsec3s = pkt.rr_list_by_type(RR_TYPE_NSEC3, ldns.LDNS_SECTION_AUTHORITY) print("NSEC3s:", sorted(nsec3s)) print("---") resolver = ub_ctx() resolver.add_ta(". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D") dnssecParse("nic.cz") dnssecParse("nonexistent-domain-blablabla.cz") dnssecParse("nonexistent-domain-blablabla.root.cz")
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' import unbound ctx = unbound.ub_ctx() ctx.resolvconf("/etc/resolv.conf") status, result = ctx.resolve("nic.cz", unbound.RR_TYPE_MX, unbound.RR_CLASS_IN) if status == 0 and result.havedata: print("Result:") print(" raw data:", result.data) for k in result.data.mx_list: print(" priority:%d address:%s" % k) status, result = ctx.resolve("nic.cz", unbound.RR_TYPE_A, unbound.RR_CLASS_IN) if status == 0 and result.havedata: print("Result:") print(" raw data:", result.data) for k in result.data.address_list: print(" address:%s" % k)
#!/usr/bin/python from unbound import ub_ctx,ub_strerror,RR_TYPE_A,RR_CLASS_IN ctx = ub_ctx() ctx.resolvconf("/etc/resolv.conf") status, result = ctx.resolve("test.record.xxx", RR_TYPE_A, RR_CLASS_IN) if status == 0 and result.havedata: print "Result:", result.data.address_list else: print "No record found" #define new local zone status = ctx.zone_add("xxx.","static") if (status != 0): print "Error zone_add:",status, ub_strerror(status) #add RR to the zone status = ctx.data_add("test.record.xxx. IN A 1.2.3.4") if (status != 0): print "Error data_add:",status, ub_strerror(status) #lookup for an A record status, result = ctx.resolve("test.record.xxx", RR_TYPE_A, RR_CLASS_IN) if status == 0 and result.havedata: print "Result:", result.data.as_address_list() else: print "No record found"
def resolve(self, name, qtype): ''' Resolves a Blockchain-based (Namecoin) DNS Name via 2 step process using DNSSEC Step 1: ------- Get NS and DS records for the Namecoin name (for example: www.mattdavid.bit) from the Namecoin Client Step 2: ------- For each listed nameserver: - Create a temporary config file for use with unbound - Set Unbound's Trust Anchor to be the given DS records for the Namecoin-based domain name - Do DNSSEC-enabled DNS resolution for the given name / qtype :param name: DNS Record Name Query (for example: www.mattdavid.bit) :param qtype: String representation of query type (for example: A, AAAA, TXT, NS, SOA, etc...) :return: Resolved value if successful, None if un-successful ''' name = name.rstrip('.') if not name.endswith('.bit'): raise ValueError('This is not a valid .bit domain') domains = name.split('.') domains.reverse() if len(domains) < 2: raise ValueError('At least SLD Required') # Get Namecoin-based Domain Info from Namecoin Blockchain nc_domain = self.nc_name_resolver.name_show(domains[1]) if not nc_domain or not nc_domain.get('value'): log.error( 'No Name Value Data Found for Namecoin-based Domain Name: d/%s' % domains[1]) raise NamecoinValueException('No Name Value Data Found for: d/%s' % domains[1]) nc_value = json.loads(nc_domain.get('value', '{}').replace('\'', '"')) if not nc_value.get('ds'): log.error( 'No DS Records Present for Namecoin-based Domain Name: %s' % name) raise NoDSRecordException() if not nc_value.get('ns'): log.error( 'No NS Records Present for Namecoin-based Domain Name: %s' % name) raise NoNameserverException() sld = '%s.%s.' % (domains[1], domains[0]) ds_record = ' '.join([str(x) for x in nc_value['ds'][0][0:3]]) # Handle both Hex and Base64 encoding (Base64 is the preferred encoding) per: # https://wiki.namecoin.info/index.php?title=Domain_Name_Specification if re.match('^[0-9a-fA-F]*$', nc_value['ds'][0][3]): ds_record += ' %s' % nc_value['ds'][0][3] else: ds_record += ' %s' % base64.b64decode( nc_value['ds'][0][3]).encode('hex').upper() ds_ta = '%s IN DS %s' % (sld, ds_record) ns_ctx = ub_ctx() ns_ctx.resolvconf(self.resolv_conf) if not os.path.isfile(self.dnssec_root_key): log.error("Trust anchor missing or inaccessible") raise Exception("Trust anchor is missing or inaccessible: %s" % self.dnssec_root_key) else: ns_ctx.add_ta_file(self.dnssec_root_key) last_error = None for ns in nc_value.get('ns', []): lookup_value = None status, result = ns_ctx.resolve(ns, rdatatype.from_text('A'), rdataclass.from_text('IN')) # NOTE: We do not require secure DNS resolution here because the Blockchain-stored DS records work as the trust anchor # and the signed RRSIG DNS results from the final DNS+DNSSEC lookup will be able to complete the chain of trust if status == 0 and result and result.data and not result.bogus: tmp_config_file = self._build_temp_unbound_config( sld, result.data.as_address_list()[0]) else: last_error = InvalidNameserverException() log.warn('No or Invalid Resolution Result for Nameserver: %s' % ns) continue ctx = ub_ctx() ctx.config(tmp_config_file) ctx.add_ta(str(ds_ta)) _qtype = None try: _qtype = rdatatype.from_text(qtype) except Exception as e: log.error( 'Unable to get RDATAType for Given Query Type [%s]: %s' % (qtype, str(e))) raise ValueError('Unable to get RDATAType for Query Type %s' % qtype) status, result = ctx.resolve(name, _qtype, rdataclass.from_text('IN')) if status != 0: log.info("DNS Resolution Failed: %s [%s]" % (name, _qtype)) elif status == 0: if not result.secure: log.info( "DNS Resolution Returned Insecure Result: %s [%s]" % (name, qtype)) last_error = InsecureResultException() elif result.bogus: log.info("DNS Resolution Returned Bogus Result: %s [%s]" % (name, qtype)) last_error = BogusResultException() elif not result.havedata: log.info("DNS Resolution Returned Empty Result: %s [%s]" % (name, qtype)) last_error = EmptyResultException() else: # Get appropriate data by query type if qtype in ['A', 'AAAA']: lookup_value = result.data.as_address_list() elif qtype in ['CNAME', 'TXT']: lookup_value = result.data.as_domain_list() elif qtype in ['MX']: lookup_value = result.data.as_mx_list() else: last_error = NotImplementedError( 'Unsupported DNS Query Type: %s' % qtype) self._delete_temp_unbound_config(tmp_config_file) if lookup_value: return lookup_value[0] if last_error and isinstance(last_error, NotImplementedError): raise last_error log.error('DNS Resolution Failed: %s [%s]' % (name, qtype)) if last_error: raise last_error return None
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ''' from __future__ import print_function import os from unbound import ub_ctx, RR_TYPE_A, RR_CLASS_IN ctx = ub_ctx() ctx.resolvconf("/etc/resolv.conf") fw = open("dnssec-valid.txt", "wb") ctx.debugout(fw) ctx.debuglevel(2) if os.path.isfile("keys"): ctx.add_ta_file("keys") #read public keys for DNSSEC verification status, result = ctx.resolve("www.nic.cz", RR_TYPE_A, RR_CLASS_IN) if status == 0 and result.havedata: print("Result:", sorted(result.data.address_list)) if result.secure:
class Resolver(object): """ Responsible for querying dns records and returning results of the query in a formatted response. The general format is: {'domain', 'rr_types', 'answer'}. This class does not attempt to reach any hosts. See 'Reacher" class for host connecting & reachability. This class does not attempt to obtain DMARC, SPF, or DKIM records from DNS. For this, see DmarcianClient class. This class borrows functionality from the 'ip_helper.py' module to assist in constructing proper IPv6 addresses. This class has sibling FormattedResponse classes that wrap dictionaries that contain the response data. Inherits from: object. Parent to: None. Sibling to: Reacher, DmarcianClient """ ctx = ub.ub_ctx() # non dnssec context ctx.resolvconf(Config.RESOLV_CONF_LOCATION) ctx_dnssec = ub.ub_ctx() ctx_dnssec.resolvconf(Config.RESOLV_CONF_LOCATION) ctx_dnssec.add_ta_file(Config.ROOT_TRUST_ANCHOR) def __init__(self): pass def get_a_records(self, domain: str, as_json: bool = False): """ Accepts domain: str. Returns a formatted answer including dictionary of A records in the format: {'domain': 'example.com', 'rr_types':['a'], 'answer': ['1.1.1.1', '2.2.2.2', '3.3.3.3' ... etc]}. If no record is found, returns None in the answer section. Set 'as_json' to True to return a pure json response instead of the wrapped formatted response. """ formatted_answer = { 'domain': domain, 'rr_types': ["a"], 'answer': None } status, results = Resolver.ctx.resolve(domain, rrtype=ub.RR_TYPE_A, rrclass=ub.RR_CLASS_IN) if status != 0: raise DNSResolveError( f"Error occurred while resolving IPv4 for {domain}") elif results.havedata == 1 and len(results.data.address_list) > 0: ipv4_addr_list = results.data.address_list formatted_answer['answer'] = {} i = 0 for ip in ipv4_addr_list: formatted_answer['answer'][i] = ip i += 1 if as_json: return json.dumps(formatted_answer) # return DNSFormattedResponse(formatted_answer) return formatted_answer def get_aaaa_records(self, domain: str, as_json: bool = False): """ Accepts domain: str. Returns a formatted answer including dictionary of A records in the format: {'domain': 'example.com', 'rr_types':['aaaa'], 'answer': ['f::0', 'f::1', 'f::2' ... etc]}. If no record is found, returns None in the answer section. Set 'as_json' to True to return a pure json response instead of the wrapped formatted response. """ formatted_answer = { 'domain': domain, 'rr_types': ["aaaa"], 'answer': None } status, results = Resolver.ctx.resolve(domain, rrtype=ub.RR_TYPE_AAAA, rrclass=ub.RR_CLASS_IN) if status != 0: raise DNSResolveError( f"Error occurred while resolving IPv4 for {domain}") elif results.havedata == 1 and len(results.data.address_list) > 0: ipv6_addr_list = results.rawdata formatted_answer['answer'] = {} i = 0 for ip in ipv6_addr_list: if ip_helper.V6.is_valid(ip): formatted_answer['answer'][ i] = ip_helper.V6.bytes_to_hexadectet(ip) i += 1 if as_json: return json.dumps(formatted_answer) # return DNSFormattedResponse(formatted_answer) return formatted_answer # no exposed json for this private method below def _get_aaaa_bytes(self, domain: str): """Private internal class method. Takes in a single server name. If ipv6 data is found, returns bytes. If not found, returns None. No formatted response has been set.""" ipv6_bytes = None status, results = Resolver.ctx.resolve(domain, rrtype=ub.RR_TYPE_AAAA) if status != 0: raise DNSResolveError( f"Error resolving AAAA record for {domain}: {ub.ub_strerror(status)}" ) elif results.havedata == 1 and ip_helper.V6.is_valid( results.rawdata[0]): ipv6_bytes = results.rawdata[0] # return results return ipv6_bytes def get_soa(self, domain: str, as_json: bool = False): """ Accepts domain: str. Returns a formatted answer containing 'Start Of Authority' records in the format: {'domain': 'example.com', 'rr_types':['soa'], 'answer': ['administrator info, other info, time info, etc]}. If no record is found, returns None in the answer section. Set 'as_json=True' to return a pure json response instead of the wrapped formatted response. """ formatted_answer = { 'domain': domain, 'rr_types': ["soa"], 'answer': None } soa_list = None status, result = Resolver.ctx_dnssec.resolve(domain, ub.RR_TYPE_SOA, ub.RR_CLASS_IN) if status == 0 and result.havedata and result.secure == 1: formatted_answer['answer'] = {} soa_list = result.data.data i = 0 for record in soa_list: formatted_answer['answer'][i] = str(record) i += 1 elif status != 0: # throw/raise error print("Resolve error: ", ub.ub_strerror(status)) elif result.havedata == 0: # if no data in result print("No data.") if as_json: return json.dumps(formatted_answer) return DNSFormattedResponse(formatted_answer) def get_ns(self, domain: str, as_json: bool = False): """ Accepts domain: str. Returns a formatted answer containing 'NS' records in the format: {'domain': 'example.com', 'rr_types':['ns'], 'answer': ['ns1.com', 'ns2.com', ... etc]}. If no record is found, returns None in the answer section. Set 'as_json=True' to return a pure json response instead of the wrapped formatted response. """ formatted_answer = { 'domain': domain, 'rr_types': ["ns"], 'answer': None } status, results = Resolver.ctx.resolve(domain, rrtype=ub.RR_TYPE_NS) if status != 0: raise DNSResolveError( f"Error occured while resolving IPv4 for {domain}") elif results.havedata == 1 and len(results.data.address_list) > 0: ns_list = list(results.data.as_domain_list()) formatted_answer['answer'] = {} i = 0 for each in ns_list: formatted_answer['answer'][i] = each i += 1 if as_json: return json.dumps(formatted_answer) # return DNSFormattedResponse(formatted_answer) return formatted_answer def get_mx(self, domain: str, as_json: bool = False): """ Accepts domain: str. Returns a formatted answer containing 'MX' records in the format: {'domain': 'example.com', 'rr_types':['mx'], 'answer': ['smtp1.com', 'smtp2.com', ... etc]}. If no record is found, returns None in the answer section. Set 'as_json=True' to return a pure json response instead of the wrapped formatted response. """ formatted_answer = { 'domain': domain, 'rr_types': ["mx"], 'answer': None } status, results = Resolver.ctx.resolve(domain, rrtype=ub.RR_TYPE_MX) if status != 0: raise DNSResolveError( f"Error while fetching Mail Exchange list for {domain}") elif results.havedata == 1 and len(results.data.as_mx_list()) > 0: mx_list = list(results.data.as_mx_list()) formatted_answer['answer'] = {} i = 0 for priority, name in mx_list: formatted_answer['answer'][i] = name i += 1 if as_json: return json.dumps(formatted_answer) # return DNSFormattedResponse(formatted_answer) return formatted_answer # methods below this line use unbound indirectly. They use methods in this class as their dependencies. # dns host mapping for use with reachability def get_ipv6_mapping(self, domain: str, associated_with: str, as_json: bool = False): """ Accepts a domain, a type of host to associate an ip address with, and optionally 'as_json=True' to output json. This builds the DNSHostMappingFormattedResponse which can then be 'unpacked' by the Reacher class method reach_dns_hosts() or can be passed into a IPV6ExistsState directly. Output is something like, {'domain': 'x.com', 'rr_types':['ns', 'aaaa'], 'answer': {'ns1.com':'f::1', 'ns2.com':'f::2' ... etc} """ formatted_answer = {'domain': domain, 'rr_types': [], 'answer': None} name_list = None if associated_with == "ns": ns_list = None if self.get_ns(domain).get_response()['answer'] is not None: ns_list = list( self.get_ns(domain).get_response()['answer'].values()) name_list = ns_list formatted_answer['rr_types'].append("ns") elif associated_with == "mx": mx_list = None if self.get_mx(domain).get_response()['answer'] is not None: mx_list = list( self.get_mx(domain).get_response()['answer'].values()) name_list = mx_list formatted_answer['rr_types'].append("mx") formatted_answer['rr_types'].append( "aaaa" ) # [rr_type, 4a] in that order indicates 'answer' content format. if associated_with == "ns" and name_list is None: # all domains have ns records, but not necessarily mx records raise ValueError( "Value of 'name_list' is None while querying for NS records. Should be non-empty list." ) if name_list: i = 0 formatted_answer['answer'] = {} for name in name_list: ip6_dict = self.get_aaaa_records( name).get_response() # None or valid ip6 address returned if ip6_dict['answer'] is not None: formatted_answer['answer'][name] = ip6_dict['answer'].get( i) else: formatted_answer['answer'][name] = ip6_dict['answer'] i += 1 if ip6_dict['answer'] and i + 1 > len( ip6_dict['answer'] ): # i beyond the bounds of 'data' dict i = 0 continue if as_json: return json.dumps(formatted_answer) return DNSHostMappingFormattedResponse(formatted_answer) def get_ipv4_mapping(self, domain: str, associated_with: str, as_json: bool = False): # newly added for decoupling """ Accepts a domain, a type of host to associate an ip address with, and optionally 'as_json=True' to output json. This builds the DNSHostMappingFormattedResponse which can then be 'unpacked' by the Reacher class method reach_dns_hosts() or can be passed into a IPV4ExistsState directly. Output is something like, {'domain': 'x.com', 'rr_types':['ns', 'a'], 'answer': {'ns1.com':'1.1.1.1', 'ns2.com':'2.2.2.2' ... etc} """ formatted_answer = {'domain': domain, 'rr_types': [], 'answer': None} name_list = None if associated_with == "ns": ns_list = None if self.get_ns(domain)['answer'] is not None: ns_list = list(self.get_ns(domain)['answer'].values()) name_list = ns_list formatted_answer['rr_types'].append("ns") elif associated_with == "mx": mx_list = None if self.get_mx(domain)['answer'] is not None: mx_list = list(self.get_mx(domain)['answer'].values()) name_list = mx_list formatted_answer['rr_types'].append("mx") formatted_answer['rr_types'].append( "a" ) # [name_record, ipv6] in that order indicates 'data' response index contents. if associated_with == "ns" and name_list is None: # all domains have ns records, but not necessarily mx records raise ValueError( "Value of 'name_list' is None while querying for NS records. Should be non-empty list." ) if name_list: i = 0 formatted_answer['answer'] = {} for name in name_list: ip4_dict = self.get_a_records( name) # None or valid ip4 address returned if ip4_dict['answer'] is not None: formatted_answer['answer'][name] = ip4_dict['answer'].get( i) else: formatted_answer['answer'][name] = ip4_dict['answer'] i += 1 if ip4_dict['answer'] and i + 1 > len( ip4_dict['answer'] ): # i beyond the bounds of 'data' dict i = 0 continue if as_json: return json.dumps(formatted_answer) # return DNSHostMappingFormattedResponse(formatted_answer) return formatted_answer # dnssec def dnssec_comprehensive(self, domain: str, as_json: bool = False): formatted_response = { 'domain': domain, 'rr_types': ['a', 'dnssec', 'dnskey', 'rrsig', 'nsec', 'ds', 'soa'], 'answer': { 'validation': { 'a': None, 'dnssec': None }, 'signatures': { 'rrsig': None, 'nsec': None, 'ds': None, 'soa': None } } } validation_response = self.dnssec_validate( domain=domain, as_json=False).get_response() formatted_response['answer']['validation'] = validation_response[ 'answer'] dnssec_sigs_response = self.get_dnssec_sigs( domain=domain, as_json=False).get_response() formatted_response['answer']['signatures'] = dnssec_sigs_response[ 'answer'] if as_json: return json.dumps(formatted_response) return DNSSECFormattedResponse(formatted_response) def dnssec_validate(self, domain: str, as_json: bool = False): """Accepts a domain: str. Returns a formatted answer dictionary with dnssec validation results in the 'answer.'""" formatted_answer = { 'domain': domain, 'rr_types': ["a", "dnssec"], 'answer': None } status, result = Resolver.ctx_dnssec.resolve(domain, ub.RR_TYPE_A) if status == 0 and result.havedata: formatted_answer['answer'] = {} ip_address_list = result.data.address_list for ip in ip_address_list: if result.secure: formatted_answer['answer'][ip] = "secure" elif result.bogus: formatted_answer['answer'][ip] = "bogus" else: formatted_answer['answer'][ip] = "insecure" if as_json: return json.dumps(formatted_answer) return DNSSECValidatedFormattedResponse(formatted_answer) def get_dnssec_sigs(self, domain: str, as_json: bool = False): """ Accepts a domain name and acquires all DNSSEC records according to RFC4034 and RFC4035: DNSKEY, RRSIG, NSEC, DS. Additionally, it also gets the SOA record. This method does not validate DNSSEC. It only checks for proper signatures. """ # fetch formatted_answer = { 'domain': domain, 'rr_types': ['dnskey', 'rrsig', 'nsec', 'ds', 'soa'], 'answer': None } dnskey_fa = self.get_dnskeys(domain, as_json=False).get_response() rrsig_fa = self.get_rrsigs(domain, as_json=False).get_response() nsec_fa = self.get_nsec(domain, as_json=False).get_response() ds_fa = self.get_ds(domain, as_json=False).get_response() soa_fa = self.get_soa(domain, as_json=False).get_response() # construct multi-resource record answer formatted_answer['answer'] = {} if soa_fa['answer'] is not None and len(soa_fa['answer']) > 0: keys = list(soa_fa['answer'].keys()) for key in keys: formatted_answer['answer']['soa'] = [] formatted_answer['answer']['soa'].append(soa_fa['answer'][key]) else: formatted_answer['answer']['soa'] = None if dnskey_fa['answer'] is not None and len(dnskey_fa['answer']) > 0: keys = list(dnskey_fa['answer'].keys()) for key in keys: formatted_answer['answer']['dnskey'] = [] formatted_answer['answer']['dnskey'].append( dnskey_fa['answer'][key]) else: formatted_answer['answer']['dnskey'] = None if rrsig_fa['answer'] is not None and len(rrsig_fa['answer']) > 0: keys = list(rrsig_fa['answer'].keys()) for key in keys: formatted_answer['answer']['rrsig'] = [] formatted_answer['answer']['rrsig'].append( rrsig_fa['answer'][key]) else: formatted_answer['answer']['rrsig'] = None if nsec_fa['answer'] is not None and len(nsec_fa['answer']) > 0: keys = list(nsec_fa['answer'].keys()) for key in keys: formatted_answer['answer']['nsec'] = [] formatted_answer['answer']['nsec'].append( nsec_fa['answer'][key]) else: formatted_answer['answer']['nsec'] = None if ds_fa['answer'] is not None and len(ds_fa['answer']) > 0: keys = list(ds_fa['answer'].keys()) for key in keys: formatted_answer['answer']['ds'] = [] formatted_answer['answer']['ds'].append(ds_fa['answer'][key]) else: formatted_answer['answer']['ds'] = None if as_json: return json.dumps(formatted_answer) return DNSSECSignaturesFormattedResponse(formatted_answer) def get_dnskeys(self, domain: str, as_json: bool = False): """Accepts a domain: str. Returns a formatted answer dictionary, including dnskey records in the 'answer'.""" formatted_answer = { 'domain': domain, 'rr_types': ["dnskey"], 'answer': None } status, result = Resolver.ctx_dnssec.resolve(domain, rrtype=ub.RR_TYPE_DNSKEY) if status == 0 and result.havedata == 1: print("returned dnskeys.") formatted_answer['answer'] = {} dns_keys_list = result.data.data # list of dnskeys. *should* return None or non-empty list i = 0 for key in dns_keys_list: # place into dict if as_json: formatted_answer['answer'][i] = str(key) else: formatted_answer['answer'][i] = key i += 1 elif status != 0: # throw/raise error print("Resolve error: ", ub.ub_strerror(status)) elif result.havedata == 0: # if no data in result print("No data.") if as_json: return json.dumps(formatted_answer) return DNSFormattedResponse(formatted_answer) def get_rrsigs(self, domain: str, as_json: bool = False): """Accepts a domain: str. Returns a formatted answer dictionary, with rrsig records in the 'answer'.""" formatted_answer = { 'domain': domain, 'rr_types': ["rrsig"], 'answer': None } status, result = Resolver.ctx_dnssec.resolve(domain, rrtype=ub.RR_TYPE_RRSIG) if status == 0 and result.havedata: print("rrsigs returned.") rrsig_list = result.data.data formatted_answer['answer'] = {} i = 0 for sig in rrsig_list: if as_json: formatted_answer['answer'][i] = str(sig) else: formatted_answer['answer'][i] = sig i += 1 elif status != 0: # throw/raise error print("Resolve error: ", ub.ub_strerror(status)) elif result.havedata == 0: # if no data in result print("No data.") print(result.rcode_str) elif result.rcode != 0: print(result.rcode_str) if as_json: return json.dumps(formatted_answer) return DNSFormattedResponse(formatted_answer) def get_ds(self, domain: str, as_json: bool = False): """Accepts a domain. Returns a formatted answer dictionary with ds records in the 'answer'.""" formatted_answer = { 'domain': domain, 'rr_types': ["ds"], 'answer': None } status, result = Resolver.ctx_dnssec.resolve(domain, rrtype=ub.RR_TYPE_DS) if status == 0 and result.havedata: print("ds record returned.") formatted_answer['answer'] = {} ds_records_list = result.data.data i = 0 for ds in ds_records_list: if as_json: formatted_answer['answer'][i] = str(ds) else: formatted_answer['answer'][i] = ds i += 0 elif status != 0: # throw/raise error print("Resolve error: ", ub.ub_strerror(status)) elif result.havedata == 0: # if no data in result print("No data.") if as_json: return json.dumps(formatted_answer) return DNSFormattedResponse(formatted_answer) def get_nsec(self, domain: str, as_json: bool = False): """Accepts a domain: str. Returns a formatted answer dictionary with the nsec records inside the 'answer'.""" formatted_answer = { 'domain': domain, 'rr_types': ["nsec"], 'answer': None } status, result = Resolver.ctx_dnssec.resolve(domain, rrtype=ub.RR_TYPE_NSEC) if status == 0 and result.havedata: nsec_list = result.data.data formatted_answer['answer'] = {} i = 0 for nsec in nsec_list: if as_json: formatted_answer['answer'][i] = str(nsec) else: formatted_answer['answer'][i] = nsec i += 1 elif status != 0: # throw/raise error print("Resolve error: ", ub.ub_strerror(status), result.rcode_str) elif result.havedata == 0: # if no data in result print("No data", result.rcode_str) if as_json: return json.dumps(formatted_answer) return DNSFormattedResponse(formatted_answer)
print("Resolving domain", domain) s, r = resolver.resolve(domain) print("status: %s, secure: %s, rcode: %s, havedata: %s, answer_len; %s" % (s, r.secure, r.rcode_str, r.havedata, r.answer_len)) s, pkt = ldns.ldns_wire2pkt(r.packet) if s != 0: raise RuntimeError("Error parsing DNS packet") rrsigs = pkt.rr_list_by_type(RR_TYPE_RRSIG, ldns.LDNS_SECTION_ANSWER) print("RRSIGs from answer:", sorted(rrsigs)) rrsigs = pkt.rr_list_by_type(RR_TYPE_RRSIG, ldns.LDNS_SECTION_AUTHORITY) print("RRSIGs from authority:", sorted(rrsigs)) nsecs = pkt.rr_list_by_type(RR_TYPE_NSEC, ldns.LDNS_SECTION_AUTHORITY) print("NSECs:", sorted(nsecs)) nsec3s = pkt.rr_list_by_type(RR_TYPE_NSEC3, ldns.LDNS_SECTION_AUTHORITY) print("NSEC3s:", sorted(nsec3s)) print("---") resolver = ub_ctx() resolver.add_ta(". IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5") dnssecParse("nic.cz") dnssecParse("nonexistent-domain-blablabla.cz") dnssecParse("nonexistent-domain-blablabla.root.cz")
#!/usr/bin/env python from unbound import ub_ctx, ub_version from binascii import hexlify RR_TYPE_TLSA = 52 print "Unbound version:", ub_version() u = ub_ctx() u.add_ta_file("keys.tlsa-test") u.set_fwd("127.0.0.1") u.set_option("dlv-anchor:", "dlv.isc.org. IN DNSKEY 257 3 5 BEAAAAPHMu/5onzrEE7z1egmhg/WPO0+juoZrW3euWEn4MxDCE1+lLy2 brhQv5rN32RKtMzX6Mj70jdzeND4XknW58dnJNPCxn8+jAGl2FZLK8t+ 1uq4W+nnA3qO2+DL+k6BD4mewMLbIYFwe0PG73Te9fZ2kJb56dhgMde5 ymX4BI/oQ+ cAK50/xvJv00Frf8kw6ucMTwFlgPe+jnGxPPEmHAte/URk Y62ZfkLoBAADLHQ9IrS2tryAe7mbBZVcOwIeU/Rw/mRx/vwwMCTgNboM QKtUdvNXDrYJDSHZws3xiRXF1Rf+al9UmZfSav/4NWLKjHzpT59k/VSt TDN0YUuWrBNh") for fqdn in ("torproject.org", "labs.nic.cz", "fedoraproject.org", "nohats.ca", "rogue.nohats.ca"): s, r = u.resolve("_443._tcp." + fqdn, RR_TYPE_TLSA) print "fqdn: %s, status: %s, rcode: %s, secure: %s, bogus: %s, why_bogus: %s" % \ (fqdn, s, r.rcode, r.secure, r.bogus, r.why_bogus) if r.havedata: print [hexlify(rr) for rr in r.data.data]
def resolve(self, name, qtype): ''' Resolves a Blockchain-based (Namecoin) DNS Name via 2 step process using DNSSEC Step 1: ------- Get NS and DS records for the Namecoin name (for example: www.mattdavid.bit) from the Namecoin Client Step 2: ------- For each listed nameserver: - Create a temporary config file for use with unbound - Set Unbound's Trust Anchor to be the given DS records for the Namecoin-based domain name - Do DNSSEC-enabled DNS resolution for the given name / qtype :param name: DNS Record Name Query (for example: www.mattdavid.bit) :param qtype: String representation of query type (for example: A, AAAA, TXT, NS, SOA, etc...) :return: Resolved value if successful, None if un-successful ''' name = name.rstrip('.') if not name.endswith('.bit'): raise ValueError('This is not a valid .bit domain') domains = name.split('.') domains.reverse() if len(domains) < 2: raise ValueError('At least SLD Required') # Get Namecoin-based Domain Info from Namecoin Blockchain nc_domain = self.nc_name_resolver.name_show(domains[1]) if not nc_domain or not nc_domain.get('value'): log.error('No Name Value Data Found for Namecoin-based Domain Name: d/%s' % domains[1]) raise NamecoinValueException('No Name Value Data Found for: d/%s' % domains[1]) nc_value = json.loads(nc_domain.get('value', '{}').replace('\'','"')) if not nc_value.get('ds'): log.error('No DS Records Present for Namecoin-based Domain Name: %s' % name) raise NoDSRecordException() if not nc_value.get('ns'): log.error('No NS Records Present for Namecoin-based Domain Name: %s' % name) raise NoNameserverException() sld = '%s.%s.' % (domains[1], domains[0]) ds_record = ' '.join([str(x) for x in nc_value['ds'][0][0:3]]) # Handle both Hex and Base64 encoding (Base64 is the preferred encoding) per: # https://wiki.namecoin.info/index.php?title=Domain_Name_Specification if re.match('^[0-9a-fA-F]*$', nc_value['ds'][0][3]): ds_record += ' %s' % nc_value['ds'][0][3] else: ds_record += ' %s' % base64.b64decode(nc_value['ds'][0][3]).encode('hex').upper() if qtype in ['DS']: return ds_record ds_ta = '%s IN DS %s' % (sld, ds_record) ns_ctx = ub_ctx() ns_ctx.resolvconf(self.resolv_conf) if not os.path.isfile(self.dnssec_root_key): log.error("Trust anchor missing or inaccessible") raise Exception("Trust anchor is missing or inaccessible: %s" % self.dnssec_root_key) else: ns_ctx.add_ta_file(self.dnssec_root_key) last_error = None for ns in nc_value.get('ns', []): lookup_value = None status, result = ns_ctx.resolve(ns, rdatatype.from_text('A'), rdataclass.from_text('IN')) # NOTE: We do not require secure DNS resolution here because the Blockchain-stored DS records work as the trust anchor # and the signed RRSIG DNS results from the final DNS+DNSSEC lookup will be able to complete the chain of trust if status == 0 and result and result.data and not result.bogus: tmp_config_file = self._build_temp_unbound_config(sld, result.data.as_address_list()[0]) else: last_error = InvalidNameserverException() log.warn('No or Invalid Resolution Result for Nameserver: %s' % ns) continue ctx = ub_ctx() ctx.config(tmp_config_file) ctx.add_ta(str(ds_ta)) _qtype = None try: _qtype = rdatatype.from_text(qtype) except Exception as e: log.error('Unable to get RDATAType for Given Query Type [%s]: %s' % (qtype, str(e))) raise ValueError('Unable to get RDATAType for Query Type %s' % qtype) status, result = ctx.resolve(name, _qtype, rdataclass.from_text('IN')) if status != 0: log.info("DNS Resolution Failed: %s [%s]" % (name, _qtype)) elif status == 0: if not result.secure: log.info("DNS Resolution Returned Insecure Result: %s [%s]" % (name, qtype)) last_error = InsecureResultException() elif result.bogus: log.info("DNS Resolution Returned Bogus Result: %s [%s]" % (name, qtype)) last_error = BogusResultException() elif not result.havedata: log.info("DNS Resolution Returned Empty Result: %s [%s]" % (name, qtype)) last_error = EmptyResultException() else: # Get appropriate data by query type # TODO: Add SOA Record Processing if qtype in ['A','AAAA']: lookup_value = result.data.as_address_list() elif qtype in ['CNAME','TXT','PTR','SPF','CAA','NAPTR']: lookup_value = result.data.as_domain_list() elif qtype in ['MX']: lookup_value = result.data.as_mx_list() elif qtype in ['NS']: lookup_value = nc_value.get('ns', []) elif qtype in ['TLSA']: lookup_value = [NamecoinResolver.tlsa_convert(result.rawdata[0])] else: last_error = NotImplementedError('Unsupported DNS Query Type: %s' % qtype) self._delete_temp_unbound_config(tmp_config_file) if lookup_value: return lookup_value[0] if last_error and isinstance(last_error, NotImplementedError): raise last_error log.error('DNS Resolution Failed: %s [%s]' % (name, qtype)) if last_error: raise last_error return None