Example #1
0
def humanize_name(name, idn=False):
    if idn:
        try:
            name = name.canonicalize().to_unicode()
        except UnicodeError:
            name = name.canonicalize().to_text()
    else:
        name = name.canonicalize().to_text()
    if name == '.':
        return name
    return name.rstrip('.')
Example #2
0
def humanize_name(name, idn=False):
    if idn:
        try:
            name = name.canonicalize().to_unicode()
        except UnicodeError:
            name = lb2s(name.canonicalize().to_text())
    else:
        name = lb2s(name.canonicalize().to_text())
    if name == '.':
        return name
    return name.rstrip('.')
Example #3
0
def make_ds(name, key, algorithm, origin=None):
    """Create a DS record for a DNSSEC key.

    *name* is the owner name of the DS record.

    *key* is a ``dns.rdtypes.ANY.DNSKEY``.

    *algorithm* is a string describing which hash algorithm to use.  The
    currently supported hashes are "SHA1" and "SHA256".  Case does not
    matter for these strings.

    *origin* is a ``dns.name.Name`` and will be used as the origin
    if *key* is a relative name.

    Returns a ``dns.rdtypes.ANY.DS``.
    """

    if algorithm.upper() == 'SHA1':
        dsalg = 1
        hash = dns.hash.hashes['SHA1']()
    elif algorithm.upper() == 'SHA256':
        dsalg = 2
        hash = dns.hash.hashes['SHA256']()
    else:
        raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)

    if isinstance(name, string_types):
        name = dns.name.from_text(name, origin)
    hash.update(name.canonicalize().to_wire())
    hash.update(_to_rdata(key, origin))
    digest = hash.digest()

    dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, dsalg) + digest
    return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
                               len(dsrdata))
Example #4
0
def make_ds(name, key, algorithm, origin=None):
    """Create a DS record for a DNSSEC key.

    :param name: Owner name of the DS record
    :type name: string
    :param key: a DNSKEY
    :type key: :py:data:`dns.rdtypes.ANY.DNSKEY`
    :param algorithm: a string describing which hash algorithm to use.The currently supported hashes are "SHA1" and "SHA256". Case does not matter for these strings.
    :type algorithm: string
    :param origin: Will be used as origin if `key` is a relative name, defaults to None
    :type origin: :py:data:`dns.name.Name`, optional
    :raises UnsupportedAlgorithm: If the algorithm is not either "SHA1" or "SHA256" exception will be thrown
    :return: a DS record
    :rtype: :py:data:`dns.rdtypes.ANY.DS`
    """

    if algorithm.upper() == 'SHA1':
        dsalg = 1
        dshash = hashlib.sha1()
    elif algorithm.upper() == 'SHA256':
        dsalg = 2
        dshash = hashlib.sha256()
    else:
        raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)

    if isinstance(name, str):
        name = dns.name.from_text(name, origin)
    dshash.update(name.canonicalize().to_wire())
    dshash.update(_to_rdata(key, origin))
    digest = dshash.digest()

    dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, dsalg) + digest
    return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
                               len(dsrdata))
Example #5
0
def make_ds(name, key, algorithm, origin=None):
    """Create a DS record for a DNSSEC key.

    *name* is the owner name of the DS record.

    *key* is a ``dns.rdtypes.ANY.DNSKEY``.

    *algorithm* is a string describing which hash algorithm to use.  The
    currently supported hashes are "SHA1" and "SHA256".  Case does not
    matter for these strings.

    *origin* is a ``dns.name.Name`` and will be used as the origin
    if *key* is a relative name.

    Returns a ``dns.rdtypes.ANY.DS``.
    """

    if algorithm.upper() == 'SHA1':
        dsalg = 1
        hash = SHA1.new()
    elif algorithm.upper() == 'SHA256':
        dsalg = 2
        hash = SHA256.new()
    else:
        raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)

    if isinstance(name, str):
        name = dns.name.from_text(name, origin)
    hash.update(name.canonicalize().to_wire())
    hash.update(_to_rdata(key, origin))
    digest = hash.digest()

    dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, dsalg) + digest
    return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
                               len(dsrdata))
