def test_update_serial(zone): # basic with zone.writer() as txn: txn.update_serial() rdataset = zone.find_rdataset("@", "soa") assert rdataset[0].serial == 2 # max with zone.writer() as txn: txn.update_serial(0xFFFFFFFF, False) rdataset = zone.find_rdataset("@", "soa") assert rdataset[0].serial == 0xFFFFFFFF # wraparound to 1 with zone.writer() as txn: txn.update_serial() rdataset = zone.find_rdataset("@", "soa") assert rdataset[0].serial == 1 # trying to set to zero sets to 1 with zone.writer() as txn: txn.update_serial(0, False) rdataset = zone.find_rdataset("@", "soa") assert rdataset[0].serial == 1 with pytest.raises(KeyError): with zone.writer() as txn: txn.update_serial(name=dns.name.from_text("unknown", None)) with pytest.raises(ValueError): with zone.writer() as txn: txn.update_serial(-1) with pytest.raises(ValueError): with zone.writer() as txn: txn.update_serial(2**31)
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, ""
def dnszone (account): zone_file = "/etc/bind/db.local" domain = "localhost" try: zone = dns.zone.from_file(zone_file, domain) for (name, ttl, rdata) in zone.iterate_rdatas(SOA): print rdata serial = rdata.serial new_serial = datetime.now().strftime("%Y%m%d%H%M%S") print "Changing SOA serial from %s to %s" % (serial, new_serial) rdata.serial = int(new_serial) rdata.mname = dns.name.Name(("ns1", "codeless-hosting", "co", "")) print "Adding record of type A:", account.domain NS_add = "@" target = dns.name.Name(("ns1", "codeless-hosting", "co", "")) print "Adding record of type NS:", NS_add rdataset = zone.find_rdataset(NS_add, rdtype=NS, create=True) rdata = dns.rdtypes.ANY.NS.NS(IN, NS, target) rdataset.add(rdata, ttl=86400) rdataset = zone.find_rdataset("www", rdtype=A, create=True) rdata = dns.rdtypes.IN.A.A(IN, A, address="192.168.1.30") rdataset.add(rdata, ttl=86400) new_zone_file = "/etc/bind/zones/db.%s" % account.domain zone.to_file(new_zone_file) except DNSException, e: print e
def update_zonemd(zone, zonemd_algorithm='sha384'): """ Calculate the digest of the zone and update the ZONEMD record's digest value with that. The ZONEMD record must already be present, for example having been added by the add_zonemd() function. This function does *not* change the serial value of the ZONEMD record. @var zone: The zone object to update. @type zone: dns.zone.Zone @var zonemd_algorithm: The name of the algorithm to use, "sha384". @type zonemd_algorithm: str @rtype: dns.rdataset.Rdataset @raises ZoneDigestUnknownAlgorithm: zonemd_algorithm is unknown Returns the ZONEMD record added, as a ZONEMD object. """ zone_name = min(zone.keys()) digest = calculate_zonemd(zone, zonemd_algorithm) zonemd = zone.find_rdataset(zone_name, ZONEMD_RTYPE).items[0] zonemd.digest = digest return zonemd
def check_record(self, key, ttype, value, domain): """Check if record already exist in domain AXFR used because resolver can also resolve addresses from remote servers""" try: xfr = dns.query.xfr('127.0.0.1', domain ,keyring=self.keyring, keyalgorithm=str(self.conf.bind_secret_algorithm).lower()) zone = dns.zone.from_xfr(xfr) rdataset = zone.find_rdataset(key, rdtype=ttype) for rdata in rdataset: if ttype in ['NS','CNAME', 'PTR']: address = rdata.target elif ttype == 'MX': address = rdata.exchange else: address = rdata.address if str(address).rstrip('.') == str(value): return True except DNSException or FormError: self.log.info('Cannot make axfr query to domain %s' % domain) except KeyError: # record not found pass except AttributeError as e: self.log.error('BUG: Got attribute error on check_record') self.log.exception(e) return False
def modify_dns(new_IP): domain = "cap.com" zoneFile = "/etc/bind/zones/db.cap.com" zone = dns.zone.from_file(zoneFile, domain) change = "www" rdataset = zone.find_rdataset(change, rdtype='A') req_reload = False for rdata in rdataset: if rdata.address != new_IP: req_reload = True rdata.address = new_IP if req_reload: for (name, ttl, rdata) in zone.iterate_rdatas('SOA'): serial = rdata.serial + 1 rdata.serial = serial zone.to_file(zoneFile) proc = Popen(["rndc", "reload", "cap.com"], stdout = PIPE, stderr = PIPE) out,err = proc.communicate() if err: print err else: pass #print "DNS zone modification complete" else: pass
def get_serial_no(self, zone): """ Obtain the serial number from the SOA of a zone """ rdataset = zone.find_rdataset(zone.origin, dns.rdatatype.from_text(RRTYPE_SOA)) return rdataset.items[0].serial
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)
def _increment_serial(self, zone): """Increment the serial number in the zone. Arguments: zone -- a dnspython.zone.Zone object """ soa_rd = zone.find_rdataset(zone.origin, "SOA") soa = soa_rd.items[0] soa.serial += 1
def generateOneCountry(category, zone, hosts): for h in hosts: domainname = domainTemplateToName(category.GeoDNSDomain, h.country) n = dns.name.from_text(domainname) try: ips = [IPy.IP(h.name)] except ValueError: ips = name_to_ips(h.name) except: continue for ip in ips: if ip.version() == 4: rdatasetA = zone.find_rdataset(n, rdtype=A, create=True) rdata = dns.rdtypes.IN.A.A(IN, A, ip.strNormal()) rdatasetA.add(rdata, ttl=default_ttl) elif ip.version() == 6: rdatasetAAAA = zone.find_rdataset(n, rdtype=AAAA, create=True) rdata = dns.rdtypes.IN.AAAA.AAAA(IN, AAAA, ip.strNormal()) rdatasetAAAA.add(rdata, ttl=default_ttl) else: continue
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, ""
def update_domain(domain, zonefile, secret): ''' Updates the given domain with the new ACME secret. ''' zone = dns.zone.from_file(zonefile, domain) # updata soa serial for (_, _, rdata) in zone.iterate_rdatas(SOA): rdata.serial += 1 # update the acme challenge acme_record = zone.find_rdataset('_acme-challenge', rdtype=TXT) for rdata in acme_record: rdata.strings = [secret.encode("utf8")] zone.to_file(zonefile)
def make_xfr(zone): q = dns.message.make_query(zone.origin, 'AXFR') msg = dns.message.make_response(q) if zone.relativize: msg.origin = zone.origin soa_name = dns.name.empty else: soa_name = zone.origin soa = zone.find_rdataset(soa_name, 'SOA') add_rdataset(msg, soa_name, soa) for (name, rds) in zone.iterate_rdatasets(): if rds.rdtype == dns.rdatatype.SOA: continue add_rdataset(msg, name, rds) add_rdataset(msg, soa_name, soa) return [msg]
def setup_serial(zone): serial=0 fn=os.environ.get("ZONE_UTIL_SERIAL_FILE") or "/var/bind/serial.txt" if os.path.isfile(fn): fh=open(fn,'r') serial=int(fh.readline()) fh.close() soa=zone.find_rdataset(zone.origin,'SOA')[0] serial+=1 soa.serial=serial fh=open(fn,'w') fh.write(str(serial) +"\n") fh.close() return zone
def setup_serial(zone): serial = 0 fn = os.environ.get("ZONE_UTIL_SERIAL_FILE") or "/var/bind/serial.txt" if os.path.isfile(fn): fh = open(fn, 'r') serial = int(fh.readline()) fh.close() soa = zone.find_rdataset(zone.origin, 'SOA')[0] serial += 1 soa.serial = serial fh = open(fn, 'w') fh.write(str(serial) + "\n") fh.close() return zone
def append(ip_adres=None, hostname=None, UUID=None): if mongo.db.users.find_one({'token': UUID}): zonefile = '/etc/bind/db.example.com' zone = dns.zone.from_file(zonefile, os.path.basename(zonefile)) rdataset = zone.find_rdataset(hostname, dns.rdatatype.A, create=True) rdata = dns.rdtypes.IN.A.A(dns.rdataclass.IN, dns.rdatatype.A, ip_adres) rdataset.add(rdata, 86400) zone.to_file(zonefile) subprocess.call(["sudo", "rndc", "reload"]) filter = {'username': session['username']} update = {"$push": {"fqdns": hostname}} mongo.db.users.update_one(filter, update) return { "message": "new A-record with ip {} and hostname {} inserted".format( ip_adres, hostname) } else: return {"message": "not authorized"}
def generate_zone_file(origin): """Generates a zone file. Accepts the zone origin as string (no trailing dot). Returns the contents of a zone file that contains all the resource records associated with the domain with the provided origin. """ Domain = get_model('powerdns_manager', 'Domain') Record = get_model('powerdns_manager', 'Record') the_domain = Domain.objects.get(name__exact=origin) the_rrs = Record.objects.filter(domain=the_domain).order_by('-type') # Generate the zone file origin = Name((origin.rstrip('.') + '.').split('.')) # Create an empty dns.zone object. # We set check_origin=False because the zone contains no records. zone = dns.zone.from_text('', origin=origin, relativize=False, check_origin=False) rdclass = dns.rdataclass._by_text.get('IN') for rr in the_rrs: # Add trailing dot to rr.name record_name = rr.name.rstrip('.') + '.' if rr.type == 'SOA': # Add SOA Resource Record # SOA content: primary hostmaster serial refresh retry expire default_ttl bits = rr.content.split() # Primary nameserver of SOA record primary = bits[0].rstrip('.') + '.' mname = Name(primary.split('.')) # Responsible hostmaster from SOA record hostmaster = bits[1].rstrip('.') + '.' rname = Name(hostmaster.split('.')) rdtype = dns.rdatatype._by_text.get('SOA') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.SOA.SOA(rdclass, rdtype, mname = mname, rname = rname, serial = int(bits[2]), refresh = int(bits[3]), retry = int(bits[4]), expire = int(bits[5]), minimum = int(bits[6]) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'NS': # Add NS Resource Record rdtype = dns.rdatatype._by_text.get('NS') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.NS.NS(rdclass, rdtype, target = Name((rr.content.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'MX': # Add MX Resource Record rdtype = dns.rdatatype._by_text.get('MX') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.MX.MX(rdclass, rdtype, preference = int(rr.prio), exchange = Name((rr.content.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'TXT': # Add TXT Resource Record rdtype = dns.rdatatype._by_text.get('TXT') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.TXT.TXT(rdclass, rdtype, strings = [rr.content.strip('"')] ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'CNAME': # Add CNAME Resource Record rdtype = dns.rdatatype._by_text.get('CNAME') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.CNAME.CNAME(rdclass, rdtype, target = Name((rr.content.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'A': # Add A Resource Record rdtype = dns.rdatatype._by_text.get('A') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.IN.A.A(rdclass, rdtype, address = rr.content ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'AAAA': # Add AAAA Resource Record rdtype = dns.rdatatype._by_text.get('AAAA') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.IN.AAAA.AAAA(rdclass, rdtype, address = rr.content ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'SPF': # Add SPF Resource Record rdtype = dns.rdatatype._by_text.get('SPF') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.SPF.SPF(rdclass, rdtype, strings = [rr.content.strip('"')] ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'PTR': # Add PTR Resource Record rdtype = dns.rdatatype._by_text.get('PTR') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.PTR.PTR(rdclass, rdtype, target = Name((rr.content.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'SRV': # Add SRV Resource Record # weight port target weight, port, target = rr.content.split() rdtype = dns.rdatatype._by_text.get('SRV') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.IN.SRV.SRV(rdclass, rdtype, priority = int(rr.prio), weight = int(weight), port = int(port), target = Name((target.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) # Export text (from the source code of http://www.dnspython.org/docs/1.10.0/html/dns.zone.Zone-class.html#to_file) EOL = '\n' f = StringIO.StringIO() f.write('$ORIGIN %s%s' % (origin, EOL)) zone.to_file(f, sorted=True, relativize=False, nl=EOL) data = f.getvalue() f.close() return data
""" # Define the response of a successful execution of the function http_status = 200 prog_status = 0 explanation = "Successfully created specified SRV record (Service: {} Port: {} A Record: {})".format(service_name, service_port, A_record) domain = "simplicify.com" zone_file = "/etc/bind/zones/db.%s" % domain new_zone_file = "new.db.%s" % domain try: zone = dns.zone.from_file(zone_file, domain) except DNSException, e: print e.__class__, e n = dns.name.from_text(A_record) rdataset = zone.find_rdataset(service_name, rdtype='SRV', create=True) rdata = dns.rdtypes.IN.SRV.SRV(IN,SRV,0,0,service_port,target=n) rdataset.add(rdata, ttl=86400) print str(rdataset) print str(rdata) try: zone.to_file(new_zone_file) except: explanation = "An unknown error occurred adding the specified SRV record (Service: {} Port: {} A Record: {})".format(service_name, service_port, A_record) status = 500 return [ 1, status, explanation, "" ] payload = "" returns = [ prog_status, http_status, explanation, payload ] return returns
def generate_zone_file(origin): """Generates a zone file. Accepts the zone origin as string (no trailing dot). Returns the contents of a zone file that contains all the resource records associated with the domain with the provided origin. """ Domain = cache.get_model('powerdns_manager', 'Domain') Record = cache.get_model('powerdns_manager', 'Record') the_domain = Domain.objects.get(name__exact=origin) the_rrs = Record.objects.filter(domain=the_domain).order_by('-type') # Generate the zone file origin = Name((origin.rstrip('.') + '.').split('.')) # Create an empty dns.zone object. # We set check_origin=False because the zone contains no records. zone = dns.zone.from_text('', origin=origin, relativize=False, check_origin=False) rdclass = dns.rdataclass._by_text.get('IN') for rr in the_rrs: # Add trailing dot to rr.name record_name = rr.name.rstrip('.') + '.' if rr.type == 'SOA': # Add SOA Resource Record # SOA content: primary hostmaster serial refresh retry expire default_ttl bits = rr.content.split() # Primary nameserver of SOA record primary = bits[0].rstrip('.') + '.' mname = Name(primary.split('.')) # Responsible hostmaster from SOA record hostmaster = bits[1].rstrip('.') + '.' rname = Name(hostmaster.split('.')) rdtype = dns.rdatatype._by_text.get('SOA') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.SOA.SOA(rdclass, rdtype, mname = mname, rname = rname, serial = int(bits[2]), refresh = int(bits[3]), retry = int(bits[4]), expire = int(bits[5]), minimum = int(bits[6]) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'NS': # Add NS Resource Record rdtype = dns.rdatatype._by_text.get('NS') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.NS.NS(rdclass, rdtype, target = Name((rr.content.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'MX': # Add MX Resource Record rdtype = dns.rdatatype._by_text.get('MX') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.MX.MX(rdclass, rdtype, preference = int(rr.prio), exchange = Name((rr.content.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'TXT': # Add TXT Resource Record rdtype = dns.rdatatype._by_text.get('TXT') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.TXT.TXT(rdclass, rdtype, strings = rr.content.split(';') ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'CNAME': # Add CNAME Resource Record rdtype = dns.rdatatype._by_text.get('CNAME') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.CNAME.CNAME(rdclass, rdtype, target = Name((rr.content.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'A': # Add A Resource Record rdtype = dns.rdatatype._by_text.get('A') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.IN.A.A(rdclass, rdtype, address = rr.content ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'AAAA': # Add AAAA Resource Record rdtype = dns.rdatatype._by_text.get('AAAA') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.IN.AAAA.AAAA(rdclass, rdtype, address = rr.content ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'SPF': # Add SPF Resource Record rdtype = dns.rdatatype._by_text.get('SPF') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.SPF.SPF(rdclass, rdtype, strings = rr.content.split(';') ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'PTR': # Add PTR Resource Record rdtype = dns.rdatatype._by_text.get('PTR') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.ANY.PTR.PTR(rdclass, rdtype, target = Name((rr.content.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) elif rr.type == 'SRV': # Add SRV Resource Record # weight port target weight, port, target = rr.content.split() rdtype = dns.rdatatype._by_text.get('SRV') rdataset = zone.find_rdataset(record_name, rdtype=rdtype, create=True) rdata = dns.rdtypes.IN.SRV.SRV(rdclass, rdtype, priority = int(rr.prio), weight = int(weight), port = int(port), target = Name((target.rstrip('.') + '.').split('.')) ) rdataset.add(rdata, ttl=int(rr.ttl)) # Export text (from the source code of http://www.dnspython.org/docs/1.10.0/html/dns.zone.Zone-class.html#to_file) EOL = '\r\n' f = StringIO.StringIO() f.write('$ORIGIN %s%s' % (origin, EOL)) zone.to_file(f, sorted=True, relativize=False, nl=EOL) data = f.getvalue() f.close() return data
def update_zone(self, zone_name, zi, db_soa_serial=None, candidate_soa_serial=None, force_soa_serial_update=False, wrap_serial_next_time=False, date_stamp=None, nsec3_seed=False, clear_dnskey=False, clear_nsec3=False): """ Use dnspython to update a Zone in the DNS server Use wrap_serial_next_time to 'fix' SOA serial numbers grossly not in the operations format YYYYMMDDnn. date is a datetime object in localtime. """ # Read in via AXFR zone for comparison purposes try: zone, dnskey_flag, nsec3param_flag = self.read_zone(zone_name) update_info = { 'dnskey_flag': dnskey_flag, 'nsec3param_flag': nsec3param_flag } except NoSuchZoneOnServerError as exc: msg = str(exc) #return (RCODE_FATAL, msg, None, None) # Send RESET as server not configured yet. return (RCODE_RESET, msg, None, None) except (dns.query.UnexpectedSource, dns.query.BadResponse) as exc: msg = ("Zone '%s', - server %s not operating correctly." % (zone_name, server.server_name)) return (RCODE_FATAL, msg, None, None) except (IOError, OSError) as exc: if exc.errno in (errno.EACCES, errno.EPERM, errno.ECONNREFUSED, errno.ENETUNREACH, errno.ETIMEDOUT): msg = ("Zone '%s' - server %s:%s not available - %s" % (zone_name, self.server, self.port, exc.strerror)) return (RCODE_ERROR, msg, None, None) msg = ("Zone '%s' - server %s:%s, fatal error %s." % (zone_name, self.server, self.port, exc.strerror)) return (RCODE_FATAL, msg, None, None) # Get current SOA record for zone to include as prerequiste in update # Makes update transaction idempotent current_soa_rr = zone.find_rdataset(zone.origin, RRTYPE_SOA).items[0] update_soa_serial_flag = False curr_serial_no = self.get_serial_no(zone) # In case of a DR failover, our DB can have a more recent serial number # than in name server try: new_serial_no = new_soa_serial_no( curr_serial_no, zone_name, db_soa_serial=db_soa_serial, candidate=candidate_soa_serial, wrap_serial_next_time=wrap_serial_next_time, date_stamp=date_stamp) except SOASerialError as exc: msg = str(exc) if (not sys.stdin.isatty()): log_critical(msg) return (RCODE_FATAL, msg, None, None) if wrap_serial_next_time or force_soa_serial_update: # Apply serial number to SOA record. zi.update_soa_serial(new_serial_no) else: # An increment should only be performed after difference update_soa_serial_flag = True # Compare server_zone with zi.rrs # Find additions and deletions del_rrs = [ rr for rr in zone.iterate_rdatas() if rr not in zi.iterate_dnspython_rrs() ] add_rrs = [ rr for rr in zi.iterate_dnspython_rrs() if rr not in zone.iterate_rdatas() ] # Check if DNSSEC settings need to be changed do_clear_nsec3 = clear_nsec3 and nsec3param_flag do_clear_dnskey = clear_dnskey and dnskey_flag do_nsec3_seed = nsec3_seed and not nsec3param_flag if (not del_rrs and not add_rrs and not do_clear_nsec3 and not do_clear_dnskey and not do_nsec3_seed): msg = ("Domain '%s' not updated as no change detected" % (zone_name)) return (RCODE_NOCHANGE, msg, curr_serial_no, update_info) # Incremental update of SOA serial number soa_rdtype = dns.rdatatype.from_text(RRTYPE_SOA) if update_soa_serial_flag: # Apply serial number to SOA record. zi.update_soa_serial(new_serial_no) # recalculate add_rrs - got to be done or else updates will be # missed add_rrs = [ rr for rr in zi.iterate_dnspython_rrs() if rr not in zone.iterate_rdatas() ] # Groom updates for DynDNS update perculiarities # SOA can never be deleted RFC 2136 Section 3.4.2.3 and 3.4.2.4 # so skip this. del_rrs = [rr for rr in del_rrs if (rr[2].rdtype != soa_rdtype)] # Can never delete the last NS on the root of a zone, # so pre add all '@' NS records (RFC 2136 Sec # 3.4.2.4) tl_label = dns.name.from_text('@', origin=dns.name.empty) ns_rdtype = dns.rdatatype.from_text(RRTYPE_NS) pre_add_rrs = [ rr for rr in add_rrs if (rr[0] == tl_label and rr[2].rdtype == ns_rdtype) ] tl_ns_rdata = [rr[2] for rr in pre_add_rrs] add_rrs = [rr for rr in add_rrs if rr not in pre_add_rrs] # Remove '@' NS delete from del_rrs if record in pre_add_rrs # ie, we are just doing a TTL update! del_rrs = [ rr for rr in del_rrs if (not (rr[0] == tl_label and rr[2] in tl_ns_rdata)) ] # CNAMEs can only be added to vacant nodes, or totally replace # RRSET on a node RFC 2136 Section 3.4.2.2 # Choose to enforce this at zi API level. # DNSSEC processing - prepare NSEC3PARM rdata if do_nsec3_seed: rn = random.getrandbits(int(settings['nsec3_salt_bit_length'])) hash_alg = settings['nsec3_hash_algorithm'] flags = settings['nsec3_flags'] iterations = settings['nsec3_iterations'] nsec3param_rdata = ("%s %s %s %016x" % (hash_alg, flags, iterations, rn)) # Test rn as random can produce garbage sometimes... rdata_list = nsec3param_rdata.split() try: # This is the piece of code where dnspython blows up... stuff = bytes.fromhex(rdata_list[-1]) except Exception: msg = ("Failed to seed NSEC3 salt - SM reset required") return (RCODE_RESET, msg, None, update_info) # Prepare dnspython tsigkeyring keyring = dns.tsigkeyring.from_text( {self.key_name: self.tsig_key['secret']}) if (self.tsig_key['algorithm'] == 'hmac-md5'): key_algorithm = dns.tsig.HMAC_MD5 else: key_algorithm = dns.name.from_text(self.tsig_key['algorithm']) # Create update # We have to use absolute FQDNs on LHS and RHS to make sure updates # to NS etc happen # While doing this also handle wee things for DNSSEC processing origin = dns.name.from_text(zone_name) update = dns.update.Update(origin, keyring=keyring, keyname=self.key_name, keyalgorithm=key_algorithm) update.present(origin, current_soa_rr) for rr in pre_add_rrs: update.add(rr[0], rr[1], rr[2]) for rr in del_rrs: update.delete(rr[0], rr[2]) # Add DNSSEC clearance stuff to end of delete section of update if do_clear_nsec3: update.delete(origin, RRTYPE_NSEC3PARAM) if do_clear_dnskey: update.delete(origin, RRTYPE_DNSKEY) for rr in add_rrs: update.add(rr[0], rr[1], rr[2]) # NSEC3PARAM seeding if do_nsec3_seed: update.add(origin, '0', RRTYPE_NSEC3PARAM, nsec3param_rdata) # Do dee TING! response = dns.query.tcp(update, self.server, port=self.port) # Process reply rcode = response.rcode() rcode_text = dns.rcode.to_text(response.rcode()) success_rcodes = (dns.rcode.NOERROR) if (rcode in self.success_rcodes): msg = ("Update '%s' to domain '%s' succeeded" % (new_serial_no, zone_name)) return (RCODE_OK, msg, new_serial_no, update_info) elif (rcode in self.retry_rcodes): msg = ("Update '%s' to domain '%s' failed: %s - will retry" % (new_serial_no, zone_name, rcode_text)) return (RCODE_ERROR, msg, None, update_info) elif (rcode in self.reset_rcodes): msg = ( "Update '%s' to domain '%s' failed: %s - SM reset required" % (new_serial_no, zone_name, rcode_text)) return (RCODE_RESET, msg, None, update_info) elif (rcode in self.fatal_rcodes): msg = ("Update '%s' to domain '%s' permanently failed: %s" % (new_serial_no, zone_name, rcode_text)) return (RCODE_FATAL, msg, None, update_info) else: msg = ("Update '%s' to domain '%s' permanently failed: '%s'" " - unknown response" % (new_serial_no, zone_name, response.rcode())) return (RCODE_FATAL, msg, None, update_info)
print "Origen de la zona:", zone.origin for (name, ttl, rdata) in zone.iterate_rdatas(SOA): serial = rdata.serial new_serial = serial + 1 print "Cambiando serial del SOA de %d a %d" % (serial, new_serial) rdata.serial = new_serial node_delete = "www2" print "Eliminando nodo", node_delete zone.delete_node(node_delete) A_change = "mail" new_IP = "192.168.2.100" print "Cambiando IPV4 del nodo", A_change, "al", new_IP rdataset = zone.find_rdataset(A_change, rdtype=A) for rdata in rdataset: rdata.address = new_IP rdataset = zone.find_rdataset("@", rdtype=NS) new_ttl = rdataset.ttl / 2 print "Cambiando el TTL de todos los NS a", new_ttl rdataset.ttl = new_ttl A_add = "www3" print "Añadir nuevo nodo www3:", A_add rdataset = zone.find_rdataset(A_add, rdtype=A, create=True) rdata = dns.rdtypes.IN.A.A(IN, A, address="192.168.10.30") rdataset.add(rdata, ttl=86400) new_zone_file = "new.%s" % domain
def update_zone(self, zone_name, zi, db_soa_serial=None, candidate_soa_serial=None, force_soa_serial_update=False, wrap_serial_next_time=False, date_stamp=None, nsec3_seed=False, clear_dnskey=False, clear_nsec3=False): """ Use dnspython to update a Zone in the DNS server Use wrap_serial_next_time to 'fix' SOA serial numbers grossly not in the operations format YYYYMMDDnn. date is a datetime object in localtime. """ # Read in via AXFR zone for comparison purposes try: zone, dnskey_flag, nsec3param_flag = self.read_zone(zone_name) update_info = {'dnskey_flag': dnskey_flag, 'nsec3param_flag': nsec3param_flag} except NoSuchZoneOnServerError as exc: msg = str(exc) #return (RCODE_FATAL, msg, None, None) # Send RESET as server not configured yet. return (RCODE_RESET, msg, None, None) except (dns.query.UnexpectedSource, dns.query.BadResponse) as exc: msg = ("Zone '%s', - server %s not operating correctly." % (zone_name, server.server_name)) return (RCODE_FATAL, msg, None, None) except (IOError, OSError) as exc: if exc.errno in (errno.EACCES, errno.EPERM, errno.ECONNREFUSED, errno.ENETUNREACH, errno.ETIMEDOUT): msg = ("Zone '%s' - server %s:%s not available - %s" % (zone_name, self.server, self.port, exc.strerror)) return (RCODE_ERROR, msg, None, None) msg = ("Zone '%s' - server %s:%s, fatal error %s." % (zone_name, self.server, self.port, exc.strerror)) return (RCODE_FATAL, msg, None, None) # Get current SOA record for zone to include as prerequiste in update # Makes update transaction idempotent current_soa_rr = zone.find_rdataset(zone.origin, RRTYPE_SOA).items[0] update_soa_serial_flag = False curr_serial_no = self.get_serial_no(zone) # In case of a DR failover, our DB can have a more recent serial number # than in name server try: new_serial_no = new_soa_serial_no(curr_serial_no, zone_name, db_soa_serial=db_soa_serial, candidate=candidate_soa_serial, wrap_serial_next_time=wrap_serial_next_time, date_stamp=date_stamp) except SOASerialError as exc: msg = str(exc) if (not sys.stdin.isatty()): log_critical(msg) return (RCODE_FATAL, msg, None, None) if wrap_serial_next_time or force_soa_serial_update: # Apply serial number to SOA record. zi.update_soa_serial(new_serial_no) else: # An increment should only be performed after difference update_soa_serial_flag = True # Compare server_zone with zi.rrs # Find additions and deletions del_rrs = [rr for rr in zone.iterate_rdatas() if rr not in zi.iterate_dnspython_rrs()] add_rrs = [rr for rr in zi.iterate_dnspython_rrs() if rr not in zone.iterate_rdatas()] # Check if DNSSEC settings need to be changed do_clear_nsec3 = clear_nsec3 and nsec3param_flag do_clear_dnskey = clear_dnskey and dnskey_flag do_nsec3_seed = nsec3_seed and not nsec3param_flag if (not del_rrs and not add_rrs and not do_clear_nsec3 and not do_clear_dnskey and not do_nsec3_seed): msg = ("Domain '%s' not updated as no change detected" % (zone_name)) return (RCODE_NOCHANGE, msg, curr_serial_no, update_info) # Incremental update of SOA serial number soa_rdtype = dns.rdatatype.from_text(RRTYPE_SOA) if update_soa_serial_flag: # Apply serial number to SOA record. zi.update_soa_serial(new_serial_no) # recalculate add_rrs - got to be done or else updates will be # missed add_rrs = [rr for rr in zi.iterate_dnspython_rrs() if rr not in zone.iterate_rdatas()] # Groom updates for DynDNS update perculiarities # SOA can never be deleted RFC 2136 Section 3.4.2.3 and 3.4.2.4 # so skip this. del_rrs = [rr for rr in del_rrs if (rr[2].rdtype != soa_rdtype)] # Can never delete the last NS on the root of a zone, # so pre add all '@' NS records (RFC 2136 Sec # 3.4.2.4) tl_label = dns.name.from_text('@', origin=dns.name.empty) ns_rdtype = dns.rdatatype.from_text(RRTYPE_NS) pre_add_rrs = [rr for rr in add_rrs if (rr[0] == tl_label and rr[2].rdtype == ns_rdtype)] tl_ns_rdata = [rr[2] for rr in pre_add_rrs] add_rrs = [rr for rr in add_rrs if rr not in pre_add_rrs] # Remove '@' NS delete from del_rrs if record in pre_add_rrs # ie, we are just doing a TTL update! del_rrs = [rr for rr in del_rrs if (not(rr[0] == tl_label and rr[2] in tl_ns_rdata))] # CNAMEs can only be added to vacant nodes, or totally replace # RRSET on a node RFC 2136 Section 3.4.2.2 # Choose to enforce this at zi API level. # DNSSEC processing - prepare NSEC3PARM rdata if do_nsec3_seed: rn = random.getrandbits(int(settings['nsec3_salt_bit_length'])) hash_alg = settings['nsec3_hash_algorithm'] flags = settings['nsec3_flags'] iterations = settings['nsec3_iterations'] nsec3param_rdata = ("%s %s %s %016x" % (hash_alg, flags, iterations, rn)) # Test rn as random can produce garbage sometimes... rdata_list = nsec3param_rdata.split() try: # This is the piece of code where dnspython blows up... stuff = bytes.fromhex(rdata_list[-1]) except Exception: msg = ("Failed to seed NSEC3 salt - SM reset required") return (RCODE_RESET, msg, None, update_info) # Prepare dnspython tsigkeyring keyring = dns.tsigkeyring.from_text({ self.key_name : self.tsig_key['secret'] }) if (self.tsig_key['algorithm'] == 'hmac-md5'): key_algorithm = dns.tsig.HMAC_MD5 else: key_algorithm = dns.name.from_text(self.tsig_key['algorithm']) # Create update # We have to use absolute FQDNs on LHS and RHS to make sure updates # to NS etc happen # While doing this also handle wee things for DNSSEC processing origin = dns.name.from_text(zone_name) update = dns.update.Update(origin, keyring=keyring, keyname = self.key_name, keyalgorithm=key_algorithm) update.present(origin, current_soa_rr) for rr in pre_add_rrs: update.add(rr[0], rr[1], rr[2]) for rr in del_rrs: update.delete(rr[0], rr[2]) # Add DNSSEC clearance stuff to end of delete section of update if do_clear_nsec3: update.delete(origin, RRTYPE_NSEC3PARAM) if do_clear_dnskey: update.delete(origin, RRTYPE_DNSKEY) for rr in add_rrs: update.add(rr[0], rr[1], rr[2]) # NSEC3PARAM seeding if do_nsec3_seed: update.add(origin, '0', RRTYPE_NSEC3PARAM, nsec3param_rdata) # Do dee TING! response = dns.query.tcp(update, self.server, port=self.port) # Process reply rcode = response.rcode() rcode_text = dns.rcode.to_text(response.rcode()) success_rcodes = (dns.rcode.NOERROR) if (rcode in self.success_rcodes): msg = ("Update '%s' to domain '%s' succeeded" % (new_serial_no, zone_name)) return (RCODE_OK, msg, new_serial_no, update_info) elif (rcode in self.retry_rcodes): msg = ("Update '%s' to domain '%s' failed: %s - will retry" % (new_serial_no, zone_name, rcode_text)) return (RCODE_ERROR, msg, None, update_info) elif (rcode in self.reset_rcodes): msg = ("Update '%s' to domain '%s' failed: %s - SM reset required" % (new_serial_no, zone_name, rcode_text)) return (RCODE_RESET, msg, None, update_info) elif (rcode in self.fatal_rcodes): msg = ("Update '%s' to domain '%s' permanently failed: %s" % (new_serial_no, zone_name, rcode_text)) return (RCODE_FATAL, msg, None, update_info) else: msg = ("Update '%s' to domain '%s' permanently failed: '%s'" " - unknown response" % (new_serial_no, zone_name, response.rcode())) return (RCODE_FATAL, msg, None, update_info)
# Define the response of a successful execution of the function http_status = 200 prog_status = 0 explanation = "Successfully created specified SRV record (Service: {} Port: {} A Record: {})".format( service_name, service_port, A_record) domain = "simplicify.com" zone_file = "/etc/bind/zones/db.%s" % domain new_zone_file = "new.db.%s" % domain try: zone = dns.zone.from_file(zone_file, domain) except DNSException, e: print e.__class__, e n = dns.name.from_text(A_record) rdataset = zone.find_rdataset(service_name, rdtype='SRV', create=True) rdata = dns.rdtypes.IN.SRV.SRV(IN, SRV, 0, 0, service_port, target=n) rdataset.add(rdata, ttl=86400) print str(rdataset) print str(rdata) try: zone.to_file(new_zone_file) except: explanation = "An unknown error occurred adding the specified SRV record (Service: {} Port: {} A Record: {})".format( service_name, service_port, A_record) status = 500 return [1, status, explanation, ""] payload = "" returns = [prog_status, http_status, explanation, payload]
class commands: def __init__(self, simplicify): self.config = simplicify.SimplicifyConfig def ls_SRV(self, service_name): """ Note: This function retrieves all configured S Args: service_name: The symbolic name of the desired service, as defined in Assigned Numbers [STD 2] or locally. An underscore (_) is prepended to the service identifier to avoid collisions with DNS labels that occur in nature. Returns: returns: Array containing the program status code, http status code, humanly readable explanation, and payload """ # Define the response of a successful execution of the function http_status = 200 prog_status = 0 explanation = "Successfully listed all service records with the specified service name ({})".format( service_name) try: response = dns.resolver.query(service_name, 'SRV') except: explanation = "An unknown error occurred listing records for the specified service ({})".format( service_name) status = 500 return [1, status, explanation, ""] results_list = [] for rdata in response: results_list.append(str(rdata.target).rstrip('.')) payload = results_list returns = [prog_status, http_status, explanation, payload] return returns def ls_A(self, dns_name): """ Note: This function retrieves all configured S Args: service_name: The symbolic name of the desired service, as defined in Assigned Numbers [STD 2] or locally. An underscore (_) is prepended to the service identifier to avoid collisions with DNS labels that occur in nature. Returns: returns: Array containing the program status code, http status code, humanly readable explanation, and payload """ # Define the response of a successful execution of the function http_status = 200 prog_status = 0 explanation = "Successfully listed all service records with the specified DNS name ({})".format( dns_name) try: response = dns.resolver.query(dns_name, 'A') except: explanation = "An unknown error occurred listing records for the specified DNS name ({})".format( dns_name) status = 500 return [1, status, explanation, ""] results_list = [] for rdata in response: results_list.append(str(rdata)) payload = results_list returns = [prog_status, http_status, explanation, payload] return returns def create_A(self, record_name, ip_address): """ Note: This function creates an A record on a bind9 DNS server Args: record_name: FQDN for record Returns: returns: Array containing the program status code, http status code, humanly readable explanation, and payload """ # Define the response of a successful execution of the function http_status = 200 prog_status = 0 explanation = "Successfully created specified A record ({})".format( record_name) domain = "simplicify.com" print "Getting zone object for domain", domain zone_file = "/etc/bind/zones/db.%s" % domain try: zone = dns.zone.from_file(zone_file, domain) print "Zone origin:", zone.origin except DNSException, e: print e.__class__, e rdataset = zone.find_rdataset(record_name, rdtype=A, create=True) rdata = dns.rdtypes.IN.A.A(IN, A, address=ip_address) rdataset.add(rdata, ttl=86400) new_zone_file = "new.db.%s" % domain try: zone.to_file(new_zone_file) except: explanation = "An unknown error occurred adding the specified A record ({})".format( dns_name) status = 500 return [1, status, explanation, ""] payload = "" returns = { "prog_status": prog_status, "http_status": http_status, "explanation": explanation, "payload": payload } return returns