net-dnsserver/dns/zone.py

114 lines
3.4 KiB
Python

#!/usr/bin/env python2
""" Zones of domain name space
See section 6.1.2 of RFC 1035 and section 4.2 of RFC 1034.
Instead of tree structures we simply use dictionaries from domain names to
zones or record sets.
These classes are merely a suggestion, feel free to use something else.
"""
from resource import ResourceRecord, RecordData
from types import Type
from classes import Class
class Catalog(object):
""" A catalog of zones """
def __init__(self):
""" Initialize the catalog """
self.zones = {}
def add_zone(self, name, zone):
""" Add a new zone to the catalog
Args:
name (str): root domain name
zone (Zone): zone
"""
self.zones[name] = zone
def find_nearest(self, name):
""" Find the zone that best matches 'name'
"""
name = name + '.'
while name:
if name in self.zones:
return self.zones[name]
name = '.'.join(name.split('.')[1:])
return None
from collections import defaultdict
defdict = lambda: defaultdict(defdict)
CONTENT_KEY = None # the dicts otherwise store strings, so this won't give a collision
class Zone(object):
""" A zone in the domain name space """
def __init__(self):
""" Initialize the Zone """
self.records = defdict()
def add_node(self, name, record_set):
""" Add a record set to the zone
Args:
name (str): domain name
record_set ([ResourceRecord]): resource records
"""
rec = self.records
for n in reversed(name.split('.')):
rec = rec[n]
if CONTENT_KEY in rec: # no collisions
rec[CONTENT_KEY] += record_set
else:
rec[CONTENT_KEY] = record_set
def lookup(self, name):
""" Find the tree node that best matches name
Possible matches are:
- no
- star (the last match is a *.a.b),
- auth (the match is probably a NS record),
- full (the name was found entirely)
"""
rec = self.records
match_type = 'no'
for i,n in enumerate(reversed(name.split('.'))):
if n in rec:
rec = rec[n]
elif '*' in rec and i == len(name.split('.'))-1:
rec = rec['*']
match_type = 'star'
break
else:
match_type = 'auth'
break # there might be nameservers here
else:
match_type = 'full'
if CONTENT_KEY in rec:
return (match_type, rec[CONTENT_KEY])
else:
return ('no', [])
def read_master_file(self, filename):
""" Read the zone from a master file
See section 5 of RFC 1035.
Args:
filename (str): the filename of the master file
"""
with open(filename) as f:
lines = f.readlines()
records = []
for line in lines:
line = line.strip()
if line.startswith(';') or not line: continue
name, ttl, type_, data = line.split()
t = Type.by_string[type_]
rdata = RecordData.create(t, data)
self.add_node(name, [ResourceRecord(name, t, Class.IN, int(ttl), rdata)])