114 lines
3.4 KiB
Python
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)])
|