Exemplo n.º 1
0
    def generate(self, domains: Iterable[str], filename: str):
        ttl = 3600
        origin = "rpz.local"

        def read_header():
            with open(filename, "r") as f:
                for line in f:
                    if not line.startswith("@"):
                        break
                    yield line

        try:
            # Read "header" only since it's expensive to parse everything.
            zone = dns.zone.from_text("\n".join(read_header()), origin=origin)
        except (dns.exception.DNSException, IOError):
            zone = dns.zone.Zone(origin)

        existing_soa_set = zone.get_rdataset("@", "SOA")
        zone.nodes.clear()

        soa_set = zone.find_rdataset("@", "SOA", create=True)
        if existing_soa_set is None:
            soa_set.ttl = 3600
            soa_set.add(
                dns.rdata.from_text("IN", "SOA",
                                    "@ hostmaster 0 86400 7200 2592000 86400"))
            logging.info("creating zone from scratch")
        else:
            soa_set.update(existing_soa_set)
            # Increment serial.
            serial = soa_set[0].serial + 1
            soa_set.add(soa_set[0].replace(serial=serial))
            logging.info("parsed existing header, new serial is %d", serial)

        in_ns = zone.find_rdataset("@", "NS", create=True)
        in_ns.ttl = ttl
        in_ns.add(dns.rdata.from_text("IN", "NS", "LOCALHOST."))

        in_cname_dot = dns.rdataset.from_text("IN", "CNAME", ttl, ".")
        for domain in domains:
            zone.find_node(domain, create=True).replace_rdataset(in_cname_dot)

        zone.to_file(filename, sorted=True)
        logging.info("block list has %d domains", len(zone.nodes) - 1)
Exemplo n.º 2
0
def calculate_zonemd(zone, zonemd_algorithm='sha384'):
    """
    Calculate the digest of the zone.

    Returns the digest for the zone.

    @var zone: The zone object to digest.
    @type zone: dns.zone.Zone
    @var zonemd_algorithm: The name of the algorithm to use, either "sha384",
                          or the number of the algorithm to use.
    @type zonemd_algorithm: str
    @raises ZoneDigestUnknownAlgorithm: zonemd_algorithm is unknown
    @rtype: bytes
    """
    if zonemd_algorithm in ('sha384', ZONEMD_DIGEST_SHA384):
        hashing = hashlib.sha384()

    # Sort the names in the zone. This is needed for canonization.
    sorted_names = sorted(zone.keys())

    # Iterate across each name in canonical order.
    for name in sorted_names:
        # Save the wire format of the name for later use.
        wire_name = name.canonicalize().to_wire()

        # Iterate across each RRSET in canonical order.
        sorted_rdatasets = sorted(zone.find_node(name).rdatasets,
                                  key=lambda rdataset: rdataset.rdtype)
        for rdataset in sorted_rdatasets:
            # Skip the RRSIG for ZONEMD.
            if rdataset.rdtype == dns.rdatatype.RRSIG:
                if rdataset.covers == ZONEMD_RTYPE:
                    continue

            # Save the wire format of the type, class, and TTL for later use.
            wire_set = struct.pack('!HHI', rdataset.rdtype, rdataset.rdclass,
                                   rdataset.ttl)
            # Extract the wire format of the RDATA and sort them.
            wire_rdatas = []
            for rdata in rdataset:
                wire_rdatas.append(rdata.to_digestable())
            wire_rdatas.sort()

            # Finally update the digest for each RR.
            for wire_rr in wire_rdatas:
                hashing.update(wire_name)
                hashing.update(wire_set)
                hashing.update(struct.pack('!H', len(wire_rr)))
                hashing.update(wire_rr)

    return hashing.digest()
