def export_zone(suffix, names, zonefile_dir, server_names, conffile_fh): filename = os.path.join(zonefile_dir, 'db._odup.%s' % (suffix.to_text().rstrip('.'))) conffile_fh.write('zone "_odup.%s" {\n' % suffix.to_text()) conffile_fh.write('\ttype master;\n') conffile_fh.write('\tfile "%s";\n' % filename) conffile_fh.write('\tallow-transfer { any; };\n') conffile_fh.write('};\n') if len(suffix) == 2: fetch_str = ' +fetch:axfr://' else: fetch_str = '' with open(filename, 'w+') as fh: fh.write('$ORIGIN _odup.%s\n' % (suffix.to_text())) fh.write('$TTL 604800\n') fh.write('@\tSOA\t%s root.nic.%s 1 1800 900 604800 86400\n' % (server_names[0].to_text(), suffix.to_text())) for server_name in server_names: fh.write('\tNS\t%s\n' % (server_name.to_text())) fh.write('\tTXT\t"v=odup1 +bound%s -all"\n' % fetch_str) for name in names: name = name.relativize(suffix) if name == dns.name.empty: continue if name[0][0].startswith('!'): fh.write('%s\tTXT\t"v=odup1 +org"\n' % (name.to_text().lstrip('!'))) elif name[0] == '*': fh.write('%s\tTXT\t"v=odup1 +bound:%d -all"\n' % (name.to_text(), len(name) - 1)) else: fh.write('%s\tTXT\t"v=odup1 +bound -all"\n' % (name.to_text()))
def humanize_name(name, idn=False, canonicalize=True): if canonicalize: name = name.canonicalize() if idn: try: name = name.to_unicode() except UnicodeError: name = lb2s(name.to_text()) else: name = lb2s(name.to_text()) if name == '.': return name return name.rstrip('.')
def domain_exists_in_bind_cache(self, **params): try: zone = dns.zone.from_xfr( dns.query.xfr(params['nameserver'], params['zone_name'], lifetime=params['timeout'])) for (name, _, _) in zone.iterate_rdatas("SOA"): if name.to_text() == ( '%s%s' % (params['zone_name'], '.')) or name.to_text() == '@': return params['zone_name'] except: return None
def _send_negative_answer(self, fz, metadata, parsed, rcode): self._set_flags(parsed) name = parsed.question[0].name forged_soa = dns.rrset.from_text( name, 3600 * 3, dns.rdataclass.IN, dns.rdatatype.from_text('SOA'), 'ns1.{} hostmaster.{} 1 {} {} {} {}'.format( name.to_text(), name.to_text(), 3 * 3600, 3600, 86400 * 7, 3 * 3600)) parsed.set_rcode(rcode) parsed.answer = [] parsed.authority = [forged_soa] parsed.additional = [] self._send('\x00{}{}{}'.format(struct.pack('!H', len(metadata)), metadata, str(parsed.to_wire())))
def _refresh_zone(self, zone=None): """get the dns zone 'zone'. It only lists records which type are in 'self.type_written'. 'zone' must be correctly configured in 'self.zone_list' @str zone: the zone name to refresh @rtype: list of hash {'key', 'class', 'type', 'ttl', 'content'} """ # zone is defined by the query string parameter # if query string is empty, use the default zone if zone is None: zone = self.zone_default # get the zone from the dns self.zone = dns.zone.from_xfr(dns.query.xfr( self.zone_list[zone]['ip'], zone)) records = [] # get all the records in a list of hash # {'key', 'class', 'type', 'ttl', 'content'} for name, node in self.zone.nodes.items(): rdatasets = node.rdatasets for rdataset in rdatasets: for rdata in rdataset: record = {} record['key'] = name.to_text(name) record['class'] = dns.rdataclass.to_text(rdataset.rdclass) record['type'] = dns.rdatatype.to_text(rdataset.rdtype) record['ttl'] = str(rdataset.ttl) record['content'] = rdata.to_text() # filter by record type if record['type'] in self.type_displayed: records.append(record) return records
def _refresh_zone(self, zone=None): """get the dns zone 'zone'. It only lists records which type are in 'self.type_written'. 'zone' must be correctly configured in 'self.zone_list' @str zone: the zone name to refresh @rtype: list of hash {'key', 'class', 'type', 'ttl', 'content'} """ # zone is defined by the query string parameter # if query string is empty, use the default zone if zone is None: zone = self.zone_default # get the zone from the dns self.zone = dns.zone.from_xfr( dns.query.xfr(self.zone_list[zone]['ip'], zone)) records = [] # get all the records in a list of hash # {'key', 'class', 'type', 'ttl', 'content'} for name, node in self.zone.nodes.items(): rdatasets = node.rdatasets for rdataset in rdatasets: for rdata in rdataset: record = {} record['key'] = name.to_text(name) record['class'] = dns.rdataclass.to_text(rdataset.rdclass) record['type'] = dns.rdatatype.to_text(rdataset.rdtype) record['ttl'] = str(rdataset.ttl) record['content'] = rdata.to_text() # filter by record type if record['type'] in self.type_displayed: records.append(record) return records
def _follow_cnames(self, domain, validation_name): """ Performs recursive CNAME lookups in case there exists a CNAME for the given validation name. If the optional dependency dnspython is not installed, the given name is simply returned. """ try: import dns.exception import dns.resolver import dns.name if self.conf('follow-cnames') != 'true': return validation_name except ImportError: return validation_name resolver = dns.resolver.Resolver() name = dns.name.from_text(validation_name) while 1: try: answer = resolver.query(name, 'CNAME') if 1 <= len(answer): name = answer[0].target else: break except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN): break except (dns.exception.Timeout, dns.resolver.YXDOMAIN, dns.resolver.NoNameservers): raise errors.PluginError('Failed to lookup CNAMEs on your requested domain {0}'.format(domain)) return name.to_text(True)
def from_wire(cls, otype, wire, cur, olen): # Calculate name length based on name, n = dns.name.from_wire(wire, cur) rate, unit, ttl = struct.unpack('!IHI', wire[cur + n:cur + olen]) _name = name.to_text() _unit = EDNS0_EDomainRate._units_from_int[unit] return cls(_name, rate, _unit, ttl)
def get_prep_value(self, value): if value is None: return None if isinstance(value, dns.name.Name): name = value else: name = dns.name.from_text(value) if self.canonicalize: name = name.canonicalize() return name.to_text()
def _get_hosts_from_dns(config): """Does a DNS zone transfer off the SOA for a given domain to get a list of hosts that can be pushed to.""" try: soa_answer = dns.resolver.query(config.dns.domain, "SOA", tcp=True) master_answer = dns.resolver.query(soa_answer[0].mname, "A", tcp=True) xfr_answer = dns.query.xfr(master_answer[0].address, config.dns.domain) zone = dns.zone.from_xfr(xfr_answer) return sorted_nicely([name.to_text() for name, ttl, rdata in zone.iterate_rdatas("A")]) except dns.exception.DNSException, e: raise HostLookupError("host lookup by dns failed: %r" % e)
def get_all_hosts(self): """Pull all hosts from DNS by doing a zone transfer.""" try: soa_answer = dns.resolver.query(self.domain, "SOA", tcp=True) soa_host = soa_answer[0].mname master_answer = dns.resolver.query(soa_host, "A", tcp=True) master_addr = master_answer[0].address xfr_answer = dns.query.xfr(master_addr, self.domain) zone = dns.zone.from_xfr(xfr_answer) return [name.to_text() for name, ttl, rdata in zone.iterate_rdatas("A")] except dns.exception.DNSException, e: raise HostLookupError("host lookup by dns failed: %r" % e)
def get_all_hosts(self): """Pull all hosts from DNS by doing a zone transfer.""" try: soa_answer = dns.resolver.query(self.domain, "SOA", tcp=True) soa_host = soa_answer[0].mname master_answer = dns.resolver.query(soa_host, "A", tcp=True) master_addr = master_answer[0].address xfr_answer = dns.query.xfr(master_addr, self.domain) zone = dns.zone.from_xfr(xfr_answer) return [ name.to_text() for name, ttl, rdata in zone.iterate_rdatas("A") ] except dns.exception.DNSException, e: raise HostLookupError("host lookup by dns failed: %r" % e)
def from_file(self, filename): z = dns.zone.from_file(filename); origin = z.origin.to_text(); if origin[-1:] == '.': origin = origin[:-1]; if origin != self._zname: raise Exception('Invalid origin'); for name in z.nodes: rname = name.to_text(); self._records[rname] = {}; for rdataset in z.nodes[name].rdatasets: rtype = rdt.to_text(rdataset.rdtype); ttl = rdataset.ttl; self._records[rname][rtype] = []; for rdata in rdataset.items: rec = record.from_rdata(rdata, ttl); self.add_record(rname, rec); self._getSOA(); self._filename = filename;
def to_text(keyring): """Convert a dictionary containing (dns.name.Name, dns.tsig.Key) pairs into a text keyring which has (textual DNS name, (textual algorithm, base64 secret)) pairs, or a dictionary containing (dns.name.Name, bytes) pairs into a text keyring which has (textual DNS name, base64 secret) pairs. @rtype: dict""" textring = {} def b64encode(secret): return base64.encodebytes(secret).decode().rstrip() for (name, key) in keyring.items(): name = name.to_text() if isinstance(key, bytes): textring[name] = b64encode(key) else: textring[name] = (key.algorithm.to_text(), b64encode(key.secret)) return textring
def zone_records(self, zone): if zone.name not in self._zone_records: try: z = self._load_zone_file(zone.name) records = [] for (name, ttl, rdata) in z.iterate_rdatas(): rdtype = dns.rdatatype.to_text(rdata.rdtype) records.append({ "name": name.to_text(), "ttl": ttl, "type": rdtype, "value": rdata.to_text() }) self._zone_records[zone.name] = records except ZoneFileSourceNotFound: return [] return self._zone_records[zone.name]
def zone_records(self, zone): try: z = dns.zone.from_xfr(dns.query.xfr(self.master, zone.name, relativize=False), relativize=False) except DNSException: raise AxfrSourceZoneTransferFailed() records = [] for (name, ttl, rdata) in z.iterate_rdatas(): rdtype = dns.rdatatype.to_text(rdata.rdtype) records.append({ "name": name.to_text(), "ttl": ttl, "type": rdtype, "value": rdata.to_text() }) return records
def main(argv): try: test_pygraphviz() try: opts, args = getopt.getopt(argv[1:], 'f:r:R:et:a:d:CPOo:T:h') except getopt.GetoptError as e: sys.stderr.write('%s\n' % str(e)) sys.exit(1) # collect trusted keys trusted_keys = [] for opt, arg in opts: if opt == '-t': try: with io.open(arg, 'r', encoding='utf-8') as fh: tk_str = fh.read() except IOError as e: logger.error('%s: "%s"' % (e.strerror, arg)) sys.exit(3) try: trusted_keys.extend(get_trusted_keys(tk_str)) except dns.exception.DNSException: logger.error( 'There was an error parsing the trusted keys file: "%s"' % arg) sys.exit(3) opts = dict(opts) if '-h' in opts: usage() sys.exit(0) if '-f' in opts and args: sys.stderr.write( 'If -f is used, then domain names may not supplied as command line arguments.\n' ) sys.exit(1) if '-R' in opts: try: rdtypes = opts['-R'].split(',') except ValueError: sys.stderr.write('The list of types was invalid: "%s"\n' % opts['-R']) sys.exit(1) try: rdtypes = [dns.rdatatype.from_text(x) for x in rdtypes] except dns.rdatatype.UnknownRdatatype: sys.stderr.write('The list of types was invalid: "%s"\n' % opts['-R']) sys.exit(1) else: rdtypes = None if '-a' in opts: try: supported_algs = opts['-a'].split(',') except ValueError: sys.stderr.write('The list of algorithms was invalid: "%s"\n' % opts['-a']) sys.exit(1) try: supported_algs = set([int(x) for x in supported_algs]) except ValueError: sys.stderr.write('The list of algorithms was invalid: "%s"\n' % opts['-a']) sys.exit(1) else: supported_algs = None if '-d' in opts: try: supported_digest_algs = opts['-d'].split(',') except ValueError: sys.stderr.write( 'The list of digest algorithms was invalid: "%s"\n' % opts['-d']) sys.exit(1) try: supported_digest_algs = set( [int(x) for x in supported_digest_algs]) except ValueError: sys.stderr.write( 'The list of digest algorithms was invalid: "%s"\n' % opts['-d']) sys.exit(1) else: supported_digest_algs = None strict_cookies = '-C' in opts allow_private = '-P' in opts remove_edges = '-e' not in opts if '-T' in opts: fmt = opts['-T'] elif '-o' in opts: fmt = opts['-o'].split('.')[-1] else: fmt = 'dot' if fmt not in ('dot', 'png', 'jpg', 'svg', 'html'): sys.stderr.write('Image format unrecognized: "%s"\n' % fmt) sys.exit(1) if '-o' in opts and '-O' in opts: sys.stderr.write( 'The -o and -O options may not be used together.\n') sys.exit(1) if '-r' not in opts or opts['-r'] == '-': opt_r = sys.stdin.fileno() else: opt_r = opts['-r'] try: with io.open(opt_r, 'r', encoding='utf-8') as fh: analysis_str = fh.read() except IOError as e: logger.error('%s: "%s"' % (e.strerror, opts.get('-r', '-'))) sys.exit(3) if not analysis_str: if opt_r != sys.stdin.fileno(): logger.error('No input.') sys.exit(3) try: analysis_structured = json.loads(analysis_str) except ValueError: logger.error('There was an error parsing the JSON input: "%s"' % opts.get('-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 = OrderedDict() 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: if name not in names: names[name] = None 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: if name not in names: names[name] = None latest_analysis_date = None name_objs = [] cache = {} for name in names: name_str = lb2s(name.canonicalize().to_text()) if name_str not in analysis_structured or analysis_structured[ name_str].get('stub', True): logger.error( 'The analysis of "%s" was not found in the input.' % lb2s(name.to_text())) continue name_obj = OfflineDomainNameAnalysis.deserialize( name, analysis_structured, cache, strict_cookies=strict_cookies, allow_private=allow_private) name_objs.append(name_obj) if latest_analysis_date is None or latest_analysis_date > name_obj.analysis_end: latest_analysis_date = name_obj.analysis_end if not name_objs: sys.exit(4) if '-t' not in opts: trusted_keys = get_default_trusted_keys(latest_analysis_date) G = DNSAuthGraph() for name_obj in name_objs: name_obj.populate_status( trusted_keys, supported_algs=supported_algs, supported_digest_algs=supported_digest_algs) for qname, rdtype in name_obj.queries: if rdtypes is None: # if rdtypes was not specified, then graph all, with some # exceptions if name_obj.is_zone() and rdtype in (dns.rdatatype.DNSKEY, dns.rdatatype.DS, dns.rdatatype.DLV): continue else: # if rdtypes was specified, then only graph rdtypes that # were specified if qname != name_obj.name or rdtype not in rdtypes: continue G.graph_rrset_auth(name_obj, qname, rdtype) if rdtypes is not None: for rdtype in rdtypes: if (name_obj.name, rdtype) not in name_obj.queries: logger.error( 'No query for "%s/%s" was included in the analysis.' % (lb2s(name_obj.name.to_text()), dns.rdatatype.to_text(rdtype))) if '-O' in opts: if name_obj.name == dns.name.root: name = 'root' else: name = lb2s( name_obj.name.canonicalize().to_text()).rstrip('.') finish_graph(G, [name_obj], rdtypes, trusted_keys, supported_algs, fmt, '%s.%s' % (name, fmt), remove_edges) G = DNSAuthGraph() if '-O' not in opts: if '-o' not in opts or opts['-o'] == '-': finish_graph(G, name_objs, rdtypes, trusted_keys, supported_algs, fmt, None, remove_edges) else: finish_graph(G, name_objs, rdtypes, trusted_keys, supported_algs, fmt, opts['-o'], remove_edges) except KeyboardInterrupt: logger.error('Interrupted.') sys.exit(4)
def main(argv): try: test_m2crypto() #TODO remove -p option (it is now the default, and -c is used to change it) try: opts, args = getopt.getopt(argv[1:], 'f:r:t:o:cpl:h') except getopt.GetoptError as e: usage(str(e)) sys.exit(1) # collect trusted keys trusted_keys = [] for opt, arg in opts: if opt == '-t': try: tk_str = io.open(arg, 'r', encoding='utf-8').read() except IOError as e: sys.stderr.write('%s: "%s"\n' % (e.strerror, arg)) sys.exit(3) try: trusted_keys.extend(get_trusted_keys(tk_str)) except dns.exception.DNSException: sys.stderr.write('There was an error parsing the trusted keys file: "%s"\n' % arg) sys.exit(3) opts = dict(opts) if '-h' in opts: usage() sys.exit(0) 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 '-l' in opts: if opts['-l'] == 'error': loglevel = logging.ERROR elif opts['-l'] == 'warning': loglevel = logging.WARNING elif opts['-l'] == 'info': loglevel = logging.INFO elif opts['-l'] == 'debug': loglevel = logging.DEBUG else: usage('Invalid log level: "%s"' % opts['-l']) sys.exit(1) else: loglevel = logging.DEBUG handler = logging.StreamHandler() handler.setLevel(logging.WARNING) logger.addHandler(handler) logger.setLevel(logging.WARNING) if '-r' not in opts or 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.get('-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 '-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) if '-c' not in opts: kwargs = { 'indent': 4, 'separators': (',', ': ') } else: kwargs = {} # if trusted keys were supplied, check that pygraphviz is installed if trusted_keys: test_pygraphviz() name_objs = [] cache = {} for name in names: name_str = lb2s(name.canonicalize().to_text()) if name_str not in analysis_structured or analysis_structured[name_str].get('stub', True): logger.error('The analysis of "%s" was not found in the input.' % lb2s(name.to_text())) continue name_objs.append(OfflineDomainNameAnalysis.deserialize(name, analysis_structured, cache)) if not name_objs: sys.exit(4) d = OrderedDict() for name_obj in name_objs: name_obj.populate_status(trusted_keys) if trusted_keys: G = DNSAuthGraph() for qname, rdtype in name_obj.queries: if name_obj.is_zone() and rdtype in (dns.rdatatype.DNSKEY, dns.rdatatype.DS, dns.rdatatype.DLV): continue G.graph_rrset_auth(name_obj, qname, rdtype) for target, mx_obj in name_obj.mx_targets.items(): if mx_obj is not None: G.graph_rrset_auth(mx_obj, target, dns.rdatatype.A) G.graph_rrset_auth(mx_obj, target, dns.rdatatype.AAAA) for target, ns_obj in name_obj.ns_dependencies.items(): if ns_obj is not None: G.graph_rrset_auth(ns_obj, target, dns.rdatatype.A) G.graph_rrset_auth(ns_obj, target, dns.rdatatype.AAAA) G.add_trust(trusted_keys) name_obj.populate_response_component_status(G) name_obj.serialize_status(d, loglevel=loglevel) if d: s = json.dumps(d, ensure_ascii=False, **kwargs) if '-c' not in opts and fh.isatty() and os.environ.get('TERM', 'dumb') != 'dumb': s = color_json(s) fh.write(s.encode('utf-8')) except KeyboardInterrupt: logger.error('Interrupted.') sys.exit(4)
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
logger.error('%s: "%s"' % (e.strerror, opts['-t'])) sys.exit(3) try: trusted_keys = get_trusted_keys(tk_str) except dns.exception.DNSException: logger.error('There was an error parsing the trusted keys file: "%s"' % opts['-t']) sys.exit(3) else: trusted_keys = () name_objs = [] cache = {} for name in names: name_str = name.canonicalize().to_text() if name_str not in analysis_structured or analysis_structured[name_str].get('stub', True): logger.error('The analysis of "%s" was not found in the input.' % name.to_text()) continue name_objs.append(OfflineDomainNameAnalysis.deserialize(name, analysis_structured, cache)) if not name_objs: sys.exit(4) G = DNSAuthGraph() for name_obj in name_objs: name_obj.populate_status(trusted_keys) for qname, rdtype in name_obj.queries: if rdtypes is None: # if rdtypes was not specified, then graph all, with some # exceptions if name_obj.is_zone() and rdtype in (dns.rdatatype.DNSKEY, dns.rdatatype.DS, dns.rdatatype.DLV): continue
def main(argv): try: test_m2crypto() test_pygraphviz() try: opts, args = getopt.getopt(argv[1:], 'f:r:R:t:Oo:h') except getopt.GetoptError as e: usage(str(e)) sys.exit(1) # collect trusted keys trusted_keys = [] for opt, arg in opts: if opt == '-t': try: tk_str = io.open(arg, 'r', encoding='utf-8').read() except IOError as e: sys.stderr.write('%s: "%s"\n' % (e.strerror, arg)) sys.exit(3) try: trusted_keys.extend(get_trusted_keys(tk_str)) except dns.exception.DNSException: sys.stderr.write( 'There was an error parsing the trusted keys file: "%s"\n' % arg) sys.exit(3) opts = dict(opts) if '-h' in opts: usage() sys.exit(0) 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 '-R' in opts: 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 if '-o' in opts and '-O' in opts: usage('The -o and -O options may not be used together.') sys.exit(1) handler = logging.StreamHandler() handler.setLevel(logging.WARNING) logger.addHandler(handler) logger.setLevel(logging.WARNING) if '-r' not in opts or 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.get('-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 '-t' not in opts: try: tk_str = io.open(TRUSTED_KEYS_ROOT, 'r', encoding='utf-8').read() except IOError as e: logger.error('Error reading trusted keys file "%s": %s' % (TRUSTED_KEYS_ROOT, e.strerror)) sys.exit(3) try: trusted_keys.extend(get_trusted_keys(tk_str)) except dns.exception.DNSException: logger.error( 'There was an error parsing the trusted keys file: "%s"' % arg) sys.exit(3) name_objs = [] cache = {} for name in names: name_str = lb2s(name.canonicalize().to_text()) if name_str not in analysis_structured or analysis_structured[ name_str].get('stub', True): logger.error( 'The analysis of "%s" was not found in the input.' % lb2s(name.to_text())) continue name_objs.append( TTLAgnosticOfflineDomainNameAnalysis.deserialize( name, analysis_structured, cache)) if not name_objs: sys.exit(4) G = DNSAuthGraph() for name_obj in name_objs: name_obj.populate_status(trusted_keys) for qname, rdtype in name_obj.queries: if rdtypes is None: # if rdtypes was not specified, then graph all, with some # exceptions if name_obj.is_zone() and rdtype in (dns.rdatatype.DNSKEY, dns.rdatatype.DS, dns.rdatatype.DLV): continue else: # if rdtypes was specified, then only graph rdtypes that # were specified if qname != name_obj.name or rdtype not in rdtypes: continue G.graph_rrset_auth(name_obj, qname, rdtype) if rdtypes is not None: for rdtype in rdtypes: if (name_obj.name, rdtype) not in name_obj.queries: logger.error( 'No query for "%s/%s" was included in the analysis.' % (lb2s(name_obj.name.to_text()), dns.rdatatype.to_text(rdtype))) if '-O' in opts: if name_obj.name == dns.name.root: name = 'root' else: name = lb2s( name_obj.name.canonicalize().to_text()).rstrip('.') finish_graph(G, [name_obj], rdtypes, trusted_keys, '%s.txt' % name) G = DNSAuthGraph() if '-O' not in opts: if '-o' not in opts or opts['-o'] == '-': finish_graph(G, name_objs, rdtypes, trusted_keys, None) else: finish_graph(G, name_objs, rdtypes, trusted_keys, opts['-o']) except KeyboardInterrupt: logger.error('Interrupted.') sys.exit(4)
def main(argv): try: test_m2crypto() test_pygraphviz() try: opts, args = getopt.getopt(argv[1:], 'f:r:R:t:Oo:T:h') except getopt.GetoptError as e: usage(str(e)) sys.exit(1) # collect trusted keys trusted_keys = [] for opt, arg in opts: if opt == '-t': try: tk_str = io.open(arg, 'r', encoding='utf-8').read() except IOError as e: sys.stderr.write('%s: "%s"\n' % (e.strerror, arg)) sys.exit(3) try: trusted_keys.extend(get_trusted_keys(tk_str)) except dns.exception.DNSException: sys.stderr.write('There was an error parsing the trusted keys file: "%s"\n' % arg) sys.exit(3) opts = dict(opts) if '-h' in opts: usage() sys.exit(0) 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 '-R' in opts: 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 if '-T' in opts: fmt = opts['-T'] elif '-o' in opts: fmt = opts['-o'].split('.')[-1] else: fmt = 'dot' if fmt not in ('dot','png','jpg','svg','html'): usage('Image format unrecognized: "%s"' % fmt) sys.exit(1) if '-o' in opts and '-O' in opts: usage('The -o and -O options may not be used together.') sys.exit(1) handler = logging.StreamHandler() handler.setLevel(logging.WARNING) logger.addHandler(handler) logger.setLevel(logging.WARNING) if '-r' not in opts or 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.get('-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 '-t' not in opts: try: tk_str = io.open(TRUSTED_KEYS_ROOT, 'r', encoding='utf-8').read() except IOError as e: logger.error('Error reading trusted keys file "%s": %s' % (TRUSTED_KEYS_ROOT, e.strerror)) sys.exit(3) try: trusted_keys.extend(get_trusted_keys(tk_str)) except dns.exception.DNSException: logger.error('There was an error parsing the trusted keys file: "%s"' % arg) sys.exit(3) name_objs = [] cache = {} for name in names: name_str = lb2s(name.canonicalize().to_text()) if name_str not in analysis_structured or analysis_structured[name_str].get('stub', True): logger.error('The analysis of "%s" was not found in the input.' % lb2s(name.to_text())) continue name_objs.append(OfflineDomainNameAnalysis.deserialize(name, analysis_structured, cache)) if not name_objs: sys.exit(4) G = DNSAuthGraph() for name_obj in name_objs: name_obj.populate_status(trusted_keys) for qname, rdtype in name_obj.queries: if rdtypes is None: # if rdtypes was not specified, then graph all, with some # exceptions if name_obj.is_zone() and rdtype in (dns.rdatatype.DNSKEY, dns.rdatatype.DS, dns.rdatatype.DLV): continue else: # if rdtypes was specified, then only graph rdtypes that # were specified if qname != name_obj.name or rdtype not in rdtypes: continue G.graph_rrset_auth(name_obj, qname, rdtype) if rdtypes is not None: for rdtype in rdtypes: if (name_obj.name, rdtype) not in name_obj.queries: logger.error('No query for "%s/%s" was included in the analysis.' % (lb2s(name_obj.name.to_text()), dns.rdatatype.to_text(rdtype))) if '-O' in opts: if name_obj.name == dns.name.root: name = 'root' else: name = lb2s(name_obj.name.canonicalize().to_text()).rstrip('.') finish_graph(G, [name_obj], rdtypes, trusted_keys, fmt, '%s.%s' % (name, fmt)) G = DNSAuthGraph() if '-O' not in opts: if '-o' not in opts or opts['-o'] == '-': finish_graph(G, name_objs, rdtypes, trusted_keys, fmt, None) else: finish_graph(G, name_objs, rdtypes, trusted_keys, fmt, opts['-o']) except KeyboardInterrupt: logger.error('Interrupted.') sys.exit(4)
ns = str(n).rstrip('.') if not ns: continue server = {"ns":ns, "axfr":False} zone_content = [] try: axfr_rr = dns.query.xfr(ns, domain, lifetime=timeout) axfr_zone = dns.zone.from_xfr(axfr_rr) except Exception, e: server["error"] = str(e) retv["servers"].append(server) continue server["axfr"] = True for name, ttl, rdata in axfr_zone.iterate_rdatas(): entry = { 'name': name.to_text(), 'ttl': ttl, 'rdclass': rdata.rdclass, 'rdtype': rdata.rdtype, 'rdata': rdata.to_text() } try: entry['pretty_rdclass'] = dns.rdataclass.to_text(rdata.rdclass) entry['pretty_rdtype'] = dns.rdatatype.to_text(rdata.rdtype) parent = dns.name.Name(domain.split('.')) if name == dns.name.empty: entry['pretty_name'] = parent.to_text() else: entry['pretty_name'] = name.concatenate(parent).to_text() except Exception: pass
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()
trusted_keys.extend(get_trusted_keys(tk_str)) except dns.exception.DNSException: logger.error( 'There was an error parsing the trusted keys file: "%s"' % arg) sys.exit(3) name_objs = [] cache = {} for name in names: name_str = name.canonicalize().to_text() if name_str not in analysis_structured or analysis_structured[ name_str].get('stub', True): logger.error( 'The analysis of "%s" was not found in the input.' % name.to_text()) continue name_objs.append( OfflineDomainNameAnalysis.deserialize(name, analysis_structured, cache)) if not name_objs: sys.exit(4) G = DNSAuthGraph() for name_obj in name_objs: name_obj.populate_status(trusted_keys) for qname, rdtype in name_obj.queries: if rdtypes is None: # if rdtypes was not specified, then graph all, with some
def main(argv): try: arghelper = GrokArgHelper(logger) arghelper.build_parser('%s %s' % (sys.argv[0], argv[0]), argv[1:]) logger.setLevel(logging.WARNING) try: arghelper.check_args() arghelper.set_kwargs() arghelper.set_buffers() arghelper.aggregate_trusted_key_info() arghelper.ingest_input() arghelper.ingest_names() except argparse.ArgumentTypeError as e: arghelper.parser.error(str(e)) except AnalysisInputError as e: s = str(e) if s: logger.error(s) sys.exit(3) if arghelper.args.minimize_output: kwargs = {} else: kwargs = {'indent': 4, 'separators': (',', ': ')} # if trusted keys were supplied, check that pygraphviz is installed if arghelper.trusted_keys: test_pygraphviz() name_objs = [] cache = {} for name in arghelper.names: name_str = lb2s(name.canonicalize().to_text()) if name_str not in arghelper.analysis_structured or arghelper.analysis_structured[ name_str].get('stub', True): logger.error( 'The analysis of "%s" was not found in the input.' % lb2s(name.to_text())) continue name_obj = OfflineDomainNameAnalysis.deserialize( name, arghelper.analysis_structured, cache, strict_cookies=arghelper.args.enforce_cookies, allow_private=arghelper.args.allow_private) name_objs.append(name_obj) if not name_objs: sys.exit(4) arghelper.update_trusted_key_info() d = OrderedDict() for name_obj in name_objs: name_obj.populate_status( arghelper.trusted_keys, supported_algs=arghelper.args.algorithms, supported_digest_algs=arghelper.args.digest_algorithms) if arghelper.trusted_keys: G = DNSAuthGraph() for qname, rdtype in name_obj.queries: if name_obj.is_zone() and rdtype in (dns.rdatatype.DNSKEY, dns.rdatatype.DS, dns.rdatatype.DLV): continue G.graph_rrset_auth(name_obj, qname, rdtype) for target, mx_obj in name_obj.mx_targets.items(): if mx_obj is not None: G.graph_rrset_auth(mx_obj, target, dns.rdatatype.A) G.graph_rrset_auth(mx_obj, target, dns.rdatatype.AAAA) for target, ns_obj in name_obj.ns_dependencies.items(): if ns_obj is not None: G.graph_rrset_auth(ns_obj, target, dns.rdatatype.A) G.graph_rrset_auth(ns_obj, target, dns.rdatatype.AAAA) G.add_trust(arghelper.trusted_keys, supported_algs=arghelper.args.algorithms) name_obj.populate_response_component_status(G) name_obj.serialize_status(d, loglevel=arghelper.log_level) if d: s = json.dumps(d, ensure_ascii=False, **kwargs) if not arghelper.args.minimize_output and arghelper.args.output_file.isatty( ) and os.environ.get('TERM', 'dumb') != 'dumb': s = color_json(s) arghelper.args.output_file.write(s.encode('utf-8')) except KeyboardInterrupt: logger.error('Interrupted.') sys.exit(4)
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': [n.to_text() for n in names] } flush = '-F' in opts 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(client_ipv4, client_ipv6, ceiling, edns_diagnostics, cache_level, explicit_delegations, rdtypes, explicit_only, dlv_domain, processes) else: a = cls(client_ipv4, client_ipv6, ceiling, edns_diagnostics, cache_level, explicit_delegations, 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)
ns = str(n).rstrip('.') if not ns: continue server = {"ns": ns, "axfr": False} zone_content = [] try: axfr_rr = dns.query.xfr(ns, domain, lifetime=timeout) axfr_zone = dns.zone.from_xfr(axfr_rr) except Exception, e: server["error"] = str(e) retv["servers"].append(server) continue server["axfr"] = True for name, ttl, rdata in axfr_zone.iterate_rdatas(): entry = { 'name': name.to_text(), 'ttl': ttl, 'rdclass': rdata.rdclass, 'rdtype': rdata.rdtype, 'rdata': rdata.to_text() } try: entry['pretty_rdclass'] = dns.rdataclass.to_text(rdata.rdclass) entry['pretty_rdtype'] = dns.rdatatype.to_text(rdata.rdtype) parent = dns.name.Name(domain.split('.')) if name == dns.name.empty: entry['pretty_name'] = parent.to_text() else: entry['pretty_name'] = name.concatenate(parent).to_text() except Exception: pass
def print_result(analysis_structured): latest_analysis_date = None name_objs = [] cache = {} names = OrderedDict() strict_cookies = False allow_private = False supported_digest_algs = None supported_algs = None rdtypes = None args = analysis_structured['_meta._dnsviz.']['names'] 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: if name not in names: names[name] = None for name in names: name_str = lb2s(name.canonicalize().to_text()) print('* %s' % name_str) if name_str not in analysis_structured or analysis_structured[name_str].get('stub', True): logger.error('The analysis of "%s" was not found in the input.' % lb2s(name.to_text())) continue name_obj = TTLAgnosticOfflineDomainNameAnalysis.deserialize(name, analysis_structured, cache, strict_cookies=strict_cookies, allow_private=allow_private) name_objs.append(name_obj) if latest_analysis_date is None or latest_analysis_date > name_obj.analysis_end: latest_analysis_date = name_obj.analysis_end if latest_analysis_date is None: logger.error('The analysis of "%s" doesn\'t include at least one analysis.' % lb2s(name.to_text())) print(names) return trusted_keys = get_default_trusted_keys(latest_analysis_date) G = DNSAuthGraph() for name_obj in name_objs: name_obj.populate_status(trusted_keys, supported_algs=supported_algs, supported_digest_algs=supported_digest_algs) for qname, rdtype in name_obj.queries: if rdtypes is None: # if rdtypes was not specified, then graph all, with some # exceptions if name_obj.is_zone() and rdtype in (dns.rdatatype.DNSKEY, dns.rdatatype.DS, dns.rdatatype.DLV): continue else: # if rdtypes was specified, then only graph rdtypes that # were specified if qname != name_obj.name or rdtype not in rdtypes: continue G.graph_rrset_auth(name_obj, qname, rdtype) if rdtypes is not None: for rdtype in rdtypes: if (name_obj.name, rdtype) not in name_obj.queries: logger.error('No query for "%s/%s" was included in the analysis.' % (lb2s(name_obj.name.to_text()), dns.rdatatype.to_text(rdtype))) finish_graph(G, name_objs, rdtypes, trusted_keys, supported_algs, None)
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
def _printcache(self, cache): for name, values in cache.items(): print(name.to_text() + ' :') for type, data in values.items(): print(dns.rdatatype.to_text(type), end=' : ') print('[' + ', '.join("'" + r.to_text() + "'" for r in data) + ']\n')
def main(argv): try: test_pygraphviz() arghelper = build_helper(logger, sys.argv[0], argv[0]) arghelper.parse_args(argv[1:]) logger.setLevel(logging.WARNING) try: arghelper.check_args() arghelper.set_buffers() arghelper.aggregate_trusted_key_info() arghelper.ingest_input() arghelper.ingest_names() except argparse.ArgumentTypeError as e: arghelper.parser.error(str(e)) except AnalysisInputError as e: s = str(e) if s: logger.error(s) sys.exit(3) latest_analysis_date = None name_objs = [] cache = {} for name in arghelper.names: name_str = lb2s(name.canonicalize().to_text()) if name_str not in arghelper.analysis_structured or arghelper.analysis_structured[ name_str].get('stub', True): logger.error( 'The analysis of "%s" was not found in the input.' % lb2s(name.to_text())) continue name_obj = TTLAgnosticOfflineDomainNameAnalysis.deserialize( name, arghelper.analysis_structured, cache, strict_cookies=arghelper.args.enforce_cookies, allow_private=arghelper.args.allow_private) name_objs.append(name_obj) if latest_analysis_date is None or latest_analysis_date > name_obj.analysis_end: latest_analysis_date = name_obj.analysis_end if not name_objs: sys.exit(4) arghelper.update_trusted_key_info(latest_analysis_date) G = DNSAuthGraph() for name_obj in name_objs: name_obj.populate_status( arghelper.trusted_keys, supported_algs=arghelper.args.algorithms, supported_digest_algs=arghelper.args.digest_algorithms, validate_prohibited_algs=arghelper.args. validate_prohibited_algs) for qname, rdtype in name_obj.queries: if arghelper.args.rr_types is None: # if rdtypes was not specified, then graph all, with some # exceptions if name_obj.is_zone() and rdtype in (dns.rdatatype.DNSKEY, dns.rdatatype.DS, dns.rdatatype.DLV): continue else: # if rdtypes was specified, then only graph rdtypes that # were specified if qname != name_obj.name or rdtype not in arghelper.args.rr_types: continue G.graph_rrset_auth(name_obj, qname, rdtype) if arghelper.args.rr_types is not None: for rdtype in arghelper.args.rr_types: if (name_obj.name, rdtype) not in name_obj.queries: logger.error( 'No query for "%s/%s" was included in the analysis.' % (lb2s(name_obj.name.to_text()), dns.rdatatype.to_text(rdtype))) if arghelper.args.derive_filename: if name_obj.name == dns.name.root: name = 'root' else: name = lb2s( name_obj.name.canonicalize().to_text()).rstrip('.') name = name.replace(os.sep, '--') finish_graph(G, [name_obj], arghelper.args.rr_types, arghelper.trusted_keys, arghelper.args.algorithms, '%s.txt' % name) G = DNSAuthGraph() if not arghelper.args.derive_filename: finish_graph(G, name_objs, arghelper.args.rr_types, arghelper.trusted_keys, arghelper.args.algorithms, arghelper.args.output_file.fileno()) except KeyboardInterrupt: logger.error('Interrupted.') sys.exit(4)
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': [n.to_text() for n in names] } flush = '-F' in opts 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, ceiling, edns_diagnostics, cache_level, explicit_delegations, rdtypes, explicit_only, dlv_domain, th_factories, processes) else: a = cls(try_ipv4, try_ipv6, client_ipv4, client_ipv6, ceiling, edns_diagnostics, cache_level, explicit_delegations, 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)