Example #6
0
 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()
Example #7
0
 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()
Example #8
0
def make_ds(
    name: Union[dns.name.Name, str],
    key: dns.rdata.Rdata,
    algorithm: Union[DSDigest, str],
    origin: Optional[dns.name.Name] = None,
) -> DS:
    """Create a DS record for a DNSSEC key.

    *name*, a ``dns.name.Name`` or ``str``, the owner name of the DS record.

    *key*, a ``dns.rdtypes.ANY.DNSKEY.DNSKEY``, the key the DS is about.

    *algorithm*, a ``str`` or ``int`` specifying the hash algorithm.
    The currently supported hashes are "SHA1", "SHA256", and "SHA384". Case
    does not matter for these strings.

    *origin*, a ``dns.name.Name`` or ``None``.  If `key` is a relative name,
    then it will be made absolute using the specified origin.

    Raises ``UnsupportedAlgorithm`` if the algorithm is unknown.

    Returns a ``dns.rdtypes.ANY.DS.DS``
    """

    try:
        if isinstance(algorithm, str):
            algorithm = DSDigest[algorithm.upper()]
    except Exception:
        raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)
    if not isinstance(key, DNSKEY):
        raise ValueError("key is not a DNSKEY")
    if algorithm == DSDigest.SHA1:
        dshash = hashlib.sha1()
    elif algorithm == DSDigest.SHA256:
        dshash = hashlib.sha256()
    elif algorithm == DSDigest.SHA384:
        dshash = hashlib.sha384()
    else:
        raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)

    if isinstance(name, str):
        name = dns.name.from_text(name, origin)
    wire = name.canonicalize().to_wire()
    assert wire is not None
    dshash.update(wire)
    dshash.update(key.to_wire(origin=origin))
    digest = dshash.digest()

    dsrdata = struct.pack("!HBB", key_id(key), key.algorithm,
                          algorithm) + digest
    ds = dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
                             len(dsrdata))
    return cast(DS, ds)
Example #9
0
def nsec3_covers_name(nsec_rrset, name, zonename):
    """
    Does NSEC3 RR cover the given name?
    """
    name = name.canonicalize()
    n1 = nsec_rrset.name.canonicalize()
    n2_hash = base64.b32encode(nsec_rrset[0].next)
    n2_hash = n2_hash.translate(b32_to_ext_hex).decode()
    n2 = dns.name.Name((n2_hash, ) + zonename.labels)
    n2 = n2.canonicalize()
    if (name.fullcompare(n1)[1] > 0) and (name.fullcompare(n2)[1] < 0):
        return True
    return False
Example #10
0
def make_ds(name, key, algorithm, origin=None):
    if algorithm.upper() == 'SHA1':
        dsalg = 1
        hash = dns.hash.get('SHA1')()
    elif algorithm.upper() == 'SHA256':
        dsalg = 2
        hash = dns.hash.get('SHA256')()
    else:
        raise UnsupportedAlgorithm, 'unsupported algorithm "%s"' % algorithm

    if isinstance(name, (str, unicode)):
        name = dns.name.from_text(name, origin)
    hash.update(name.canonicalize().to_wire())
    hash.update(_to_rdata(key, origin))
    digest = hash.digest()

    dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, dsalg) + digest
    return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
                               len(dsrdata))
Example #11
0
def make_ds(name, key, algorithm, origin=None):
    if algorithm.upper() == 'SHA1':
        dsalg = 1
        hash = dns.hash.get('SHA1')()
    elif algorithm.upper() == 'SHA256':
        dsalg = 2
        hash = dns.hash.get('SHA256')()
    else:
        raise UnsupportedAlgorithm, 'unsupported algorithm "%s"' % algorithm

    if isinstance(name, (str, unicode)):
        name = dns.name.from_text(name, origin)
    hash.update(name.canonicalize().to_wire())
    hash.update(_to_rdata(key, origin))
    digest = hash.digest()

    dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, dsalg) + digest
    return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
                               len(dsrdata))