Exemplo n.º 3
0
def zeroconf_to_zone(target_zone='example.com',
                     target_ns='localhost',
                     zeroconf_results={},
                     locmap={},
                     priomap={},
                     ttl=1800):
    import dns.name
    import dns.reversename
    import dns.resolver
    import dns.query
    from dns.exception import DNSException, Timeout
    from dns.resolver import NoAnswer, NXDOMAIN
    import dns.zone
    import dns.node
    import dns.rdataset
    import dns.rdata
    #from dns.rdatatype import *
    import dns.rdatatype
    import dns.rdataclass

    if not isinstance(locmap, dict):
        raise TypeError
    if not isinstance(priomap, dict):
        raise TypeError

    if target_zone == 'example.com':
        zone = """@ 86400 IN SOA {ns}. administrator.example.com. 1970000000 \
28800 7200 604800 1800
@ 86400 IN NS {ns}.""".format(ns=target_ns)
    else:
        zone = dns.zone.from_xfr(dns.query.xfr(target_ns, target_zone))
        zone = zone.get('@').to_text(zone.origin)
    zone = dns.zone.from_text(zone, origin=target_zone)
    # ttl = ttl if not ttl == None else \
    #     zone.get_rdataset('@', dns.rdatatype.SOA).ttl

    reverse_resolved = {}

    for key in zeroconf_results:
        inst_name, inst_type, inst_domain, inst_name_orig = key
        inst_subtypes = zeroconf_results[key]['subtypes'] \
            if 'subtypes' in zeroconf_results[key] else []

        # create service type and subtype nodes (empty nodes deleted at the
        # end)
        type_node = zone.find_node(dns.name.from_text(inst_type,
                                                      origin=zone.origin),
                                   create=True)
        subtype_nodes = [
            zone.find_node(dns.name.from_text(subtype + '._sub.' + inst_type,
                                              origin=zone.origin),
                           create=True) for subtype in inst_subtypes
        ]

        # <Instance> must be a single DNS label, any dots should be escaped
        # before concatenating all portions of a Service Instance Name,
        # according to DNS-SD (RFC6763).
        # A workaround is necessary for buggy software that does not adhere to
        # the rules:
        inst_name = re.sub(r'(?<!\\)\.', r'\.', inst_name)

        inst_fullname = dns.name.from_text("%s.%s" % (inst_name, inst_type),
                                           origin=zone.origin)

        inst_addr = zeroconf_results[key]['address']
        if inst_addr not in reverse_resolved:
            try:
                reverse_resolved[inst_addr] = dns.resolver.query(
                    dns.reversename.from_address(inst_addr), dns.rdatatype.PTR)
            except DNSException, e:
                reverse_resolved[inst_addr] = None
                continue
        # inst_hostname_rev_rr = dns.resolver.query(dns.reversename.
        #                                           from_address(inst_addr),
        #                                           dns.rdatatype.PTR)
        inst_hostname_rev_rr = reverse_resolved[inst_addr] \
            if reverse_resolved[inst_addr] is not None else []
        zeroconf_results[key]['hostname_rev'] = [
            i.to_text(relativize=False) for i in inst_hostname_rev_rr
        ]

        if not zeroconf_results[key]['hostname_rev']:
            continue

        node_ptr_rdata = dns.rdata.from_text(dns.rdataclass.IN,
                                             dns.rdatatype.PTR,
                                             inst_fullname.to_text())
        # fill service type and subtype nodes with PTR rdata
        type_node.find_rdataset(dns.rdataclass.IN,
                                dns.rdatatype.PTR,
                                create=True).add(node_ptr_rdata, ttl=ttl)
        for subtype_node in subtype_nodes:
            subtype_node.find_rdataset(dns.rdataclass.IN,
                                       dns.rdatatype.PTR,
                                       create=True).add(node_ptr_rdata,
                                                        ttl=ttl)

        # create instance node
        inst_node = zone.find_node(inst_fullname, create=True)

        # avahi-browse returns a single SRV and TXT record so we should
        # try to resolve again

        qname = dns.name.from_text("%s.%s" % (inst_name_orig, inst_type),
                                   origin=dns.name.from_text(inst_domain))

        instresolver = dns.resolver.Resolver()

        mdns_addr = '224.0.0.251'

        for l, rdt in (('srv', dns.rdatatype.SRV), ('txt', dns.rdatatype.TXT)):
            if inst_domain == 'local':
                # mDNS:
                # * try a multicast query first:
                #   - with unicast-response (QU) bit set
                #   - legacy per RFC6762 sec. 6.7 as we wont set
                #     source_port=5353
                # * fallback to direct unicast query (RFC6762 sec. 5.5)
                #   caveat:
                #   unicast responses must be less than 512 bytes
                #   or else we will not get an answer (no EDNS0)
                a = mudns_query(qname,
                                rdt,
                                qwhere=mdns_addr,
                                qport=5353,
                                qtimeout=2,
                                qu=True)
                #print "mudns returned %s" % str(a)
                if a is None:
                    a = mudns_query(qname,
                                    rdt,
                                    qwhere=inst_addr,
                                    qport=5353,
                                    qtimeout=2,
                                    res=instresolver)
            else:
                a = mudns_query(qname, rdt, qtimeout=4)

            if isinstance(a, dns.resolver.Answer):
                zeroconf_results[key][l] = [rd.to_text() for rd in a[::]]
            else:
                if l == 'srv':
                    zeroconf_results[key][l] = [
                        '0 0 %s %s' % (zeroconf_results[key]['port'],
                                       zeroconf_results[key]['hostname'])
                    ]
                elif l == 'txt':
                    # reverse the order of fields as returned by avahi
                    txt_rec_rev = re.split('(?<=")\s+(?=")',
                                           zeroconf_results[key][l])[::-1]
                    zeroconf_results[key][l] = [' '.join(txt_rec_rev)]

            #print ""
        #continue

        # replace hostname.local or whatever avahi returns with
        # reverse-resolved fqdn
        for h in zeroconf_results[key]['hostname_rev']:
            zeroconf_results[key]['srv'] = [
                re.sub(r'%s\.?' % zeroconf_results[key]['hostname'], r'%s' % h,
                       r) for r in zeroconf_results[key]['srv']
            ]
            zeroconf_results[key]['txt'] = [
                re.sub(r'%s\.?' % zeroconf_results[key]['hostname'],
                       r'%s' % h.rstrip('.'), r)
                for r in zeroconf_results[key]['txt']
            ]

        # txt record mangling
        for (i, txt_rec) in enumerate(zeroconf_results[key]['txt']):
            # Bonjour Printing mangling
            # discard ephemeral fields: printer-state
            txt_rec = txt_field_mangle(txt_rec, 'printer-state', None)
            zeroconf_results[key]['txt'][i] = txt_rec

            # note field mangling
            if inst_name in locmap:
                txt_rec = txt_field_mangle(txt_rec, 'note', locmap[inst_name])
                zeroconf_results[key]['txt'][i] = txt_rec

            # priority field mangling
            if (inst_name, inst_type) in priomap:
                newprio = priomap[(inst_name, inst_type)]
            elif inst_name in priomap:
                newprio = priomap[inst_name]
            elif inst_type in priomap:
                newprio = priomap[inst_type]
            else:
                newprio = None
            if newprio is not None:
                prio = txt_field_mangle(txt_rec, 'priority')
                if prio is not None:
                    prio = int(prio)
                    if prio == 0:
                        continue
                else:
                    # Bonjour Printing spec v1.2 sec. 9.2.5
                    # if not specified, priority defaults to 50
                    prio = 50
                # keep the modulo 10 part of original priority
                prio = prio % 10
                txt_rec = txt_field_mangle(txt_rec, 'priority', newprio + prio)
                zeroconf_results[key]['txt'][i] = txt_rec

        # fill instance node with SRV and TXT rdata
        for rec_type in ('srv', 'txt'):
            for r in zeroconf_results[key][rec_type]:
                inst_node.find_rdataset(dns.rdataclass.IN,
                                        dns.rdatatype.from_text(rec_type),
                                        create=True).add(dns.rdata.from_text(
                                            dns.rdataclass.IN,
                                            dns.rdatatype.from_text(rec_type),
                                            r),
                                                         ttl=ttl)
