Example #1
0
def update_serial(zone):
    soaset = zone.get_rdataset('@', dns.rdatatype.SOA)
    soa = soaset[0]
    if dns.version.MAJOR < 2:
        soa.serial += 1
    else:
        soaset.add(soa.replace(serial=soa.serial + 1))
Example #2
0
def info_page(env, responde):
	responde('200 Here you go', [('Content-Type', 'text/html')])
	yield HEADER
	try:
		addr = dns.reversename.from_address(env["REMOTE_ADDR"])
		yield DOMAIN % str(dns.resolver.query(addr, "PTR")[0])
	except dns.exception.DNSException:
		traceback.print_exc()
		yield NO_DOMAIN
	yield HEADER2
	try:
		zone = dns.zone.from_xfr(dns.query.xfr("127.0.0.1", "lan"))
		domains = zone.nodes.keys()
		domains.sort()
		for domain in domains:
			a_record = zone.get_rdataset(domain, "a")
			if a_record:
				ip = str(a_record.items[0])
                                name = str(domain).decode("idna").encode("utf-8")
				if ip == env["REMOTE_ADDR"]:
					delete = DELETE % name
				else:
					delete = ""
				yield ROW % (name, ip, delete)
	except dns.exception.DNSException:
		traceback.print_exc()
		yield ROW % ("Error", "loading", "domains")
	yield FOOTER 
Example #3
0
def info_page(env, responde):
    responde('200 Here you go', [('Content-Type', 'text/html')])
    yield HEADER
    try:
        addr = dns.reversename.from_address(env["REMOTE_ADDR"])
        yield DOMAIN % str(dns.resolver.query(addr, "PTR")[0])
    except dns.exception.DNSException:
        traceback.print_exc()
        yield NO_DOMAIN
    yield HEADER2
    try:
        zone = dns.zone.from_xfr(dns.query.xfr("127.0.0.1", "lan"))
        domains = zone.nodes.keys()
        domains.sort()
        for domain in domains:
            a_record = zone.get_rdataset(domain, "a")
            if a_record:
                ip = str(a_record.items[0])
                name = str(domain).decode("idna").encode("utf-8")
                if ip == env["REMOTE_ADDR"]:
                    delete = DELETE % name
                else:
                    delete = ""
                yield ROW % (name, ip, delete)
    except dns.exception.DNSException:
        traceback.print_exc()
        yield ROW % ("Error", "loading", "domains")
    yield FOOTER
    def run(self, domain, record_type=None, name=None,
            nameserver=None, content=None, tags=None,
            tags_and=None
            ):
        response = self.getAPI("/api/1.0/dns/records/", {
            "domain": domain,
            "type": record_type,
            "name": name,
            "nameserver": nameserver,
            "content": content,
            "tags": tags,
            "tags_and": tags_and
        })

        result = ""
        if len(response["records"]) > 0:
            zone = dns.zone.Zone(dns.name.from_text(
                response["records"][0]["dns_zone"]
            ))
            for record in response["records"]:
                try:
                    rdtype = dns.rdatatype.from_text(record["type"])
                    rdata = dns.rdata.from_text(
                        dns.rdataclass.IN, rdtype, record["content"]
                    )
                    n = zone.get_rdataset(record["name"], rdtype, create=True)
                    n.add(rdata, record["ttl"])
                except Exception, e:
                    print "Error: %s" % e.message
                    print "Record Name: %s" % record["name"]
                    print "Record Type: %s" % record["type"]
                    print "Record Content: %s" % record["content"]
                    print "Record TTL: %d" % record["ttl"]
                    print "---"
            result = zone.to_text()
Example #5
0
def run():
    global serials, q

    while True:
        for domain in DOMAINS:
            zone = dns.zone.from_xfr(dns.query.xfr(DNS_SERVER,
                                                   domain,
                                                   relativize=False),
                                     relativize=False)

            serial = zone.get_rdataset(domain, 'SOA')[0].serial
            if serial == serials.get(domain, None):
                continue
            serials[domain] = serial

            sync(domain, zone, serial)

        try:
            v = q.get(timeout=INTERVAL)
            q.task_done()

            # Skip any update notifications accumulated in the queue.
            # We want to fetch the most recent zone from the DNS
            # server anyway.
            while True:
                try:
                    v = q.get_nowait()
                    q.task_done()
                except queue.Empty:
                    break

        except queue.Empty:
            pass
