def _probe( analyst_cls: Analyst, name: str, config, rdclass, ceiling, query_class_mixin, explicit_delegations, odd_ports, cache, cache_lock, tm, resolver, ): if ceiling is not None and name.is_subdomain(ceiling): c = ceiling else: c = name try: a = analyst_cls( name, rdclass=rdclass, dlv_domain=config.dlv_domain, try_ipv4=config.try_ipv4, try_ipv6=config.try_ipv6, client_ipv4=config.client_ipv4, client_ipv6=config.client_ipv6, query_class_mixin=query_class_mixin, ceiling=c, edns_diagnostics=config.edns_diagnostics, explicit_delegations=explicit_delegations, stop_at_explicit=config.stop_at_explicit, odd_ports=odd_ports, extra_rdtypes=config.rdtypes, explicit_only=config.explicit_only, analysis_cache=cache, cache_level=config.cache_level, analysis_cache_lock=cache_lock, transport_manager=tm, th_factories=config.th_factories, resolver=resolver, ) return a.analyze() # report exceptions related to network connectivity except (NetworkConnectivityException, transport.RemoteQueryTransportError) as e: logger.error("Error analyzing %s: %s" % (humanize_name(name), e)) except: logger.exception("Error analyzing %s" % humanize_name(name)) return None
def clean_date(self): dt = datetime.datetime(self.cleaned_data['date'].year, self.cleaned_data['date'].month, self.cleaned_data['date'].day, \ 23, 59, 59, 999999, tzinfo=utc) self.name_obj = OfflineDomainNameAnalysis.objects.latest(name, dt) if self.name_obj is None: del self.name_obj raise forms.ValidationError( 'No analysis for %s known prior to %s!' % (fmt.humanize_name(name), self.cleaned_data['date']))
def _analyze(args): (cls, name, dlv_domain, try_ipv4, try_ipv6, client_ipv4, client_ipv6, query_class_mixin, ceiling, edns_diagnostics, \ stop_at_explicit, extra_rdtypes, explicit_only, cache, cache_level, cache_lock, th_factories) = args if ceiling is not None and name.is_subdomain(ceiling): c = ceiling else: c = name try: a = cls(name, dlv_domain=dlv_domain, try_ipv4=try_ipv4, try_ipv6=try_ipv6, client_ipv4=client_ipv4, client_ipv6=client_ipv6, query_class_mixin=query_class_mixin, ceiling=c, edns_diagnostics=edns_diagnostics, explicit_delegations=explicit_delegations, stop_at_explicit=stop_at_explicit, odd_ports=odd_ports, extra_rdtypes=extra_rdtypes, explicit_only=explicit_only, analysis_cache=cache, cache_level=cache_level, analysis_cache_lock=cache_lock, transport_manager=tm, th_factories=th_factories, resolver=full_resolver) return a.analyze() # re-raise a KeyboardInterrupt, as this means we've been interrupted except KeyboardInterrupt: raise # report exceptions related to network connectivity except (NetworkConnectivityException, transport.RemoteQueryTransportError) as e: logger.error('Error analyzing %s: %s' % (fmt.humanize_name(name), e)) # don't report EOFError, as that is what is raised if there is a # KeyboardInterrupt in ParallelAnalyst except EOFError: pass except: logger.exception('Error analyzing %s' % fmt.humanize_name(name)) return None
def _analyze(args): (cls, name, dlv_domain, try_ipv4, try_ipv6, client_ipv4, client_ipv6, query_class_mixin, ceiling, edns_diagnostics, \ stop_at_explicit, extra_rdtypes, explicit_only, cache, cache_level, cache_lock) = args if ceiling is not None and name.is_subdomain(ceiling): c = ceiling else: c = name try: a = cls(name, dlv_domain=dlv_domain, try_ipv4=try_ipv4, try_ipv6=try_ipv6, client_ipv4=client_ipv4, client_ipv6=client_ipv6, query_class_mixin=query_class_mixin, ceiling=c, edns_diagnostics=edns_diagnostics, explicit_delegations=explicit_delegations, stop_at_explicit=stop_at_explicit, odd_ports=odd_ports, extra_rdtypes=extra_rdtypes, explicit_only=explicit_only, analysis_cache=cache, cache_level=cache_level, analysis_cache_lock=cache_lock, transport_manager=tm, th_factories=th_factories, resolver=resolver) return a.analyze() # re-raise a KeyboardInterrupt, as this means we've been interrupted except KeyboardInterrupt: raise # report exceptions related to network connectivity except (NetworkConnectivityException, transport.RemoteQueryTransportError) as e: logger.error('Error analyzing %s: %s' % (fmt.humanize_name(name), e)) # don't report EOFError, as that is what is raised if there is a # KeyboardInterrupt in ParallelAnalyst except EOFError: pass except: logger.exception('Error analyzing %s' % fmt.humanize_name(name)) return None
def _analyze((cls, name, dlv_domain, try_ipv4, try_ipv6, client_ipv4, client_ipv6, ceiling, edns_diagnostics, explicit_delegations, extra_rdtypes, explicit_only, cache, cache_level, cache_lock, th_factories)): if ceiling is not None and name.is_subdomain(ceiling): c = ceiling else: c = name try: a = cls(name, dlv_domain=dlv_domain, try_ipv4=try_ipv4, try_ipv6=try_ipv6, client_ipv4=client_ipv4, client_ipv6=client_ipv6, ceiling=c, edns_diagnostics=edns_diagnostics, explicit_delegations=explicit_delegations, extra_rdtypes=extra_rdtypes, explicit_only=explicit_only, analysis_cache=cache, cache_level=cache_level, analysis_cache_lock=cache_lock, transport_manager=tm, th_factories=th_factories) return a.analyze() # re-raise a KeyboardInterrupt, as this means we've been interrupted except KeyboardInterrupt: raise # report exceptions related to network connectivity except (NetworkConnectivityException, transport.RemoteQueryTransportError), e: logger.error('Error analyzing %s: %s' % (fmt.humanize_name(name), e))
def clean_date(self): dt = datetime.datetime( self.cleaned_data["date"].year, self.cleaned_data["date"].month, self.cleaned_data["date"].day, 23, 59, 59, 999999, tzinfo=utc, ) self.name_obj = OfflineDomainNameAnalysis.objects.latest(name, dt) if self.name_obj is None: del self.name_obj raise forms.ValidationError( "No analysis for %s known prior to %s!" % (fmt.humanize_name(name), self.cleaned_data["date"]) )
def _analyze((cls, name, dlv_domain, client_ipv4, client_ipv6, ceiling, edns_diagnostics, explicit_delegations, extra_rdtypes, explicit_only, cache, cache_level, cache_lock)): if ceiling is not None and name.is_subdomain(ceiling): c = ceiling else: c = name try: a = cls(name, dlv_domain=dlv_domain, client_ipv4=client_ipv4, client_ipv6=client_ipv6, ceiling=c, edns_diagnostics=edns_diagnostics, explicit_delegations=explicit_delegations, extra_rdtypes=extra_rdtypes, explicit_only=explicit_only, analysis_cache=cache, cache_level=cache_level, analysis_cache_lock=cache_lock) return a.analyze() # re-raise a KeyboardInterrupt, as this means we've been interrupted except KeyboardInterrupt: raise # don't report EOFError, as that is what is raised if there is a # KeyboardInterrupt in ParallelAnalyst except EOFError: pass except: logger.exception('Error analyzing %s' % fmt.humanize_name(name)) return None
c = name try: a = cls(name, dlv_domain=dlv_domain, try_ipv4=try_ipv4, try_ipv6=try_ipv6, client_ipv4=client_ipv4, client_ipv6=client_ipv6, ceiling=c, edns_diagnostics=edns_diagnostics, explicit_delegations=explicit_delegations, extra_rdtypes=extra_rdtypes, explicit_only=explicit_only, analysis_cache=cache, cache_level=cache_level, analysis_cache_lock=cache_lock, transport_manager=tm, th_factories=th_factories) return a.analyze() # re-raise a KeyboardInterrupt, as this means we've been interrupted except KeyboardInterrupt: raise # report exceptions related to network connectivity except (NetworkConnectivityException, transport.RemoteQueryTransportError), e: logger.error('Error analyzing %s: %s' % (fmt.humanize_name(name), e)) # don't report EOFError, as that is what is raised if there is a # KeyboardInterrupt in ParallelAnalyst except EOFError: pass except: logger.exception('Error analyzing %s' % fmt.humanize_name(name)) return None class BulkAnalyst(object): analyst_cls = PrivateAnalyst def __init__(self, try_ipv4, try_ipv6, client_ipv4, client_ipv6, ceiling, edns_diagnostics, cache_level, explicit_delegations, extra_rdtypes, explicit_only, dlv_domain, th_factories): self.try_ipv4 = try_ipv4 self.try_ipv6 = try_ipv6 self.client_ipv4 = client_ipv4 self.client_ipv6 = client_ipv6 self.ceiling = ceiling self.edns_diagnostics = edns_diagnostics self.cache_level = cache_level self.explicit_delegations = explicit_delegations self.extra_rdtypes = extra_rdtypes
def clean_explicit_delegation(self): resolver = Resolver.from_file('/etc/resolv.conf', StandardRecursiveQueryCD) s = self.cleaned_data['explicit_delegation'] mappings = set() i = 1 for line in s.splitlines(): line = line.strip() if not line: continue # get ride of extra columns cols = line.split() if len(cols) > 1: line = '%s %s' % (cols[0], cols[-1]) try: name, addr = line.split() except ValueError: # first see if it's a plain IP address try: addr = IPAddr(line.strip()) except ValueError: # if not, then assign name to mapping name = line addr = None else: # if it's an IP with no name specified, then create # a name name = 'ns%d' % i i += 1 try: name = dns.name.from_text(name) except: raise forms.ValidationError( 'The domain name was invalid: "%s"' % name) # no address is provided, so query A/AAAA records for the name if addr is None: query_tuples = ((name, dns.rdatatype.A, dns.rdataclass.IN), (name, dns.rdatatype.AAAA, dns.rdataclass.IN)) answer_map = resolver.query_multiple_for_answer( *query_tuples) found_answer = False for a in answer_map.values(): if isinstance(a, DNSAnswer): found_answer = True for a_rr in a.rrset: mappings.add((name, IPAddr(a_rr.to_text()))) # negative responses elif isinstance( a, (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer)): pass # error responses elif isinstance(a, (dns.exception.Timeout, dns.resolver.NoNameservers)): raise forms.ValidationError( 'There was an error resolving "%s". Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name)) if not found_answer: raise forms.ValidationError( '"%s" did not resolve to an address. Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name)) # otherwise, add the address else: if addr and addr[0] == '[' and addr[-1] == ']': addr = addr[1:-1] try: addr = IPAddr(addr) except ValueError: raise forms.ValidationError( 'The IP address was invalid: "%s"' % addr) mappings.add((name, addr)) # if there something in the box, yet no mappings resulted, then raise a # validation error if self.cleaned_data['explicit_delegation'] and not mappings: raise forms.ValidationError( 'Unable to process address records!') return mappings
class DomainNameAnalysisForm(forms.Form): EXTRA_TYPES = ((dns.rdatatype.A, dns.rdatatype.to_text( dns.rdatatype.A)), (dns.rdatatype.AAAA, dns.rdatatype.to_text(dns.rdatatype.AAAA)), (dns.rdatatype.TXT, dns.rdatatype.to_text(dns.rdatatype.TXT)), (dns.rdatatype.PTR, dns.rdatatype.to_text(dns.rdatatype.PTR)), (dns.rdatatype.MX, dns.rdatatype.to_text(dns.rdatatype.MX)), (dns.rdatatype.SOA, dns.rdatatype.to_text(dns.rdatatype.SOA)), (dns.rdatatype.CNAME, dns.rdatatype.to_text(dns.rdatatype.CNAME)), (dns.rdatatype.SRV, dns.rdatatype.to_text(dns.rdatatype.SRV)), (dns.rdatatype.NAPTR, dns.rdatatype.to_text(dns.rdatatype.NAPTR)), (dns.rdatatype.TLSA, dns.rdatatype.to_text(dns.rdatatype.TLSA))) ANALYSIS_TYPES = ((ANALYSIS_TYPE_AUTHORITATIVE, 'Authoritative servers'), (ANALYSIS_TYPE_RECURSIVE, 'Recursive servers')) PERSPECTIVE = (('server', 'DNSViz server (me)'), ('client', 'Web client (you)')) force_ancestor = forms.TypedChoiceField( label='Force ancestor analysis', choices=ANCESTOR_CHOICES, initial=name.to_text(), required=True, coerce=dns.name.from_text, help_text= 'Usually it is sufficient to select the name itself (%s) or its zone, in which case cached values will be used for the analysis of any ancestor names (unless it is determined that they are out of date). Occasionally it is useful to re-analyze some portion of the ancestry, in which case the desired ancestor can be selected. However, the overall analysis will take longer.' % (fmt.humanize_name(name, True))) extra_types = forms.TypedMultipleChoiceField( choices=EXTRA_TYPES, initial=(), required=False, coerce=int, help_text= 'Select any extra RR types to query as part of this analysis. A default set of types will already be queried based on the nature of the name, but any types selected here will assuredly be included.' ) edns_diagnostics = forms.BooleanField( label='EDNS diagnostics', initial=False, required=False, help_text='Issue queries specific to EDNS diagnostics.') explicit_delegation = forms.CharField( initial='', required=False, widget=forms.Textarea(attrs={ 'cols': 50, 'rows': 5 }), help_text= 'If you wish to designate servers explicitly for the "force ancestor" zone (rather than following delegation from the IANA root), enter the server names, one per line. You may optionally include an IPv4 or IPv6 address on the same line as the name.' ) analysis_type = forms.TypedChoiceField( choices=ANALYSIS_TYPES, initial=ANALYSIS_TYPE_AUTHORITATIVE, coerce=int, widget=forms.RadioSelect(), help_text= 'If authoritative analysis is selected, then the authoritative servers will be analyzed, beginning at the root servers--or the servers explicitly designated; if recursive analysis is selected, then the designated recursive servers will be analyzed.' ) perspective = forms.TypedChoiceField( choices=PERSPECTIVE, initial='server', widget=forms.RadioSelect(), help_text= 'If \'DNSViz server\' is selected, then the diagnostic queries will be issued from the DNSViz server. If \'Web client\' is selected, they will be issued from the browser (requires the use of a Java applet).' ) sockname = forms.CharField(widget=forms.HiddenInput(), required=False) def clean(self): cleaned_data = super(DomainNameAnalysisForm, self).clean() if cleaned_data.get('analysis_type', None) == ANALYSIS_TYPE_RECURSIVE and \ not cleaned_data.get('explicit_delegation', None): raise forms.ValidationError( 'If recursive analysis is desired, then servers names and/or addresses must be specified.' ) if cleaned_data.get('perspective', None) == 'client' and \ not cleaned_data.get('sockname', None): raise forms.ValidationError( 'No address supplied for WebSocket') return cleaned_data def clean_explicit_delegation(self): resolver = Resolver.from_file('/etc/resolv.conf', StandardRecursiveQueryCD) s = self.cleaned_data['explicit_delegation'] mappings = set() i = 1 for line in s.splitlines(): line = line.strip() if not line: continue # get ride of extra columns cols = line.split() if len(cols) > 1: line = '%s %s' % (cols[0], cols[-1]) try: name, addr = line.split() except ValueError: # first see if it's a plain IP address try: addr = IPAddr(line.strip()) except ValueError: # if not, then assign name to mapping name = line addr = None else: # if it's an IP with no name specified, then create # a name name = 'ns%d' % i i += 1 try: name = dns.name.from_text(name) except: raise forms.ValidationError( 'The domain name was invalid: "%s"' % name) # no address is provided, so query A/AAAA records for the name if addr is None: query_tuples = ((name, dns.rdatatype.A, dns.rdataclass.IN), (name, dns.rdatatype.AAAA, dns.rdataclass.IN)) answer_map = resolver.query_multiple_for_answer( *query_tuples) found_answer = False for a in answer_map.values(): if isinstance(a, DNSAnswer): found_answer = True for a_rr in a.rrset: mappings.add((name, IPAddr(a_rr.to_text()))) # negative responses elif isinstance( a, (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer)): pass # error responses elif isinstance(a, (dns.exception.Timeout, dns.resolver.NoNameservers)): raise forms.ValidationError( 'There was an error resolving "%s". Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name)) if not found_answer: raise forms.ValidationError( '"%s" did not resolve to an address. Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name)) # otherwise, add the address else: if addr and addr[0] == '[' and addr[-1] == ']': addr = addr[1:-1] try: addr = IPAddr(addr) except ValueError: raise forms.ValidationError( 'The IP address was invalid: "%s"' % addr) mappings.add((name, addr)) # if there something in the box, yet no mappings resulted, then raise a # validation error if self.cleaned_data['explicit_delegation'] and not mappings: raise forms.ValidationError( 'Unable to process address records!') return mappings
def _get_label_for_node(notices, node_name, val): l = None m1 = _node_re.search(node_name) if m1 is not None: t1 = m1.group('node_type') #TODO sort keys by dns name (i.e., not simply by textual name) if t1 == 'RRset': m2 = _rrset_node_re.search(m1.group('remnant')) l = '%s/%s' % (fmt.humanize_name(dns.name.from_text(m2.group('name')), True), m2.group('rdtype')) if m1.group('id') == '0': l += ' (NXDOMAIN)' elif m1.group('id') == '1': l += ' (NODATA)' if m1.group('id') not in ('2', '3'): bisect.insort(notices['RRset status'][val[0]['status']],l) elif t1 == 'DNSKEY': m2 = _dnskey_node_re.search(m1.group('remnant')) l = '%s/DNSKEY (alg %s, id %s)' % (fmt.humanize_name(dns.name.from_text(m2.group('name')), True), m2.group('alg'), m2.group('key_tag')) bisect.insort(notices['DNSKEY/DS/NSEC status'][val[0]['status']],l) elif t1 in ('DS','DLV'): m2 = _ds_node_re.search(m1.group('remnant')) l = '%s/%s (alg %s, id %s)' % (fmt.humanize_name(dns.name.from_text(m2.group('name')), True), t1, m2.group('alg'), m2.group('key_tag')) bisect.insort(notices['DNSKEY/DS/NSEC status'][val[0]['status']],l) elif t1.startswith('NSEC'): m2 = _nsec_node_re.search(m1.group('remnant')) l = '%s proving non-existence of %s/%s' % (t1, fmt.humanize_name(dns.name.from_text(m2.group('name')), True), m2.group('rdtype')) bisect.insort(notices['DNSKEY/DS/NSEC status'][val[0]['status']],l) else: m1 = _edge_re.search(node_name) if m1 is not None: t1 = m1.group('node_type') if t1 == 'del': m2 = _del_re.search(m1.group('remnant')) l = '%s to %s' % (fmt.humanize_name(dns.name.from_text(m2.group('parent')), True), fmt.humanize_name(dns.name.from_text(m2.group('child')), True)) bisect.insort(notices['delegation status'][val[0]['status']],l) elif t1 == 'digest': m2 = _digest_re.search(m1.group('remnant')) l = '%s/%s (alg %s, id %s)' % (fmt.humanize_name(dns.name.from_text(m2.group('name')), True), m2.group('type'), m2.group('alg'), m2.group('key_tag')) elif t1 == 'RRSIG': m2 = _node_re.search(m1.group('remnant')) m3 = _rrsig_dnskey_re.search(m2.group('remnant')) dnskey_str = 'alg %s, id %s' % (m3.group('alg'), m3.group('key_tag')) t2 = m2.group('node_type') if t2 == 'RRset': m3 = _rrset_node_re.search(m2.group('remnant')) l = 'RRSIG %s/%s %s' % (fmt.humanize_name(dns.name.from_text(m3.group('name')), True), m3.group('rdtype'), dnskey_str) elif t2 == 'DNSKEY': m3 = _dnskey_node_re.search(m2.group('remnant')) l = 'RRSIG %s/DNSKEY %s' % (fmt.humanize_name(dns.name.from_text(m3.group('name')), True), dnskey_str) elif t2 in ('DS','DLV'): m3 = _ds_node_re.search(m2.group('remnant')) l = 'RRSIG %s/%s %s' % (fmt.humanize_name(dns.name.from_text(m3.group('name')), True), t2, dnskey_str) elif t2.startswith('NSEC'): m3 = _nsec_node_re.search(m2.group('remnant')) l = 'RRSIG %s proving non-existence of %s/%s %s' % (t2, fmt.humanize_name(dns.name.from_text(m3.group('name')), True), m3.group('rdtype'), dnskey_str) elif t1.startswith('NSEC'): m2 = _nsecc_re.search(m1.group('remnant')) l = '%s proving non-existence of %s/%s' % (m2.group('type'), fmt.humanize_name(dns.name.from_text(m2.group('name')), True), m2.group('rdtype')) elif t1 == 'dname': m2 = _dname_re.search(m1.group('remnant')) l = 'CNAME synthesis of %s' % (fmt.humanize_name(dns.name.from_text(m2.group('name')), True)) else: m1 = _zone_re.search(node_name) if m1 is not None: l = '%s zone' % (fmt.humanize_name(dns.name.from_text(m1.group('name')), True)) return l
def name_addr_mappings_from_string(domain, addr_mappings, delegation_mapping, require_name): global next_port addr_mappings = addr_mappings.split(',') i = 1 for mapping in addr_mappings: # get rid of whitespace mapping = mapping.strip() # Determine whether there is a port stuck on there match = PORT_RE.search(mapping) if match is not None: mapping = match.group(1) port = int(match.group(2)) port_str = ':%d' % port else: port = 53 port_str = '' num_replacements = None # if the value is actually a path, then check it as a zone file if os.path.isfile(mapping): # if this is a file containing delegation records, then read the # file, create a name=value string, and call name_addr_mappings_from_string() if require_name: mappings_from_file = [] try: s = io.open(mapping, 'r', encoding='utf-8').read() except IOError as e: usage('%s: "%s"' % (e.strerror, mapping)) sys.exit(3) try: m = dns.message.from_text(str(';ANSWER\n'+s)) except dns.exception.DNSException as e: usage('Error reading delegation records from %s: "%s"' % (mapping, e)) sys.exit(3) try: ns_rrset = m.find_rrset(m.answer, domain, dns.rdataclass.IN, dns.rdatatype.NS) except KeyError: usage('No NS records for %s found in %s' % (lb2s(domain.canonicalize().to_text()), mapping)) sys.exit(3) for rdata in ns_rrset: a_rrsets = [r for r in m.answer if r.name == rdata.target and r.rdtype in (dns.rdatatype.A, dns.rdatatype.AAAA)] if not a_rrsets or not rdata.target.is_subdomain(domain.parent()): mappings_from_file.append(lb2s(rdata.target.canonicalize().to_text())) else: for a_rrset in a_rrsets: for a_rdata in a_rrset: mappings_from_file.append('%s=%s' % (lb2s(rdata.target.canonicalize().to_text()), IPAddr(a_rdata.address))) name_addr_mappings_from_string(domain, ','.join(mappings_from_file), delegation_mapping, require_name) continue # otherwise (it is the zone proper), just serve the file else: if port_str == '': #TODO assign random port here port = next_port next_port += 1 _serve_zone(domain, mapping, port) name = 'localhost' addr = '127.0.0.1' else: # First determine whether the argument is name=value or simply value try: name, addr = NAME_VAL_DELIM_RE.split(mapping, 1) except ValueError: # Argument is a single value. Now determine whether that value is # a name or an address. try: IPAddr(BRACKETS_RE.sub(r'\1', mapping)) except ValueError: # see if this was an IPv6 address without a port try: IPAddr(mapping + port_str) except ValueError: pass else: usage('Brackets are required around IPv6 addresses.') sys.exit(1) # value is not an address name = mapping addr = None else: if require_name: usage('A name is required to accompany the address for this option.') sys.exit(1) # value is an address name = 'ns%d' % i addr, num_replacements = BRACKETS_RE.subn(r'\1', mapping) i += 1 else: # Argument is name=value addr, num_replacements = BRACKETS_RE.subn(r'\1', addr) if not name: usage('The domain name was empty.') sys.exit(1) # At this point, name is defined, and addr may or may not be defined. # Both are of type str. # Check that the name is valid try: name = dns.name.from_text(name) except dns.exception.DNSException: usage('The domain name was invalid: "%s"' % name) sys.exit(1) # Add the name to the NS RRset delegation_mapping[(domain, dns.rdatatype.NS)].add(dns.rdtypes.ANY.NS.NS(dns.rdataclass.IN, dns.rdatatype.NS, name)) if addr is None: if not require_name: # If no address is provided, query A/AAAA records for the name query_tuples = ((name, dns.rdatatype.A, dns.rdataclass.IN), (name, dns.rdatatype.AAAA, dns.rdataclass.IN)) answer_map = bootstrap_resolver.query_multiple_for_answer(*query_tuples) found_answer = False for (n, rdtype, rdclass) in answer_map: a = answer_map[(n, rdtype, rdclass)] if isinstance(a, DNSAnswer): found_answer = True delegation_mapping[(name, rdtype)] = dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, rdtype, [IPAddr(r.address) for r in a.rrset]) if port != 53: for r in a.rrset: odd_ports[(domain, IPAddr(r.address))] = port # negative responses elif isinstance(a, (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer)): pass # error responses elif isinstance(a, (dns.exception.Timeout, dns.resolver.NoNameservers)): usage('There was an error resolving "%s". Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name)) sys.exit(1) if not found_answer: usage('"%s" did not resolve to an address. Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name)) sys.exit(1) elif not addr: if not require_name: usage('The IP address was empty.') sys.exit(1) else: try: IPAddr(addr) except ValueError: # see if this was an IPv6 address without a port try: IPAddr(addr + port_str) except ValueError: usage('The IP address was invalid: "%s"' % addr) sys.exit(1) else: usage('Brackets are required around IPv6 addresses.') sys.exit(1) if IPAddr(addr).version == 6: if num_replacements < 1: usage('Brackets are required around IPv6 addresses.') sys.exit(1) a_rdtype = dns.rdatatype.AAAA rdtype_cls = dns.rdtypes.IN.AAAA.AAAA else: a_rdtype = dns.rdatatype.A rdtype_cls = dns.rdtypes.IN.A.A if (name, a_rdtype) not in delegation_mapping: delegation_mapping[(name, a_rdtype)] = dns.rrset.RRset(name, dns.rdataclass.IN, a_rdtype) delegation_mapping[(name, a_rdtype)].add(rdtype_cls(dns.rdataclass.IN, a_rdtype, addr)) if port != 53: odd_ports[(domain, IPAddr(addr))] = port
def _get(self, request, name_obj, timestamp, url_subdir, date_form): options_form, values = get_dnssec_options_form_data({}) trusted_keys_explicit = values['tk'] trusted_zones = values['ta'] trusted_keys = trusted_keys_explicit + trusted_zones name_obj.retrieve_all() name_obj.populate_status(trusted_keys) zone_obj = name_obj.zone rdtypes = options_form.cleaned_data['rr'] qrrsets = [(name_obj, name, rdtype) for (name, rdtype) in name_obj.queries if rdtype in rdtypes] # if DANE, then add the A/AAAA records for the DANE host if len(name_obj.name) > 2 and name_obj.name[1] in ('_tcp', '_udp', '_sctp'): dane_host_obj = name_obj.get_dane_hostname() if dane_host_obj is not None: dane_host_obj.retrieve_all() dane_host_obj.populate_status(trusted_keys) if dane_host_obj.zone.name == name_obj.zone.name: if dns.rdatatype.A in rdtypes: qrrsets.append((dane_host_obj, dane_host_obj.name, dns.rdatatype.A)) if dns.rdatatype.AAAA in rdtypes: qrrsets.append((dane_host_obj, dane_host_obj.name, dns.rdatatype.AAAA)) qrrsets.insert(0, (zone_obj, zone_obj.name, dns.rdatatype.NS)) qrrsets.insert(0, (zone_obj, zone_obj.name, dns.rdatatype.DNSKEY)) if zone_obj.parent is not None: qrrsets.insert(0, (zone_obj, zone_obj.name, dns.rdatatype.DS)) parent_all_auth_servers = zone_obj.parent.get_auth_or_designated_servers( ) parent_server_list = [(ip, zone_obj.parent.get_ns_name_for_ip(ip)[0]) for ip in parent_all_auth_servers] parent_server_list.sort(cmp=util.ip_name_cmp) all_auth_servers = zone_obj.get_auth_or_designated_servers() server_list = [(ip, zone_obj.get_ns_name_for_ip(ip)[0]) for ip in all_auth_servers] server_list.sort(cmp=util.ip_name_cmp) response_consistency = [] for my_name_obj, name, rdtype in qrrsets: if rdtype == dns.rdatatype.DS: slist = parent_server_list zone_name = my_name_obj.parent_name() else: slist = server_list zone_name = my_name_obj.zone.name pos_matrix = [] # if all servers are unresponsive, some of the queries enumerated above # might not have been asked if (name, rdtype) not in my_name_obj.queries: continue query = my_name_obj.queries[(name, rdtype)] servers_pos_responses = set() #servers_neg_responses = set() servers_error_responses = set() for rrset_info in query.answer_info: servers_pos_responses.update( [s[0] for s in rrset_info.servers_clients]) #if (name, rdtype) in name_obj.nxdomain_servers_clients: # servers_neg_responses.update([s[0] for s in name_obj.nxdomain_servers_clients[(name, rdtype)]]) #if (name, rdtype) in name_obj.noanswer_servers_clients: # servers_neg_responses.update([s[0] for s in name_obj.noanswer_servers_clients[(name, rdtype)]]) #TODO error responses #TODO NSEC responses for rrset_info in query.answer_info: rrset_servers = set([s[0] for s in rrset_info.servers_clients]) row_grouping = [] row = [] row.append((fmt.humanize_name(rrset_info.rrset.name, True), 'not-styled')) row.append((rrset_info.rrset.ttl, 'not-styled')) row.append((dns.rdatatype.to_text(rrset_info.rrset.rdtype), 'not-styled')) rrset_str = '' rrset_list = [ Response.RdataWrapper(x) for x in rrset_info.rrset ] rrset_list.sort() for rrw in rrset_list: rr = rrw._rdata rr_str = escape(rr.to_text(), quote=True) if rrset_info.rrset.rdtype == dns.rdatatype.DNSKEY: rr_str += ' ; <b>key tag = %d</b>' % Response.DNSKEYMeta.calc_key_tag( rr) rrset_str += '\n<div class="rr">%s</div>' % rr_str row.append((rrset_str, 'not-styled')) status = ('OK', 'valid') row.append(status) for server, names in slist: if server in rrset_servers: row.append(( 'Y', 'valid', )) else: server_queried = False for q in query.queries.values(): if server in q.responses: server_queried = True if server_queried: row.append(('', 'not-styled')) else: row.append(('', 'not-queried', None, 'Server %s not queried for %s/%s.' % (server, fmt.humanize_name(name), dns.rdatatype.to_text(rdtype)))) row_grouping.append(row) for rrsig in my_name_obj.rrsig_status[rrset_info]: rrsig_servers = set([ s[0] for s in rrset_info.rrsig_info[rrsig].servers_clients ]) row = [] row.append(('', 'not-styled')) row.append( (rrset_info.rrsig_info[rrsig].ttl, 'not-styled')) row.append(('RRSIG', 'not-styled')) row.append(('<div class="rr">%s</div>' % rrsig.to_text(), 'not-styled')) try: status = filter( lambda x: x.signature_valid == True, my_name_obj.rrsig_status[rrset_info] [rrsig].values())[0] except IndexError: status = my_name_obj.rrsig_status[rrset_info][ rrsig].values()[0] style = Status.rrsig_status_mapping[ status.validation_status] row.append( (Status.rrsig_status_mapping[status.validation_status], style)) for server, names in slist: if server in rrsig_servers: row.append(('Y', style)) elif server not in rrset_servers: row.append(('', 'not-queried')) else: row.append(('', 'not-styled')) row_grouping.append(row) pos_matrix.append(row_grouping) row_grouping = [] row = [] row.append(('RR count (Answer/Authority/Additional)', 'not-styled', None, None, 4)) row.append(('OK', 'valid')) for server, names in slist: server_queried = False response = None for q in query.queries.values(): if server in q.responses: server_queried = True r = q.responses[server].values()[0] if r.is_complete_response(): response = r break if server_queried and response is not None: answer_ct = 0 for i in response.message.answer: answer_ct += len(i) authority_ct = 0 for i in response.message.authority: authority_ct += len(i) additional_ct = 0 for i in response.message.additional: additional_ct += len(i) if response.message.edns >= 0: additional_ct += 1 row.append( ('%d/%d/%d' % (answer_ct, authority_ct, additional_ct), 'valid')) elif not server_queried: row.append(('', 'not-queried', None, 'Server %s not queried for %s/%s.' % (server, fmt.humanize_name(name), dns.rdatatype.to_text(rdtype)))) elif server: row.append(('', 'not-styled')) row_grouping.append(row) pos_matrix.append(row_grouping) row_grouping = [] row = [] row.append(('Response size (bytes)', 'not-styled', None, None, 4)) row.append(('OK', 'valid')) for server, names in slist: server_queried = False response = None for q in query.queries.values(): if server in q.responses: server_queried = True r = q.responses[server].values()[0] if r.is_complete_response(): response = r break if server_queried and response is not None: row.append((response.msg_size, 'valid')) elif not server_queried: row.append(('', 'not-queried', None, 'Server %s not queried for %s/%s.' % (server, fmt.humanize_name(name), dns.rdatatype.to_text(rdtype)))) elif server: row.append(('', 'not-styled')) row_grouping.append(row) pos_matrix.append(row_grouping) row_grouping = [] row = [] row.append(('Response time (ms)', 'not-styled', None, None, 4)) row.append(('OK', 'valid')) for server, names in slist: server_queried = False response = None for q in query.queries.values(): if server in q.responses: server_queried = True r = q.responses[server].values()[0] if r.is_complete_response(): response = r break if server_queried and response is not None: row.append((int(response.response_time * 1e3), 'valid')) elif not server_queried: row.append(('', 'not-queried', None, 'Server %s not queried for %s/%s.' % (server, fmt.humanize_name(name), dns.rdatatype.to_text(rdtype)))) elif server: row.append(('', 'not-styled')) row_grouping.append(row) pos_matrix.append(row_grouping) if pos_matrix: response_consistency.append( ('Responses for %s/%s' % (fmt.humanize_name( name, True), dns.rdatatype.to_text(rdtype)), slist, pos_matrix)) return render_to_response('responses.html', { 'name_obj': name_obj, 'timestamp': timestamp, 'url_subdir': url_subdir, 'title': name_obj, 'date_form': date_form, 'response_consistency': response_consistency }, context_instance=RequestContext(request))
def name_addr_mappings_from_string(domain, addr_mappings, delegation_mapping, require_name): global next_port addr_mappings = addr_mappings.split(',') i = 1 for mapping in addr_mappings: # get rid of whitespace mapping = mapping.strip() # Determine whether there is a port stuck on there match = PORT_RE.search(mapping) if match is not None: mapping = match.group(1) port = int(match.group(2)) port_str = ':%d' % port else: port = 53 port_str = '' num_replacements = None # if the value is actually a path, then check it as a zone file if os.path.isfile(mapping): # if this is a file containing delegation records, then read the # file, create a name=value string, and call name_addr_mappings_from_string() if require_name: mappings_from_file = [] try: s = io.open(mapping, 'r', encoding='utf-8').read() except IOError as e: usage('%s: "%s"' % (e.strerror, mapping)) sys.exit(3) try: m = dns.message.from_text(str(';ANSWER\n' + s)) except dns.exception.DNSException as e: usage('Error reading delegation records from %s: "%s"' % (mapping, e)) sys.exit(3) try: ns_rrset = m.find_rrset(m.answer, domain, dns.rdataclass.IN, dns.rdatatype.NS) except KeyError: usage('No NS records for %s found in %s' % (lb2s(domain.canonicalize().to_text()), mapping)) sys.exit(3) for rdata in ns_rrset: a_rrsets = [ r for r in m.answer if r.name == rdata.target and r.rdtype in ( dns.rdatatype.A, dns.rdatatype.AAAA) ] if not a_rrsets or not rdata.target.is_subdomain( domain.parent()): mappings_from_file.append( lb2s(rdata.target.canonicalize().to_text())) else: for a_rrset in a_rrsets: for a_rdata in a_rrset: mappings_from_file.append( '%s=%s' % (lb2s( rdata.target.canonicalize().to_text()), IPAddr(a_rdata.address))) name_addr_mappings_from_string(domain, ','.join(mappings_from_file), delegation_mapping, require_name) continue # otherwise (it is the zone proper), just serve the file else: if port_str == '': #TODO assign random port here port = next_port next_port += 1 _serve_zone(domain, mapping, port) name = 'localhost' addr = '127.0.0.1' else: # First determine whether the argument is name=value or simply value try: name, addr = NAME_VAL_DELIM_RE.split(mapping, 1) except ValueError: # Argument is a single value. Now determine whether that value is # a name or an address. try: IPAddr(BRACKETS_RE.sub(r'\1', mapping)) except ValueError: # see if this was an IPv6 address without a port try: IPAddr(mapping + port_str) except ValueError: pass else: usage('Brackets are required around IPv6 addresses.') sys.exit(1) # value is not an address name = mapping addr = None else: if require_name: usage( 'A name is required to accompany the address for this option.' ) sys.exit(1) # value is an address name = 'ns%d' % i addr, num_replacements = BRACKETS_RE.subn(r'\1', mapping) i += 1 else: # Argument is name=value addr, num_replacements = BRACKETS_RE.subn(r'\1', addr) if not name: usage('The domain name was empty.') sys.exit(1) # At this point, name is defined, and addr may or may not be defined. # Both are of type str. # Check that the name is valid try: name = dns.name.from_text(name) except dns.exception.DNSException: usage('The domain name was invalid: "%s"' % name) sys.exit(1) # Add the name to the NS RRset delegation_mapping[(domain, dns.rdatatype.NS)].add( dns.rdtypes.ANY.NS.NS(dns.rdataclass.IN, dns.rdatatype.NS, name)) if addr is None: if not require_name: # If no address is provided, query A/AAAA records for the name query_tuples = ((name, dns.rdatatype.A, dns.rdataclass.IN), (name, dns.rdatatype.AAAA, dns.rdataclass.IN)) answer_map = stub_resolver.query_multiple_for_answer( *query_tuples) found_answer = False for (n, rdtype, rdclass) in answer_map: a = answer_map[(n, rdtype, rdclass)] if isinstance(a, DNSAnswer): found_answer = True delegation_mapping[( name, rdtype)] = dns.rrset.from_text_list( name, 0, dns.rdataclass.IN, rdtype, [IPAddr(r.address) for r in a.rrset]) if port != 53: for r in a.rrset: odd_ports[(domain, IPAddr(r.address))] = port # negative responses elif isinstance( a, (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer)): pass # error responses elif isinstance( a, (dns.exception.Timeout, dns.resolver.NoNameservers)): usage( 'There was an error resolving "%s". Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name)) sys.exit(1) if not found_answer: usage( '"%s" did not resolve to an address. Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name)) sys.exit(1) elif not addr: if not require_name: usage('The IP address was empty.') sys.exit(1) else: try: IPAddr(addr) except ValueError: # see if this was an IPv6 address without a port try: IPAddr(addr + port_str) except ValueError: usage('The IP address was invalid: "%s"' % addr) sys.exit(1) else: usage('Brackets are required around IPv6 addresses.') sys.exit(1) if IPAddr(addr).version == 6: if num_replacements < 1: usage('Brackets are required around IPv6 addresses.') sys.exit(1) a_rdtype = dns.rdatatype.AAAA rdtype_cls = dns.rdtypes.IN.AAAA.AAAA else: a_rdtype = dns.rdatatype.A rdtype_cls = dns.rdtypes.IN.A.A if (name, a_rdtype) not in delegation_mapping: delegation_mapping[(name, a_rdtype)] = dns.rrset.RRset( name, dns.rdataclass.IN, a_rdtype) delegation_mapping[(name, a_rdtype)].add( rdtype_cls(dns.rdataclass.IN, a_rdtype, addr)) if port != 53: odd_ports[(domain, IPAddr(addr))] = port
def _get(self, request, name_obj, timestamp, url_subdir, date_form): options_form, values = get_dnssec_options_form_data({}) trusted_keys_explicit = values['tk'] trusted_zones = values['ta'] trusted_keys = trusted_keys_explicit + trusted_zones name_obj.retrieve_all() name_obj.populate_status(trusted_keys) zone_obj = name_obj.zone delegation_matrix = [] def stealth_cmp(x, y): return cmp((y[0], x[1], x[2]), (x[0], y[1], y[2])) all_names_list = list(zone_obj.get_ns_names()) if not all_names_list: all_names_list = list(zone_obj.get_auth_ns_ip_mapping()) all_names_list.sort() if zone_obj.parent is not None and not zone_obj.get_auth_or_designated_servers( ).difference(zone_obj.parent.get_auth_or_designated_servers()): no_non_auth_parent_msg = 'All %s servers are also authoritative for %s' % ( fmt.humanize_name( zone_obj.parent_name()), fmt.humanize_name(zone_obj.name)) else: no_non_auth_parent_msg = None #XXX need something equivalent here for lack of authoritative response for NS show_msg = False ips_from_child = zone_obj.get_servers_in_child() ips_from_parent = zone_obj.get_servers_in_parent() for name in all_names_list: if zone_obj.parent is not None: in_bailiwick = name.is_subdomain(zone_obj.parent_name()) glue_required = name.is_subdomain(zone_obj.name) else: in_bailiwick = None glue_required = None parent_status = { 'in_bailiwick': in_bailiwick, 'glue_required': glue_required } row = [] row.append(fmt.humanize_name(name)) # (t/f in parent), (glue IPs (or error, if missing)), (real IPs) if zone_obj.get_ns_names_in_parent(): glue_mapping = zone_obj.get_glue_ip_mapping() parent_status['in_parent'] = name in glue_mapping glue_ips_v4 = filter(lambda x: x.version == 4, glue_mapping.get(name, set())) glue_ips_v4.sort() glue_ips_v6 = filter(lambda x: x.version == 6, glue_mapping.get(name, set())) glue_ips_v6.sort() else: glue_ips_v4 = [] glue_ips_v6 = [] if zone_obj.delegation_status == Status.DELEGATION_STATUS_INCOMPLETE: parent_status['in_parent'] = False else: parent_status['in_parent'] = None show_msg = True row.append({ 'parent_status': parent_status, 'glue_ips_v4': glue_ips_v4, 'glue_ips_v6': glue_ips_v6 }) # (t/f in parent), (glue IPs (or error, if missing)), (real IPs) names_in_child = zone_obj.get_ns_names_in_child() if names_in_child: in_child = name in zone_obj.get_ns_names_in_child() #XXX #elif zone_obj.get_servers_authoritative_for_query(zone_obj.name, dns.rdatatype.NS): # in_child = None else: in_child = False auth_mapping = zone_obj.get_auth_ns_ip_mapping() auth_ips_v4 = filter(lambda x: x.version == 4, auth_mapping.get(name, set())) auth_ips_v4.sort() auth_ips_v6 = filter(lambda x: x.version == 6, auth_mapping.get(name, set())) auth_ips_v6.sort() row.append({ 'in_child': in_child, 'auth_ips_v4': auth_ips_v4, 'auth_ips_v6': auth_ips_v6 }) delegation_matrix.append(row) stealth_matrix = [] stealth_rows = [] for server in zone_obj.get_stealth_servers(): names, ancestor_zone = zone_obj.get_ns_name_for_ip(server) stealth_rows.append((ancestor_zone, names, server)) stealth_rows.sort(cmp=stealth_cmp) for ancestor_zone, names, server in stealth_rows: names = map(fmt.humanize_name, names) if ancestor_zone is not None: ancestor_zone = fmt.humanize_name(ancestor_zone) row = (names, ancestor_zone, server) stealth_matrix.append(row) return render_to_response('servers.html', { 'name_obj': name_obj, 'timestamp': timestamp, 'url_subdir': url_subdir, 'title': name_obj, 'date_form': date_form, 'zone_obj': zone_obj, 'delegation': delegation_matrix, 'stealth': stealth_matrix, 'no_non_auth_parent_msg': no_non_auth_parent_msg, 'show_msg': show_msg, 'ips_from_parent': ips_from_parent, 'ips_from_child': ips_from_child }, context_instance=RequestContext(request))
def name_addr_mappings_from_string(mappings): mappings_set = set() mappings = mappings.split(',') i = 1 for mapping in mappings: try: name, addr = mapping.rsplit('=', 1) except ValueError: # first see if it's a plain IP address try: addr = IPAddr(mapping.strip()) except ValueError: # if not, then assign name to mapping name = mapping addr = None else: # if it's an IP with no name specified, then create # a name name = 'ns%d' % i i += 1 else: addr = addr.strip() name = name.strip() try: name = dns.name.from_text(name) except dns.exception.DNSException: usage('The domain name was invalid: "%s"' % name) sys.exit(1) # no address is provided, so query A/AAAA records for the name if addr is None: query_tuples = ((name, dns.rdatatype.A, dns.rdataclass.IN), (name, dns.rdatatype.AAAA, dns.rdataclass.IN)) answer_map = resolver.query_multiple_for_answer(*query_tuples) found_answer = False for a in answer_map.values(): if isinstance(a, DNSAnswer): found_answer = True for a_rr in a.rrset: mappings_set.add((name, IPAddr(a_rr.to_text()))) # negative responses elif isinstance(a, (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer)): pass # error responses elif isinstance(a, (dns.exception.Timeout, dns.resolver.NoNameservers)): usage('There was an error resolving "%s". Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name)) sys.exit(1) if not found_answer: usage('"%s" did not resolve to an address. Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name)) sys.exit(1) # otherwise, add the address elif not addr: usage('No IP address was supplied.') sys.exit(1) else: if addr and addr[0] == '[' and addr[-1] == ']': addr = addr[1:-1] try: addr = IPAddr(addr) except ValueError: usage('The IP address was invalid: "%s"' % addr) sys.exit(1) mappings_set.add((name, addr)) return mappings_set
def clean_explicit_delegation(self): resolver = Resolver.from_file("/etc/resolv.conf", StandardRecursiveQueryCD) s = self.cleaned_data["explicit_delegation"] mappings = set() i = 1 for line in s.splitlines(): line = line.strip() if not line: continue # get ride of extra columns cols = line.split() if len(cols) > 1: line = "%s %s" % (cols[0], cols[-1]) try: name, addr = line.split() except ValueError: # first see if it's a plain IP address try: addr = IPAddr(line.strip()) except ValueError: # if not, then assign name to mapping name = line addr = None else: # if it's an IP with no name specified, then create # a name name = "ns%d" % i i += 1 try: name = dns.name.from_text(name) except: raise forms.ValidationError('The domain name was invalid: "%s"' % name) # no address is provided, so query A/AAAA records for the name if addr is None: query_tuples = ( (name, dns.rdatatype.A, dns.rdataclass.IN), (name, dns.rdatatype.AAAA, dns.rdataclass.IN), ) answer_map = resolver.query_multiple_for_answer(*query_tuples) found_answer = False for a in answer_map.values(): if isinstance(a, DNSAnswer): found_answer = True for a_rr in a.rrset: mappings.add((name, IPAddr(a_rr.to_text()))) # negative responses elif isinstance(a, (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer)): pass # error responses elif isinstance(a, (dns.exception.Timeout, dns.resolver.NoNameservers)): raise forms.ValidationError( 'There was an error resolving "%s". Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name) ) if not found_answer: raise forms.ValidationError( '"%s" did not resolve to an address. Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name) ) # otherwise, add the address else: if addr and addr[0] == "[" and addr[-1] == "]": addr = addr[1:-1] try: addr = IPAddr(addr) except ValueError: raise forms.ValidationError('The IP address was invalid: "%s"' % addr) mappings.add((name, addr)) # if there something in the box, yet no mappings resulted, then raise a # validation error if self.cleaned_data["explicit_delegation"] and not mappings: raise forms.ValidationError("Unable to process address records!") return mappings
def domain_analysis_form(name): ANCESTOR_CHOICES = [(name.to_text(), fmt.humanize_name(name, True))] n = name while n != dns.name.root: n = n.parent() ANCESTOR_CHOICES.append((n.to_text(), fmt.humanize_name(n, True))) ANCESTOR_CHOICES.reverse() class DomainNameAnalysisForm(forms.Form): EXTRA_TYPES = ( (dns.rdatatype.A, dns.rdatatype.to_text(dns.rdatatype.A)), (dns.rdatatype.AAAA, dns.rdatatype.to_text(dns.rdatatype.AAAA)), (dns.rdatatype.TXT, dns.rdatatype.to_text(dns.rdatatype.TXT)), (dns.rdatatype.PTR, dns.rdatatype.to_text(dns.rdatatype.PTR)), (dns.rdatatype.MX, dns.rdatatype.to_text(dns.rdatatype.MX)), (dns.rdatatype.SOA, dns.rdatatype.to_text(dns.rdatatype.SOA)), (dns.rdatatype.CNAME, dns.rdatatype.to_text(dns.rdatatype.CNAME)), (dns.rdatatype.SRV, dns.rdatatype.to_text(dns.rdatatype.SRV)), (dns.rdatatype.NAPTR, dns.rdatatype.to_text(dns.rdatatype.NAPTR)), (dns.rdatatype.TLSA, dns.rdatatype.to_text(dns.rdatatype.TLSA)), ) ANALYSIS_TYPES = ( (ANALYSIS_TYPE_AUTHORITATIVE, "Authoritative servers"), (ANALYSIS_TYPE_RECURSIVE, "Recursive servers"), ) force_ancestor = forms.TypedChoiceField( label="Force ancestor analysis", choices=ANCESTOR_CHOICES, initial=name.to_text(), required=True, coerce=dns.name.from_text, help_text="Usually it is sufficient to select the name itself (%s) or its zone, in which case cached values will be used for the analysis of any ancestor names (unless it is determined that they are out of date). Occasionally it is useful to re-analyze some portion of the ancestry, in which case the desired ancestor can be selected. However, the overall analysis will take longer." % (fmt.humanize_name(name, True)), ) extra_types = forms.TypedMultipleChoiceField( choices=EXTRA_TYPES, initial=(), required=False, coerce=int, help_text="Select any extra RR types to query as part of this analysis. A default set of types will already be queried based on the nature of the name, but any types selected here will assuredly be included.", ) edns_diagnostics = forms.BooleanField( label="EDNS diagnostics", initial=False, required=False, help_text="Issue queries specific to EDNS diagnostics.", ) explicit_delegation = forms.CharField( initial="", required=False, widget=forms.Textarea(attrs={"cols": 50, "rows": 5}), help_text='If you wish to designate servers explicitly for the "force ancestor" zone (rather than following delegation from the IANA root), enter the server names, one per line. You may optionally include an IPv4 or IPv6 address on the same line as the name.', ) analysis_type = forms.TypedChoiceField( choices=ANALYSIS_TYPES, initial=ANALYSIS_TYPE_AUTHORITATIVE, coerce=int, widget=forms.RadioSelect(), help_text="If authoritative analysis is selected, then the authoritative servers will be analyzed, beginning at the root servers--or the servers explicitly designated; if recursive analysis is selected, then the designated recursive servers will be analyzed.", ) def clean(self): cleaned_data = super(DomainNameAnalysisForm, self).clean() if cleaned_data.get("analysis_type", None) == ANALYSIS_TYPE_RECURSIVE and not cleaned_data.get( "explicit_delegation", None ): raise forms.ValidationError( "If recursive analysis is desired, then servers names and/or addresses must be specified." ) return cleaned_data def clean_explicit_delegation(self): resolver = Resolver.from_file("/etc/resolv.conf", StandardRecursiveQueryCD) s = self.cleaned_data["explicit_delegation"] mappings = set() i = 1 for line in s.splitlines(): line = line.strip() if not line: continue # get ride of extra columns cols = line.split() if len(cols) > 1: line = "%s %s" % (cols[0], cols[-1]) try: name, addr = line.split() except ValueError: # first see if it's a plain IP address try: addr = IPAddr(line.strip()) except ValueError: # if not, then assign name to mapping name = line addr = None else: # if it's an IP with no name specified, then create # a name name = "ns%d" % i i += 1 try: name = dns.name.from_text(name) except: raise forms.ValidationError('The domain name was invalid: "%s"' % name) # no address is provided, so query A/AAAA records for the name if addr is None: query_tuples = ( (name, dns.rdatatype.A, dns.rdataclass.IN), (name, dns.rdatatype.AAAA, dns.rdataclass.IN), ) answer_map = resolver.query_multiple_for_answer(*query_tuples) found_answer = False for a in answer_map.values(): if isinstance(a, DNSAnswer): found_answer = True for a_rr in a.rrset: mappings.add((name, IPAddr(a_rr.to_text()))) # negative responses elif isinstance(a, (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer)): pass # error responses elif isinstance(a, (dns.exception.Timeout, dns.resolver.NoNameservers)): raise forms.ValidationError( 'There was an error resolving "%s". Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name) ) if not found_answer: raise forms.ValidationError( '"%s" did not resolve to an address. Please specify an address or use a name that resolves properly.' % fmt.humanize_name(name) ) # otherwise, add the address else: if addr and addr[0] == "[" and addr[-1] == "]": addr = addr[1:-1] try: addr = IPAddr(addr) except ValueError: raise forms.ValidationError('The IP address was invalid: "%s"' % addr) mappings.add((name, addr)) # if there something in the box, yet no mappings resulted, then raise a # validation error if self.cleaned_data["explicit_delegation"] and not mappings: raise forms.ValidationError("Unable to process address records!") return mappings return DomainNameAnalysisForm