Exemplo n.º 4
0
 def add_rdata(nodename: str, typ: str, ttl: int, data: str) -> None:
     node = zone.find_node(nodename, create=True)
     typ_int = dns.rdatatype.from_text(typ)
     rdata = dns.rdata.from_text(dns.rdataclass.IN, typ_int, data)
     node.find_rdataset(dns.rdataclass.IN, typ_int, create=True).add(rdata, ttl=ttl)
Exemplo n.º 5
0
def zeroconf_to_zone(target_zone='example.com', target_ns='localhost',
                     zeroconf_results={}, locmap={}, priomap={}, ttl=1800):
    import dns.name
    import dns.reversename
    import dns.resolver
    import dns.query
    from dns.exception import DNSException, Timeout
    from dns.resolver import NoAnswer, NXDOMAIN
    import dns.zone
    import dns.node
    import dns.rdataset
    import dns.rdata
    #from dns.rdatatype import *
    import dns.rdatatype
    import dns.rdataclass

    if not isinstance(locmap, dict):
        raise TypeError
    if not isinstance(priomap, dict):
        raise TypeError

    if target_zone == 'example.com':
        zone = """@ 86400 IN SOA {ns}. administrator.example.com. 1970000000 \
28800 7200 604800 1800
@ 86400 IN NS {ns}.""".format(ns=target_ns)
    else:
        zone = dns.zone.from_xfr(dns.query.xfr(target_ns, target_zone))
        zone = zone.get('@').to_text(zone.origin)
    zone = dns.zone.from_text(zone, origin=target_zone)
    # ttl = ttl if not ttl == None else \
    #     zone.get_rdataset('@', dns.rdatatype.SOA).ttl

    reverse_resolved = {}

    for key in zeroconf_results:
        inst_name, inst_type, inst_domain, inst_name_orig = key
        inst_subtypes = zeroconf_results[key]['subtypes'] \
            if 'subtypes' in zeroconf_results[key] else []

        # create service type and subtype nodes (empty nodes deleted at the
        # end)
        type_node = zone.find_node(dns.name.from_text(inst_type,
                                                      origin=zone.origin),
                                   create=True)
        subtype_nodes = [zone.find_node(dns.name.from_text(subtype + '._sub.'
                                                           + inst_type,
                                                           origin=zone.origin),
                                        create=True)
                         for subtype in inst_subtypes]

        # <Instance> must be a single DNS label, any dots should be escaped
        # before concatenating all portions of a Service Instance Name,
        # according to DNS-SD (RFC6763).
        # A workaround is necessary for buggy software that does not adhere to
        # the rules:
        inst_name = re.sub(r'(?<!\\)\.', r'\.', inst_name)

        inst_fullname = dns.name.from_text("%s.%s" % (inst_name, inst_type),
                                           origin=zone.origin)

        inst_addr = zeroconf_results[key]['address']
        if inst_addr not in reverse_resolved:
            try:
                reverse_resolved[inst_addr] = dns.resolver.query(
                    dns.reversename.from_address(inst_addr),
                    dns.rdatatype.PTR)
            except DNSException, e:
                reverse_resolved[inst_addr] = None
                continue
        # inst_hostname_rev_rr = dns.resolver.query(dns.reversename.
        #                                           from_address(inst_addr),
        #                                           dns.rdatatype.PTR)
        inst_hostname_rev_rr = reverse_resolved[inst_addr] \
            if reverse_resolved[inst_addr] is not None else []
        zeroconf_results[key]['hostname_rev'] = [i.to_text(relativize=False)
                                                 for i in inst_hostname_rev_rr]

        if not zeroconf_results[key]['hostname_rev']:
            continue

        node_ptr_rdata = dns.rdata.from_text(dns.rdataclass.IN,
                                             dns.rdatatype.PTR,
                                             inst_fullname.to_text())
        # fill service type and subtype nodes with PTR rdata
        type_node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.PTR,
                                create=True).add(node_ptr_rdata, ttl=ttl)
        for subtype_node in subtype_nodes:
            subtype_node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.PTR,
                                       create=True).add(node_ptr_rdata,
                                                        ttl=ttl)

        # create instance node
        inst_node = zone.find_node(inst_fullname, create=True)

        # avahi-browse returns a single SRV and TXT record so we should
        # try to resolve again

        qname = dns.name.from_text("%s.%s" % (inst_name_orig, inst_type),
                                  origin=dns.name.from_text(inst_domain))

        instresolver = dns.resolver.Resolver()

        mdns_addr = '224.0.0.251'

        for l, rdt in (('srv', dns.rdatatype.SRV),
                       ('txt', dns.rdatatype.TXT)):
            if inst_domain == 'local':
                # mDNS:
                # * try a multicast query first:
                #   - with unicast-response (QU) bit set
                #   - legacy per RFC6762 sec. 6.7 as we wont set
                #     source_port=5353
                # * fallback to direct unicast query (RFC6762 sec. 5.5)
                #   caveat:
                #   unicast responses must be less than 512 bytes
                #   or else we will not get an answer (no EDNS0)
                a = mudns_query(qname, rdt, qwhere=mdns_addr, qport=5353,
                                qtimeout=2, qu=True)
                #print "mudns returned %s" % str(a)
                if a is None:
                    a = mudns_query(qname, rdt, qwhere=inst_addr,
                                    qport=5353, qtimeout=2,
                                    res=instresolver)
            else:
                a = mudns_query(qname, rdt, qtimeout=4)

            if isinstance(a, dns.resolver.Answer):
                zeroconf_results[key][l] = [rd.to_text() for rd in a[::]]
            else:
                if l == 'srv':
                    zeroconf_results[key][l] = [
                        '0 0 %s %s' %
                        (zeroconf_results[key]['port'],
                         zeroconf_results[key]['hostname'])
                        ]
                elif l == 'txt':
                    # reverse the order of fields as returned by avahi
                    txt_rec_rev = re.split('(?<=")\s+(?=")',
                                           zeroconf_results[key][l]
                                           )[::-1]
                    zeroconf_results[key][l] = [' '.join(txt_rec_rev)]

            #print ""
        #continue

        # replace hostname.local or whatever avahi returns with
        # reverse-resolved fqdn
        for h in zeroconf_results[key]['hostname_rev']:
            zeroconf_results[key]['srv'] = [
                re.sub(
                    r'%s\.?' % zeroconf_results[key]['hostname'],
                    r'%s' % h,
                    r)
                for r in zeroconf_results[key]['srv']
                ]
            zeroconf_results[key]['txt'] = [
                re.sub(
                    r'%s\.?' % zeroconf_results[key]['hostname'],
                    r'%s' % h.rstrip('.'),
                    r)
                for r in zeroconf_results[key]['txt']
                ]

        # txt record mangling
        for (i, txt_rec) in enumerate(zeroconf_results[key]['txt']):
            # Bonjour Printing mangling
            # discard ephemeral fields: printer-state
            txt_rec = txt_field_mangle(txt_rec, 'printer-state', None)
            zeroconf_results[key]['txt'][i] = txt_rec

            # note field mangling
            if inst_name in locmap:
                txt_rec = txt_field_mangle(txt_rec, 'note', locmap[inst_name])
                zeroconf_results[key]['txt'][i] = txt_rec

            # priority field mangling
            if (inst_name, inst_type) in priomap:
                newprio = priomap[(inst_name, inst_type)]
            elif inst_name in priomap:
                newprio = priomap[inst_name]
            elif inst_type in priomap:
                newprio = priomap[inst_type]
            else:
                newprio = None
            if newprio is not None:
                prio = txt_field_mangle(txt_rec, 'priority')
                if prio is not None:
                    prio = int(prio)
                    if prio == 0:
                        continue
                else:
                    # Bonjour Printing spec v1.2 sec. 9.2.5
                    # if not specified, priority defaults to 50
                    prio = 50
                # keep the modulo 10 part of original priority
                prio = prio % 10
                txt_rec = txt_field_mangle(txt_rec, 'priority', newprio + prio)
                zeroconf_results[key]['txt'][i] = txt_rec

        # fill instance node with SRV and TXT rdata
        for rec_type in ('srv', 'txt'):
            for r in zeroconf_results[key][rec_type]:
                inst_node.find_rdataset(dns.rdataclass.IN,
                                        dns.rdatatype.from_text(rec_type),
                                        create=True).add(
                    dns.rdata.from_text(dns.rdataclass.IN,
                                        dns.rdatatype.from_text(rec_type),
                                        r),
                    ttl=ttl)