Example #6
0
def validate_zonemd(zone):
    """
    Validate the digest of the zone.

    @var zone: The zone object to validate.
    @type zone: dns.zone.Zone
    @rtype: (bool, str) tuple

    Returns a tuple of (success code, error message). The success code
    is True if the digest is correct, and False otherwise. The error
    message is "" if there is no error, otherwise a description of the
    problem.
    """
    # Get the SOA and ZONEMD records for the zone.
    zone_name = min(zone.keys())
    soa_rdataset = zone.get_rdataset(zone_name, dns.rdatatype.SOA)
    soa = soa_rdataset.items[0]
    #    zonemd = zone.find_rdataset(zone_name, ZONEMD_RTYPE).items[0]

    original_digests = {}
    for zonemd in zone.find_rdataset(zone_name, ZONEMD_RTYPE).items:
        # Verify that the SOA matches between the SOA and the ZONEMD.
        if soa.serial != zonemd.serial:
            err = ("SOA serial " + str(soa.serial) + " does not " +
                   "match ZONEMD serial " + str(zonemd.serial))
            return False, err

        # Save the original digest.
        if zonemd.algorithm in original_digests:
            err = ("Digest algorithm " + str(zonemd.algorithm) +
                   "used more than once")
            return False, err
        original_digests[zonemd.algorithm] = zonemd.digest

        # Put a placeholder in for the ZONEMD.
        if zonemd.algorithm in _EMPTY_DIGEST_BY_ALGORITHM:
            zonemd.digest = _EMPTY_DIGEST_BY_ALGORITHM[zonemd.algorithm]
        else:
            zonemd.digest = b'\0' * len(zonemd.digest)

    # Calculate the digest.
    digest = calculate_zonemd(zone)

    # Restore ZONEMD.
    for zonemd in zone.find_rdataset(zone_name, ZONEMD_RTYPE).items:
        zonemd.digest = original_digests[zonemd.algorithm]

    # Verify the digest in the zone matches the calculated value.
    if digest != original_digests[ZONEMD_DIGEST_SHA384]:
        zonemd_b2a = binascii.b2a_hex(original_digest[ZONEMD_DIGEST_SHA384])
        zonemd_hex = zonemd_b2a.decode()
        digest_hex = binascii.b2a_hex(digest).decode()
        err = ("ZONEMD digest " + zonemd_hex + " does not " +
               "match calculated digest " + digest_hex)
        return False, err

    # Everything matches, enjoy your zone.
    return True, ""
Example #7
0
def add_zonemd(zone, zonemd_algorithm='sha384', zonemd_ttl=None):
    """
    Add a ZONEMD record to a zone. This also removes any existing
    ZONEMD records in the zone.

    The ZONEMD record will be at the zone apex, and have an all-zero
    digest.

    If the TTL is not specified, then the TTL of the SOA record is
    used.

    @var zone: The zone object to update.
    @type zone: dns.zone.Zone
    @var zonemd_algorithm: The name of the algorithm to use,
                      "sha384", or the number of the algorithm to use.
    @type zonemd_algorithm: str
    @var zonemd_ttl: The TTL to use for the ZONEMD record, or None to
                     get this from the zone SOA.
    @type zonemd_ttl: int
    @rtype: dns.rdataset.Rdataset
    @raises ZoneDigestUnknownAlgorithm: zonemd_algorithm is unknown

    Returns the placeholder ZONEMD record added, as a ZONEMD object.
    """
    if zonemd_algorithm in ('sha384', 1):
        algorithm = 1
    else:
        msg = 'Unknown digest ' + zonemd_algorithm
        raise ZoneDigestUnknownAlgorithm(msg)

    empty_digest = _EMPTY_DIGEST_BY_ALGORITHM[algorithm]

    # Remove any existing ZONEMD from the zone.
    # Also find the first name, which will be the zone name.
    for name in zone:
        zone.delete_rdataset(name, ZONEMD_RTYPE)
    zone_name = min(zone.keys())

    # Get the zone name.
    zone_name = min(zone.keys())

    # Get the SOA.
    soa_rdataset = zone.get_rdataset(zone_name, dns.rdatatype.SOA)
    soa = soa_rdataset.items[0]

    # Get the TTL to use for our placeholder ZONEMD.
    if zonemd_ttl is None:
        zonemd_ttl = soa_rdataset.ttl

    # Build placeholder ZONEMD and add to the zone.
    placeholder = dns.rdataset.Rdataset(dns.rdataclass.IN, ZONEMD_RTYPE)
    placeholder.update_ttl(zonemd_ttl)
    placeholder_rdata = ZONEMD(dns.rdataclass.IN, soa.serial,
                               algorithm, empty_digest)
    placeholder.add(placeholder_rdata)
    zone.replace_rdataset(zone_name, placeholder)

    return placeholder_rdata
