def main(argv): global tm global resolver try: try: opts, args = getopt.getopt(argv[1:], 'f:d:l:c:r:t:64b:u:kmpo:a:R:x:EAs:Fh') except getopt.GetoptError, e: usage(str(e)) sys.exit(1) tm = transport.DNSQueryTransportManager() resolver = Resolver.from_file('/etc/resolv.conf', StandardRecursiveQueryCD, transport_manager=tm) # get all the -x options explicit_delegations = {} client_ipv4 = None client_ipv6 = None for opt, arg in opts: if opt == '-x': try: domain, mappings = arg.split(':', 1) except ValueError: usage('Incorrect usage of -x option: "%s"' % arg) sys.exit(1) domain = domain.strip() mappings = mappings.strip() try: domain = dns.name.from_text(domain) except dns.exception.DNSException: usage('The domain name was invalid: "%s"' % domain) sys.exit(1) if not mappings: usage('Incorrect usage of -x option: "%s"' % arg) sys.exit(1) if domain not in explicit_delegations: explicit_delegations[domain] = set() explicit_delegations[domain].update(name_addr_mappings_from_string(mappings)) elif opt == '-b': try: addr = IPAddr(arg) except ValueError: usage('The IP address was invalid: "%s"' % arg) sys.exit(1) if addr.version == 4: client_ipv4 = addr fam = socket.AF_INET else: client_ipv6 = addr fam = socket.AF_INET6 try: s = socket.socket(fam) s.bind((addr, 0)) del s except socket.error, e: if e.errno == errno.EADDRNOTAVAIL: usage('Cannot bind to specified IP address: "%s"' % addr) sys.exit(1)
def setUp(self): self.tm = transport.DNSQueryTransportManager() self.resolver = Resolver.from_file('/etc/resolv.conf', StandardRecursiveQueryCD, transport_manager=self.tm) self.helper = DomainListArgHelper(self.resolver) self.logger = logging.getLogger() for handler in self.logger.handlers: self.logger.removeHandler(handler) self.logger.addHandler(logging.NullHandler()) try: ArgHelper.bindable_ip('::1') except argparse.ArgumentTypeError: self.use_ipv6 = False else: self.use_ipv6 = True self.first_port = ZoneFileToServe._next_free_port self.custom_query_mixin_edns_options_orig = CustomQueryMixin.edns_options[:]
def get_explicit_delegations(tm, query_authoritative_servers: bool, server_list: typing.Optional[str]): explicit_delegations = {} odd_ports = {} if not query_authoritative_servers: bootstrap_resolver = Resolver.from_file("/etc/resolv.conf", StandardRecursiveQueryCD, transport_manager=tm) explicit_delegations[(WILDCARD_EXPLICIT_DELEGATION, dns.rdatatype.NS)] = dns.rrset.RRset( WILDCARD_EXPLICIT_DELEGATION, dns.rdataclass.IN, dns.rdatatype.NS) if server_list: _name_addr_mappings_from_string( WILDCARD_EXPLICIT_DELEGATION, server_list, explicit_delegations, odd_ports, ) else: for i, server in enumerate(bootstrap_resolver._servers): if IPAddr(server).version == 6: rdtype = dns.rdatatype.AAAA else: rdtype = dns.rdatatype.A name = dns.name.from_text("ns%d" % i) explicit_delegations[(WILDCARD_EXPLICIT_DELEGATION, dns.rdatatype.NS)].add( dns.rdtypes.ANY.NS.NS( dns.rdataclass.IN, dns.rdatatype.NS, name)) if (name, rdtype) not in explicit_delegations: explicit_delegations[(name, rdtype)] = dns.rrset.RRset( name, dns.rdataclass.IN, rdtype) explicit_delegations[(name, rdtype)].add( dns.rdata.from_text(dns.rdataclass.IN, rdtype, server)) return (explicit_delegations, odd_ports)
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 main(argv): global tm global th_factories global resolver global bootstrap_resolver global explicit_delegations global odd_ports global next_port try: try: opts, args = getopt.getopt(argv[1:], 'f:d:l:c:r:t:64b:u:kmpo:a:R:x:N:D:ne:EAs:Fh') except getopt.GetoptError as e: usage(str(e)) sys.exit(1) _init_tm() bootstrap_resolver = Resolver.from_file('/etc/resolv.conf', StandardRecursiveQueryCD, transport_manager=tm) # get all the options for which there might be multiple values explicit_delegations = {} odd_ports = {} stop_at_explicit = {} client_ipv4 = None client_ipv6 = None delegation_info = {} for opt, arg in opts: if opt in ('-x', '-N'): try: domain, mappings = arg.split(':', 1) except ValueError: usage('Incorrect usage of %s option: "%s"' % (opt, arg)) sys.exit(1) domain = domain.strip() mappings = mappings.strip() match = STOP_RE.search(domain) if match is not None: if opt == '-N': usage('Incorrect usage of %s option: "%s"' % (opt, arg)) sys.exit(1) domain = match.group(1) try: domain = dns.name.from_text(domain) except dns.exception.DNSException: usage('The domain name was invalid: "%s"' % domain) sys.exit(1) if opt == '-N' and domain == dns.name.root: usage('The root zone cannot be used with option -N.') sys.exit(1) if match is not None: stop_at_explicit[domain] = True else: stop_at_explicit[domain] = False if opt == '-N': if domain == dns.name.root: usage('The root zone cannot be used with option -N.') sys.exit(1) parent = domain.parent() if parent not in delegation_info: delegation_info[parent] = {} delegation_mapping = delegation_info[parent] else: delegation_mapping = explicit_delegations if not mappings: usage('Incorrect usage of %s option: "%s"' % (arg, opt)) sys.exit(1) if (domain, dns.rdatatype.NS) not in delegation_mapping: delegation_mapping[(domain, dns.rdatatype.NS)] = dns.rrset.RRset(domain, dns.rdataclass.IN, dns.rdatatype.NS) name_addr_mappings_from_string(domain, mappings, delegation_mapping, opt == '-N') elif opt == '-D': try: domain, ds_str = arg.split(':', 1) except ValueError: usage('Incorrect usage of %s option: "%s"' % (opt, arg)) sys.exit(1) domain = domain.strip() ds_str = ds_str.strip() try: domain = dns.name.from_text(domain) except dns.exception.DNSException: usage('The domain name was invalid: "%s"' % domain) sys.exit(1) parent = domain.parent() if parent not in delegation_info: delegation_info[parent] = {} delegation_mapping = delegation_info[parent] if not ds_str: usage('Incorrect usage of %s option: "%s"' % (arg, opt)) sys.exit(1) if (domain, dns.rdatatype.DS) not in delegation_mapping: delegation_mapping[(domain, dns.rdatatype.DS)] = dns.rrset.RRset(domain, dns.rdataclass.IN, dns.rdatatype.DS) ds_from_string(domain, ds_str.strip(), delegation_mapping) elif opt == '-b': try: addr = IPAddr(arg) except ValueError: usage('The IP address was invalid: "%s"' % arg) sys.exit(1) if addr.version == 4: client_ipv4 = addr fam = socket.AF_INET else: client_ipv6 = addr fam = socket.AF_INET6 try: s = socket.socket(fam) s.bind((addr, 0)) del s except socket.error as e: if e.errno == errno.EADDRNOTAVAIL: usage('Cannot bind to specified IP address: "%s"' % addr) sys.exit(1) opts = dict(opts) if '-h' in opts: usage() sys.exit(0) if not ('-f' in opts or args) and '-r' not in opts: usage('When -r is not used, either -f must be used or domain names must be supplied as command line arguments.') sys.exit(1) if '-f' in opts and args: usage('If -f is used, then domain names may not supplied as command line arguments.') sys.exit(1) if '-A' in opts and '-s' in opts: usage('If -A is used, then -s cannot be used.') sys.exit(1) if '-x' in opts and '-A' not in opts: usage('-x may only be used in conjunction with -A.') sys.exit(1) if '-N' in opts and '-A' not in opts: usage('-N may only be used in conjunction with -A.') sys.exit(1) if '-D' in opts and '-N' not in opts: #TODO retrieve NS/A/AAAA if -D is specified but -N is not usage('-D may only be used in conjunction with -N.') sys.exit(1) if '-4' in opts and '-6' in opts: usage('-4 and -6 may not be used together.') sys.exit(1) if '-a' in opts: try: ceiling = dns.name.from_text(opts['-a']) except dns.exception.DNSException: usage('The domain name was invalid: "%s"' % opts['-a']) sys.exit(1) elif '-A' in opts: ceiling = None else: ceiling = dns.name.root if '-R' in opts: explicit_only = True try: rdtypes = opts['-R'].split(',') except ValueError: usage('The list of types was invalid: "%s"' % opts['-R']) sys.exit(1) try: rdtypes = [dns.rdatatype.from_text(x) for x in rdtypes] except dns.rdatatype.UnknownRdatatype: usage('The list of types was invalid: "%s"' % opts['-R']) sys.exit(1) else: rdtypes = None explicit_only = False # if neither is specified, then they're both tried if '-4' not in opts and '-6' not in opts: try_ipv4 = True try_ipv6 = True # if one or the other is specified, then only the one specified is # tried else: if '-4' in opts: try_ipv4 = True try_ipv6 = False else: # -6 in opts try_ipv4 = False try_ipv6 = True for domain in delegation_info: if (domain, dns.rdatatype.NS) in explicit_delegations: usage('Cannot use "%s" with -x if its child is specified with -N' % lb2s(domain.canonicalize().to_text())) sys.exit(1) port = next_port next_port += 1 _create_and_serve_zone(domain, delegation_info[domain], port) localhost = dns.name.from_text('localhost') loopback = IPAddr('127.0.0.1') explicit_delegations[(domain, dns.rdatatype.NS)] = dns.rrset.RRset(domain, dns.rdataclass.IN, dns.rdatatype.NS) explicit_delegations[(domain, dns.rdatatype.NS)].add(dns.rdtypes.ANY.NS.NS(dns.rdataclass.IN, dns.rdatatype.NS, localhost)) explicit_delegations[(localhost, dns.rdatatype.A)] = dns.rrset.RRset(localhost, dns.rdataclass.IN, dns.rdatatype.A) explicit_delegations[(localhost, dns.rdatatype.A)].add(dns.rdtypes.IN.A.A(dns.rdataclass.IN, dns.rdatatype.A, loopback)) odd_ports[(domain, loopback)] = port stop_at_explicit[domain] = True if '-A' not in opts: if '-t' in opts: cls = RecursiveParallelAnalyst else: cls = RecursiveBulkAnalyst explicit_delegations[(WILDCARD_EXPLICIT_DELEGATION, dns.rdatatype.NS)] = dns.rrset.RRset(WILDCARD_EXPLICIT_DELEGATION, dns.rdataclass.IN, dns.rdatatype.NS) if '-s' in opts: name_addr_mappings_from_string(WILDCARD_EXPLICIT_DELEGATION, opts['-s'], explicit_delegations, False) else: for i, server in enumerate(bootstrap_resolver._servers): if IPAddr(server).version == 6: rdtype = dns.rdatatype.AAAA else: rdtype = dns.rdatatype.A name = dns.name.from_text('ns%d' % i) explicit_delegations[(WILDCARD_EXPLICIT_DELEGATION, dns.rdatatype.NS)].add(dns.rdtypes.ANY.NS.NS(dns.rdataclass.IN, dns.rdatatype.NS, name)) if (name, rdtype) not in explicit_delegations: explicit_delegations[(name, rdtype)] = dns.rrset.RRset(name, dns.rdataclass.IN, rdtype) explicit_delegations[(name, rdtype)].add(dns.rdata.from_text(dns.rdataclass.IN, rdtype, server)) else: if '-t' in opts: cls = ParallelAnalyst else: cls = BulkAnalyst edns_diagnostics = '-E' in opts if '-u' in opts: # check that version is >= 2.7.9 if HTTPS is requested if opts['-u'].startswith('https'): vers0, vers1, vers2 = sys.version_info[:3] if (2, 7, 9) > (vers0, vers1, vers2): sys.stderr.write('python version >= 2.7.9 is required to use a DNS looking glass with HTTPS.\n') sys.exit(1) url = urlparse.urlparse(opts['-u']) if url.scheme in ('http', 'https'): th_factories = (transport.DNSQueryTransportHandlerHTTPFactory(opts['-u'], insecure='-k' in opts),) elif url.scheme == 'ws': if url.hostname is not None: usage('WebSocket URL must designate a local UNIX domain socket.') sys.exit(1) th_factories = (transport.DNSQueryTransportHandlerWebSocketServerFactory(url.path),) elif url.scheme == 'ssh': th_factories = (transport.DNSQueryTransportHandlerRemoteCmdFactory(opts['-u']),) else: usage('Unsupported URL scheme: "%s"' % opts['-u']) sys.exit(1) else: th_factories = None if '-l' in opts: try: dlv_domain = dns.name.from_text(opts['-l']) except dns.exception.DNSException: usage('The domain name was invalid: "%s"' % opts['-l']) sys.exit(1) else: dlv_domain = None # the following option is not documented in usage, as it doesn't # apply to most users try: cache_level = int(opts['-c']) except (KeyError, ValueError): cache_level = None try: processes = int(opts.get('-t', 1)) except ValueError: usage('The number of threads used must be greater than 0.') sys.exit(1) if processes < 1: usage('The number of threads used must be greater than 0.') sys.exit(1) try: val = int(opts.get('-d', 2)) except ValueError: usage('The debug value must be an integer between 0 and 3.') sys.exit(1) if val < 0 or val > 3: usage('The debug value must be an integer between 0 and 3.') sys.exit(1) if val > 2: debug_level = logging.DEBUG elif val > 1: debug_level = logging.INFO elif val > 0: debug_level = logging.WARNING else: debug_level = logging.ERROR handler = logging.StreamHandler() handler.setLevel(debug_level) logger.addHandler(handler) logger.setLevel(debug_level) if '-A' in opts: if try_ipv4 and get_client_address(A_ROOT_IPV4) is None: logger.warning('No global IPv4 connectivity detected') if try_ipv6 and get_client_address(A_ROOT_IPV6) is None: logger.warning('No global IPv6 connectivity detected') if '-r' in opts: if opts['-r'] == '-': opt_r = sys.stdin.fileno() else: opt_r = opts['-r'] try: analysis_str = io.open(opt_r, 'r', encoding='utf-8').read() except IOError as e: logger.error('%s: "%s"' % (e.strerror, opts.get('-r', '-'))) sys.exit(3) try: analysis_structured = json.loads(analysis_str) except ValueError: logger.error('There was an error parsing the json input: "%s"' % opts['-r']) sys.exit(3) # check version if '_meta._dnsviz.' not in analysis_structured or 'version' not in analysis_structured['_meta._dnsviz.']: logger.error('No version information in JSON input.') sys.exit(3) try: major_vers, minor_vers = [int(x) for x in str(analysis_structured['_meta._dnsviz.']['version']).split('.', 1)] except ValueError: logger.error('Version of JSON input is invalid: %s' % analysis_structured['_meta._dnsviz.']['version']) sys.exit(3) # ensure major version is a match and minor version is no greater # than the current minor version curr_major_vers, curr_minor_vers = [int(x) for x in str(DNS_RAW_VERSION).split('.', 1)] if major_vers != curr_major_vers or minor_vers > curr_minor_vers: logger.error('Version %d.%d of JSON input is incompatible with this software.' % (major_vers, minor_vers)) sys.exit(3) names = [] if '-f' in opts: if opts['-f'] == '-': opts['-f'] = sys.stdin.fileno() try: f = io.open(opts['-f'], 'r', encoding='utf-8') except IOError as e: logger.error('%s: "%s"' % (e.strerror, opts['-f'])) sys.exit(3) for line in f: name = line.strip() try: name = dns.name.from_text(name) except UnicodeDecodeError as e: logger.error('%s: "%s"' % (e, name)) except dns.exception.DNSException: logger.error('The domain name was invalid: "%s"' % name) else: names.append(name) f.close() else: if args: # python3/python2 dual compatibility if isinstance(args[0], bytes): args = [codecs.decode(x, sys.getfilesystemencoding()) for x in args] else: try: args = analysis_structured['_meta._dnsviz.']['names'] except KeyError: logger.error('No names found in json input!') sys.exit(3) for name in args: try: name = dns.name.from_text(name) except UnicodeDecodeError as e: logger.error('%s: "%s"' % (e, name)) except dns.exception.DNSException: logger.error('The domain name was invalid: "%s"' % name) else: names.append(name) if '-p' in opts: kwargs = { 'indent': 4, 'separators': (',', ': ') } else: kwargs = {} meta_only = '-m' in opts if '-o' not in opts or opts['-o'] == '-': opts['-o'] = sys.stdout.fileno() try: fh = io.open(opts['-o'], 'wb') except IOError as e: logger.error('%s: "%s"' % (e.strerror, opts['-o'])) sys.exit(3) def _flush(name_obj): d = OrderedDict() name_obj.serialize(d) s = json.dumps(d, **kwargs) lindex = s.index('{') rindex = s.rindex('}') fh.write(s[lindex+1:rindex]+',') dnsviz_meta = { 'version': DNS_RAW_VERSION, 'names': [lb2s(n.to_text()) for n in names] } flush = '-F' in opts if '-n' in opts or '-e' in opts: CustomQueryMixin.edns_options = [] if '-e' in opts: CustomQueryMixin.edns_options.append(_get_ecs_option(opts['-e'])) if '-n' in opts: CustomQueryMixin.edns_options.append(_get_nsid_option()) query_class_mixin = CustomQueryMixin else: query_class_mixin = None name_objs = [] if '-r' in opts: cache = {} for name in names: if name.canonicalize().to_text() not in analysis_structured: logger.error('The domain name was not found in the analysis input: "%s"' % name.to_text()) continue name_objs.append(OnlineDomainNameAnalysis.deserialize(name, analysis_structured, cache)) else: if '-t' in opts: a = cls(try_ipv4, try_ipv6, client_ipv4, client_ipv6, query_class_mixin, ceiling, edns_diagnostics, stop_at_explicit, cache_level, rdtypes, explicit_only, dlv_domain, processes) else: if cls.use_full_resolver: _init_full_resolver() else: _init_stub_resolver() a = cls(try_ipv4, try_ipv6, client_ipv4, client_ipv6, query_class_mixin, ceiling, edns_diagnostics, stop_at_explicit, cache_level, rdtypes, explicit_only, dlv_domain) if flush: fh.write('{') a.analyze(names, _flush) fh.write('"_meta._dnsviz.":%s}' % json.dumps(dnsviz_meta, **kwargs)) sys.exit(0) name_objs = a.analyze(names) name_objs = [x for x in name_objs if x is not None] if not name_objs: sys.exit(4) d = OrderedDict() for name_obj in name_objs: name_obj.serialize(d, meta_only) d['_meta._dnsviz.'] = dnsviz_meta try: fh.write(json.dumps(d, ensure_ascii=False, **kwargs).encode('utf-8')) except IOError as e: logger.error('Error writing analysis: %s' % e) sys.exit(3) except KeyboardInterrupt: logger.error('Interrupted.') sys.exit(4) # tm is global (because of possible multiprocessing), so we need to # explicitly close it here finally: if tm is not None: tm.close()
def main(argv): global tm global full_resolver global stub_resolver global explicit_delegations global odd_ports global next_port try: try: opts, args = getopt.getopt( argv[1:], 'f:d:l:c:r:t:64b:u:kmpo:a:R:x:N:D:e:EAs:Fh') except getopt.GetoptError as e: usage(str(e)) sys.exit(1) _init_tm() stub_resolver = Resolver.from_file('/etc/resolv.conf', StandardRecursiveQueryCD, transport_manager=tm) # get all the options for which there might be multiple values explicit_delegations = {} odd_ports = {} stop_at_explicit = {} client_ipv4 = None client_ipv6 = None delegation_info = {} for opt, arg in opts: if opt in ('-x', '-N'): try: domain, mappings = arg.split(':', 1) except ValueError: usage('Incorrect usage of %s option: "%s"' % (opt, arg)) sys.exit(1) domain = domain.strip() mappings = mappings.strip() match = STOP_RE.search(domain) if match is not None: if opt == '-N': usage('Incorrect usage of %s option: "%s"' % (opt, arg)) sys.exit(1) domain = match.group(1) try: domain = dns.name.from_text(domain) except dns.exception.DNSException: usage('The domain name was invalid: "%s"' % domain) sys.exit(1) if opt == '-N' and domain == dns.name.root: usage('The root zone cannot be used with option -N.') sys.exit(1) if match is not None: stop_at_explicit[domain] = True else: stop_at_explicit[domain] = False if opt == '-N': if domain == dns.name.root: usage('The root zone cannot be used with option -N.') sys.exit(1) parent = domain.parent() if parent not in delegation_info: delegation_info[parent] = {} delegation_mapping = delegation_info[parent] else: delegation_mapping = explicit_delegations if not mappings: usage('Incorrect usage of %s option: "%s"' % (arg, opt)) sys.exit(1) if (domain, dns.rdatatype.NS) not in delegation_mapping: delegation_mapping[(domain, dns.rdatatype.NS)] = dns.rrset.RRset( domain, dns.rdataclass.IN, dns.rdatatype.NS) name_addr_mappings_from_string(domain, mappings, delegation_mapping, opt == '-N') elif opt == '-D': try: domain, ds_str = arg.split(':', 1) except ValueError: usage('Incorrect usage of %s option: "%s"' % (opt, arg)) sys.exit(1) domain = domain.strip() ds_str = ds_str.strip() try: domain = dns.name.from_text(domain) except dns.exception.DNSException: usage('The domain name was invalid: "%s"' % domain) sys.exit(1) parent = domain.parent() if parent not in delegation_info: delegation_info[parent] = {} delegation_mapping = delegation_info[parent] if not ds_str: usage('Incorrect usage of %s option: "%s"' % (arg, opt)) sys.exit(1) if (domain, dns.rdatatype.DS) not in delegation_mapping: delegation_mapping[(domain, dns.rdatatype.DS)] = dns.rrset.RRset( domain, dns.rdataclass.IN, dns.rdatatype.DS) ds_from_string(domain, ds_str.strip(), delegation_mapping) elif opt == '-b': try: addr = IPAddr(arg) except ValueError: usage('The IP address was invalid: "%s"' % arg) sys.exit(1) if addr.version == 4: client_ipv4 = addr fam = socket.AF_INET else: client_ipv6 = addr fam = socket.AF_INET6 try: s = socket.socket(fam) s.bind((addr, 0)) del s except socket.error as e: if e.errno == errno.EADDRNOTAVAIL: usage('Cannot bind to specified IP address: "%s"' % addr) sys.exit(1) opts = dict(opts) if '-h' in opts: usage() sys.exit(0) if not ('-f' in opts or args) and '-r' not in opts: usage( 'When -r is not used, either -f must be used or domain names must be supplied as command line arguments.' ) sys.exit(1) if '-f' in opts and args: usage( 'If -f is used, then domain names may not supplied as command line arguments.' ) sys.exit(1) if '-A' in opts and '-s' in opts: usage('If -A is used, then -s cannot be used.') sys.exit(1) if '-x' in opts and '-A' not in opts: usage('-x may only be used in conjunction with -A.') sys.exit(1) if '-N' in opts and '-A' not in opts: usage('-N may only be used in conjunction with -A.') sys.exit(1) if '-D' in opts and '-N' not in opts: #TODO retrieve NS/A/AAAA if -D is specified but -N is not usage('-D may only be used in conjunction with -N.') sys.exit(1) if '-4' in opts and '-6' in opts: usage('-4 and -6 may not be used together.') sys.exit(1) if '-a' in opts: try: ceiling = dns.name.from_text(opts['-a']) except dns.exception.DNSException: usage('The domain name was invalid: "%s"' % opts['-a']) sys.exit(1) elif '-A' in opts: ceiling = None else: ceiling = dns.name.root if '-R' in opts: explicit_only = True try: rdtypes = opts['-R'].split(',') except ValueError: usage('The list of types was invalid: "%s"' % opts['-R']) sys.exit(1) try: rdtypes = [dns.rdatatype.from_text(x) for x in rdtypes] except dns.rdatatype.UnknownRdatatype: usage('The list of types was invalid: "%s"' % opts['-R']) sys.exit(1) else: rdtypes = None explicit_only = False # if neither is specified, then they're both tried if '-4' not in opts and '-6' not in opts: try_ipv4 = True try_ipv6 = True # if one or the other is specified, then only the one specified is # tried else: if '-4' in opts: try_ipv4 = True try_ipv6 = False else: # -6 in opts try_ipv4 = False try_ipv6 = True for domain in delegation_info: if (domain, dns.rdatatype.NS) in explicit_delegations: usage( 'Cannot use "%s" with -x if its child is specified with -N' % lb2s(domain.canonicalize().to_text())) sys.exit(1) port = next_port next_port += 1 _create_and_serve_zone(domain, delegation_info[domain], port) localhost = dns.name.from_text('localhost') loopback = IPAddr('127.0.0.1') explicit_delegations[(domain, dns.rdatatype.NS)] = dns.rrset.RRset( domain, dns.rdataclass.IN, dns.rdatatype.NS) explicit_delegations[(domain, dns.rdatatype.NS)].add( dns.rdtypes.ANY.NS.NS(dns.rdataclass.IN, dns.rdatatype.NS, localhost)) explicit_delegations[(localhost, dns.rdatatype.A)] = dns.rrset.RRset( localhost, dns.rdataclass.IN, dns.rdatatype.A) explicit_delegations[(localhost, dns.rdatatype.A)].add( dns.rdtypes.IN.A.A(dns.rdataclass.IN, dns.rdatatype.A, loopback)) odd_ports[(domain, loopback)] = port stop_at_explicit[domain] = True if '-A' not in opts: if '-t' in opts: cls = RecursiveParallelAnalyst else: cls = RecursiveBulkAnalyst explicit_delegations[(WILDCARD_EXPLICIT_DELEGATION, dns.rdatatype.NS)] = dns.rrset.RRset( WILDCARD_EXPLICIT_DELEGATION, dns.rdataclass.IN, dns.rdatatype.NS) if '-s' in opts: name_addr_mappings_from_string(WILDCARD_EXPLICIT_DELEGATION, opts['-s'], explicit_delegations, False) else: for i, server in enumerate(stub_resolver._servers): if IPAddr(server).version == 6: rdtype = dns.rdatatype.AAAA else: rdtype = dns.rdatatype.A name = dns.name.from_text('ns%d' % i) explicit_delegations[(WILDCARD_EXPLICIT_DELEGATION, dns.rdatatype.NS)].add( dns.rdtypes.ANY.NS.NS( dns.rdataclass.IN, dns.rdatatype.NS, name)) if (name, rdtype) not in explicit_delegations: explicit_delegations[(name, rdtype)] = dns.rrset.RRset( name, dns.rdataclass.IN, rdtype) explicit_delegations[(name, rdtype)].add( dns.rdata.from_text(dns.rdataclass.IN, rdtype, server)) else: if '-t' in opts: cls = ParallelAnalyst else: cls = BulkAnalyst edns_diagnostics = '-E' in opts if '-u' in opts: # check that version is >= 2.7.9 if HTTPS is requested if opts['-u'].startswith('https'): vers0, vers1, vers2 = sys.version_info[:3] if (2, 7, 9) > (vers0, vers1, vers2): sys.stderr.write( 'python version >= 2.7.9 is required to use a DNS looking glass with HTTPS.\n' ) sys.exit(1) url = urlparse.urlparse(opts['-u']) if url.scheme in ('http', 'https'): th_factories = (transport.DNSQueryTransportHandlerHTTPFactory( opts['-u'], insecure='-k' in opts), ) elif url.scheme == 'ws': if url.hostname is not None: usage( 'WebSocket URL must designate a local UNIX domain socket.' ) sys.exit(1) th_factories = ( transport.DNSQueryTransportHandlerWebSocketFactory( url.path), ) else: usage('Unsupported URL scheme: "%s"' % opts['-u']) sys.exit(1) else: th_factories = None if '-l' in opts: try: dlv_domain = dns.name.from_text(opts['-l']) except dns.exception.DNSException: usage('The domain name was invalid: "%s"' % opts['-l']) sys.exit(1) else: dlv_domain = None # the following option is not documented in usage, as it doesn't # apply to most users try: cache_level = int(opts['-c']) except (KeyError, ValueError): cache_level = None try: processes = int(opts.get('-t', 1)) except ValueError: usage('The number of threads used must be greater than 0.') sys.exit(1) if processes < 1: usage('The number of threads used must be greater than 0.') sys.exit(1) try: val = int(opts.get('-d', 2)) except ValueError: usage('The debug value must be an integer between 0 and 3.') sys.exit(1) if val < 0 or val > 3: usage('The debug value must be an integer between 0 and 3.') sys.exit(1) if val > 2: debug_level = logging.DEBUG elif val > 1: debug_level = logging.INFO elif val > 0: debug_level = logging.WARNING else: debug_level = logging.ERROR handler = logging.StreamHandler() handler.setLevel(debug_level) logger.addHandler(handler) logger.setLevel(debug_level) if '-A' in opts: if try_ipv4 and get_client_address(A_ROOT_IPV4) is None: logger.warning('No global IPv4 connectivity detected') if try_ipv6 and get_client_address(A_ROOT_IPV6) is None: logger.warning('No global IPv6 connectivity detected') if '-r' in opts: if opts['-r'] == '-': opt_r = sys.stdin.fileno() else: opt_r = opts['-r'] try: analysis_str = io.open(opt_r, 'r', encoding='utf-8').read() except IOError as e: logger.error('%s: "%s"' % (e.strerror, opts.get('-r', '-'))) sys.exit(3) try: analysis_structured = json.loads(analysis_str) except ValueError: logger.error( 'There was an error parsing the json input: "%s"' % opts['-r']) sys.exit(3) # check version if '_meta._dnsviz.' not in analysis_structured or 'version' not in analysis_structured[ '_meta._dnsviz.']: logger.error('No version information in JSON input.') sys.exit(3) try: major_vers, minor_vers = [ int(x) for x in str(analysis_structured['_meta._dnsviz.'] ['version']).split('.', 1) ] except ValueError: logger.error('Version of JSON input is invalid: %s' % analysis_structured['_meta._dnsviz.']['version']) sys.exit(3) # ensure major version is a match and minor version is no greater # than the current minor version curr_major_vers, curr_minor_vers = [ int(x) for x in str(DNS_RAW_VERSION).split('.', 1) ] if major_vers != curr_major_vers or minor_vers > curr_minor_vers: logger.error( 'Version %d.%d of JSON input is incompatible with this software.' % (major_vers, minor_vers)) sys.exit(3) names = [] if '-f' in opts: if opts['-f'] == '-': opts['-f'] = sys.stdin.fileno() try: f = io.open(opts['-f'], 'r', encoding='utf-8') except IOError as e: logger.error('%s: "%s"' % (e.strerror, opts['-f'])) sys.exit(3) for line in f: name = line.strip() try: name = dns.name.from_text(name) except UnicodeDecodeError as e: logger.error('%s: "%s"' % (e, name)) except dns.exception.DNSException: logger.error('The domain name was invalid: "%s"' % name) else: names.append(name) f.close() else: if args: # python3/python2 dual compatibility if isinstance(args[0], bytes): args = [ codecs.decode(x, sys.getfilesystemencoding()) for x in args ] else: try: args = analysis_structured['_meta._dnsviz.']['names'] except KeyError: logger.error('No names found in json input!') sys.exit(3) for name in args: try: name = dns.name.from_text(name) except UnicodeDecodeError as e: logger.error('%s: "%s"' % (e, name)) except dns.exception.DNSException: logger.error('The domain name was invalid: "%s"' % name) else: names.append(name) if '-p' in opts: kwargs = {'indent': 4, 'separators': (',', ': ')} else: kwargs = {} meta_only = '-m' in opts if '-o' not in opts or opts['-o'] == '-': opts['-o'] = sys.stdout.fileno() try: fh = io.open(opts['-o'], 'wb') except IOError as e: logger.error('%s: "%s"' % (e.strerror, opts['-o'])) sys.exit(3) def _flush(name_obj): d = collections.OrderedDict() name_obj.serialize(d) s = json.dumps(d, **kwargs) lindex = s.index('{') rindex = s.rindex('}') fh.write(s[lindex + 1:rindex] + ',') dnsviz_meta = { 'version': DNS_RAW_VERSION, 'names': [lb2s(n.to_text()) for n in names] } flush = '-F' in opts if '-e' in opts: CustomQueryMixin.edns_options = [_get_ecs_option(opts['-e'])] query_class_mixin = CustomQueryMixin else: query_class_mixin = None name_objs = [] if '-r' in opts: cache = {} for name in names: if name.canonicalize().to_text() not in analysis_structured: logger.error( 'The domain name was not found in the analysis input: "%s"' % name.to_text()) continue name_objs.append( OnlineDomainNameAnalysis.deserialize( name, analysis_structured, cache)) else: if '-t' in opts: a = cls(try_ipv4, try_ipv6, client_ipv4, client_ipv6, query_class_mixin, ceiling, edns_diagnostics, stop_at_explicit, cache_level, rdtypes, explicit_only, dlv_domain, th_factories, processes) else: _init_resolver() a = cls(try_ipv4, try_ipv6, client_ipv4, client_ipv6, query_class_mixin, ceiling, edns_diagnostics, stop_at_explicit, cache_level, rdtypes, explicit_only, dlv_domain, th_factories) if flush: fh.write('{') a.analyze(names, _flush) fh.write('"_meta._dnsviz.":%s}' % json.dumps(dnsviz_meta, **kwargs)) sys.exit(0) name_objs = a.analyze(names) name_objs = [x for x in name_objs if x is not None] if not name_objs: sys.exit(4) d = collections.OrderedDict() for name_obj in name_objs: name_obj.serialize(d, meta_only) d['_meta._dnsviz.'] = dnsviz_meta try: fh.write( json.dumps(d, ensure_ascii=False, **kwargs).encode('utf-8')) except IOError as e: logger.error('Error writing analysis: %s' % e) sys.exit(3) except KeyboardInterrupt: logger.error('Interrupted.') sys.exit(4) # tm is global (because of possible multiprocessing), so we need to # explicitly close it here finally: if tm is not None: tm.close()
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