Example #12
0
def make_ds(name, key, algorithm, origin=None):
    """Create a DS record for a DNSSEC key.

    *name*, a ``dns.name.Name`` or ``str``, the owner name of the DS record.

    *key*, a ``dns.rdtypes.ANY.DNSKEY.DNSKEY``, the key the DS is about.

    *algorithm*, a ``str`` specifying the hash algorithm.
      The currently supported hashes are "SHA1", "SHA256", and "SHA384". Case
      does not matter for these strings.

    *origin*, a ``dns.name.Name`` or ``None``, if `key` is a relative name,
      then it will be made absolute using the specified origin.

    Raises ``UnsupportedAlgorithm`` if the algorithm is unknown.

    Returns a ``dns.rdtypes.ANY.DS.DS``
    """

    if algorithm.upper() == "SHA1":
        dsalg = 1
        dshash = hashlib.sha1()
    elif algorithm.upper() == "SHA256":
        dsalg = 2
        dshash = hashlib.sha256()
    elif algorithm.upper() == "SHA384":
        dsalg = 4
        dshash = hashlib.sha384()
    else:
        raise UnsupportedAlgorithm('unsupported algorithm "%s"' % algorithm)

    if isinstance(name, str):
        name = dns.name.from_text(name, origin)
    dshash.update(name.canonicalize().to_wire())
    dshash.update(_to_rdata(key, origin))
    digest = dshash.digest()

    dsrdata = struct.pack("!HBB", key_id(key), key.algorithm, dsalg) + digest
    return dns.rdata.from_wire(dns.rdataclass.IN, dns.rdatatype.DS, dsrdata, 0,
                               len(dsrdata))
Example #13
0
class DomainNameField(models.CharField):
    description = _("Domain name (with maximum length of %(max_length)s characters)")

    def __init__(self, *args, **kwargs):
        self.canonicalize = kwargs.pop('canonicalize', True)
        super(DomainNameField, self).__init__(*args, **kwargs)

    def from_db_value(self, value, expression, connection, context):
        return self.to_python(value)

    def to_python(self, value):
        if value is None:
            return None
        if isinstance(value, dns.name.Name):
            name = value
        else:
            try:
                name = dns.name.from_text(value)
            except Exception, e:
                raise ValidationError('%s: %s is of type %s' % (e, value, type(value)))
        if self.canonicalize:
            name = name.canonicalize()
        return name
Example #14
0
File: grok.py Project: G2CU/dnsviz
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)
Example #15
0
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)
Example #16
0
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)
Example #17
0
                tk_str = open(opts['-t']).read()
            except IOError, e:
                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
Example #18
0
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)
Example #19
0
            except IOError, 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 = 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()
Example #20
0
def uuid_for_name(name):
    return uuid.uuid5(uuid.NAMESPACE_DNS, name.canonicalize().to_text())
Example #21
0
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)
Example #22
0
def name_url_encode(name):
    if name == dns.name.root:
        return 'root'
    return urllib.quote(name.canonicalize().to_text().rstrip('.').replace('/', 'S'), safe='')
Example #23
0
def uuid_for_name(name):
    return uuid.uuid5(uuid.NAMESPACE_DNS, name.canonicalize().to_text())
Example #24
0
            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': [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)
Example #25
0
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)
Example #26
0
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()
Example #27
0
def name_url_encode(name):
    if name == dns.name.root:
        return 'root'
    return urllib.quote(name.canonicalize().to_text().rstrip('.').replace('/', 'S'), safe='')
Example #28
0
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()
Example #29
0
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)
Example #30
0
            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': [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)