Example #8
0
def test_write_after_rollback(zone):
    with pytest.raises(ExpectedException):
        with zone.writer() as txn:
            a99 = dns.name.from_text("a99", None)
            rds = dns.rdataset.from_text("in", "a", 300, "10.0.0.99")
            txn.add(a99, rds)
            raise ExpectedException
    with zone.writer() as txn:
        a99 = dns.name.from_text("a99", None)
        rds = dns.rdataset.from_text("in", "a", 300, "10.99.99.99")
        txn.add(a99, rds)
    assert zone.get_rdataset("a99", "a") == rds
Example #9
0
def test_write_after_rollback(zone):
    with pytest.raises(ExpectedException):
        with zone.writer() as txn:
            a99 = dns.name.from_text('a99', None)
            rds = dns.rdataset.from_text('in', 'a', 300, '10.0.0.99')
            txn.add(a99, rds)
            raise ExpectedException
    with zone.writer() as txn:
        a99 = dns.name.from_text('a99', None)
        rds = dns.rdataset.from_text('in', 'a', 300, '10.99.99.99')
        txn.add(a99, rds)
    assert zone.get_rdataset('a99', 'a') == rds
Example #10
0
def validate_zonemd(zone):
    """
    Validate the digest of the zone.

    @var zone: The zone object to validate.
    @type zone: dns.zone.Zone
    @rtype: (bool, str) tuple

    Returns a tuple of (success code, error message). The success code
    is True if the digest is correct, and False otherwise. The error
    message is "" if there is no error, otherwise a description of the
    problem.
    """
    # Get the SOA and ZONEMD records for the zone.
    zone_name = min(zone.keys())
    soa_rdataset = zone.get_rdataset(zone_name, dns.rdatatype.SOA)
    soa = soa_rdataset.items[0]
    zonemd = zone.find_rdataset(zone_name, ZONEMD_RTYPE).items[0]

    # Verify that the SOA matches between the SOA and the ZONEMD.
    if soa.serial != zonemd.serial:
        err = ("SOA serial " + str(soa.serial) + " does not " +
               "match ZONEMD serial " + str(zonemd.serial))
        return False, err

    # Verify that we understand the digest algorithm.
    if zonemd.algorithm not in _EMPTY_DIGEST_BY_ALGORITHM:
        err = "Unknown digest algorithm " + str(zonemd.algorithm)
        return False, err

    # Put a placeholder in for the ZONEMD.
    original_digest = zonemd.digest
    zonemd.digest = _EMPTY_DIGEST_BY_ALGORITHM[zonemd.algorithm]

    # Calculate the digest and restore ZONEMD.
    digest = calculate_zonemd(zone, zonemd.algorithm)
    zonemd.digest = original_digest

    # Verify the digest in the zone matches the calculated value.
    if digest != zonemd.digest:
        zonemd_hex = binascii.b2a_hex(zonemd.digest).decode()
        digest_hex = binascii.b2a_hex(digest).decode()
        err = ("ZONEMD digest " + zonemd_hex + " does not " +
               "match calculated digest " + digest_hex)
        return False, err

    # Everything matches, enjoy your zone.
    return True, ""
Example #11
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)
Example #12
0
def update_serial(zone):
    soa = zone.get_rdataset('@', dns.rdatatype.SOA)[0]
    soa.serial += 1
Example #13
0
 def _get_zonemd(self, zone):
     return zone.get_rdataset(zone.origin, 'ZONEMD')
Example #14
0
def _check_zones(zone_list, add_rrsets, delete_rrsets):
    """Apply changes to zones and run named-checkzone.
    
    This function always returns None.  Its only effect is to raise an 
    exception if the updates cause named-checkzone to fail.
    
    This function will check to ensure that deleted records existed previously,
    and added records did not exist.  This duplicates the checks built into the
    DNS UPDATE message, but this is necessary because relying only on the DNS
    UPDATE constraints could mean that updates to one zone get processed but
    updates to another get rejected.  We want to try as hard as possible to make
    this all-or-nothing.
    """
    
    zones = {}
    
    # Acquire a copy of each zone.
    for zone in zone_list:
        axfr = dns.query.xfr(jinx_global_settings['DNS_NAMESERVER'], zone, relativize=False, port=int(jinx_global_settings['DNS_NAMESERVER_PORT']))
        zones[zone] = dns.zone.from_xfr(axfr, relativize=False)
    
    # Apply the updates.
    for rrset in delete_rrsets:
        zone = zones[_get_zone(rrset.name, zone_list)]
        
        new = _rrset_to_rdataset(rrset)
        existing = zone.get_rdataset(rrset.name, rrset.rdtype)
        
        # Are we deleting or modifying?  If an RRset with this name is in the 
        # add group, then it's actually a modification.
        if rrset.name in [r.name for r in add_rrsets]:
            action = "modify"
        else:
            action = "delete"
        
        if existing is None:
            raise JinxInvalidStateError("You are attempting to %s %s, but this record does not exist in DNS." %
                                        (action, _format_rrset_name(rrset)))
        elif not _rdatasets_are_equal(new, existing):
            raise JinxInvalidStateError("You are attempting to %s %s, but its value in DNS has changed since you constructed your update." %
                                        (action, _format_rrset_name(rrset)))
        
        zone.delete_rdataset(rrset.name, rrset.rdtype)
    
    for rrset in add_rrsets:
        zone = zones[_get_zone(rrset.name, zone_list)]
        
        if zone.get_rdataset(rrset.name, rrset.rdtype) is not None:
            raise JinxInvalidStateError("You are attempting to add %s, but this record already exists in DNS." % 
                                        _format_rrset_name(rrset))
        
        rdataset = _rrset_to_rdataset(rrset)
        zone.replace_rdataset(rrset.name, rdataset)
    
    for name, zone in zones.iteritems():
        zone_file = tempfile.NamedTemporaryFile()
        zone.to_file(zone_file, sorted=False, relativize=False)
        zone_file.flush()
        
        # Just flush, but don't close, the temp file, because Python temp files
        # are deleted once they're closed.
        
        try:
            checkzone = subprocess.Popen("/usr/sbin/named-checkzone -k fail -m fail -n fail -S ignore %s %s" % (name, zone_file.name),
                                         shell=True,
                                         stdout=subprocess.PIPE,
                                         stderr=subprocess.PIPE)
            checkzone.wait()
            
            if checkzone.returncode != 0:
                raise JinxInvalidRequestError("zone %s failed zone check:\n%s" % (name, checkzone.stdout.read()))
        except OSError, exception:
            # Perhaps I should wrap this in a JinxAPIServerError, but really,
            # if I just let it go up, the middleware will do that for me.
            
            raise 
Example #15
0
 def _add_to_zone(zone, name, rtype, input_data):
     rdtype = dns.rdatatype.from_text(rtype)
     rdata = dns.rdata.from_text(dns.rdataclass.IN, rdtype, input_data)
     n = zone.get_rdataset(name, rdtype, create=True)
     n.add(rdata, ttl=3600)
     return zone
Example #16
0
 def _read_zone(self, path):
     zone = dns.zone.from_file(path, check_origin=False, allow_include=False)
     rd_set = zone.get_rdataset('', SOA)
     soa = rd_set[0]  # Only one SOA is allowed per zone
     return zone.origin.to_text(omit_final_dot=True), soa.serial