# HG changeset patch # User Tero Marttila # Date 1238705971 -10800 # Node ID 86b05c0ab5cd3de8bd931dcaa51b44d9b22fca54 # Parent 8b633782f02d1707d41c64458a561c080143e0ae generate a db.paivola.fi file diff -r 8b633782f02d -r 86b05c0ab5cd bind.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bind.py Thu Apr 02 23:59:31 2009 +0300 @@ -0,0 +1,187 @@ +""" + High-level BIND stuff +""" + +from __future__ import with_statement + +import bind_conf as bindc + +import os.path, datetime + +DEFAULT_TTL = bindc.Interval(3600) + +class Settings (object) : + """ + A set of basic settings for a zone, mostly default TTL/refresh/retry/expire/minimum settings + """ + + def __init__ (self, ttl, hostmaster, refresh, retry, expire, minimum) : + self.ttl = ttl + self.hostmaster = hostmaster + self.refresh = refresh + self.retry = retry + self.expire = expire + self.minimum = minimum + +class AutoSerial (object) : + """ + Automatically generate the next serial to use by loading it from a file. + + The generated serials are in YYYYMMDDXX format. + """ + + def __init__ (self, path) : + """ + Load the current serial + + @param path the path to the serial file + """ + + # store + self.path = path + + # load it + # XXX: locking + serial = self.read() + + # current date + today = datetime.date.today() + + # parse it + if serial : + date, code = self.parse(serial) + + else : + date, code = today, 0 + + # increment it + date, code = self.next(date, code) + + # format it + self._serial = self.build(date, code) + + # write it out + self.write(self._serial) + + def parse (self, serial) : + """ + Parse the given serial into a (datetime.date, code) format + """ + + # build it into a date + date = datetime.date( + year = int(serial[0:4]), + month = int(serial[4:6]), + day = int(serial[6:8]) + ) + + code = int(serial[8:]) + + return date, code + + def next (self, date, code) : + """ + Return the next valid serial following the given one + """ + + # current date + today = datetime.date.today() + + # now to increment? + if date < today : + # jump to today's first serial + date = today + code = 0 + + else : + # today or overflowed into the future, just increment the code + code += 1 + + # code overflowed into next day? + if code > 99 : + date += datetime.timedelta(days=1) + code = 0 + + # ok + return date, code + + def build (self, date, code) : + """ + Build a serial code the given date/code + """ + + assert 0 <= code <= 99 + + return "%s%02d" % (date.strftime("%Y%m%d"), code) + + def read (self) : + """ + Read the current serial, returning it, or None, if not found... + """ + + # if it doesn't exist, default + if not os.path.exists(self.path) : + return None + + # read it + with open(self.path) as fh : + return fh.read().strip() + + def write (self, serial) : + """ + Write a new serial + """ + + with open(self.path, 'w') as fh : + fh.write("%s\n" % (serial, )) + + def serial (self) : + """ + Return a new, unused serial code (before __init__) + """ + + return self._serial + +class Domain (bindc.ZoneFile) : + """ + A domain has a skeleton of stuff defined, but the rest is $INCLUDE'd from elsewhere, which is useful for + multi-domain setups where the domains are mostly similar + """ + + def __init__ (self, domain, path, nameservers, mailservers, serial, settings, include=None, objs=None) : + """ + @param domain the domain name + @param path the path to the zone file + @param nameservers list of nameservers as labels + @param mailservers list of (pref, label) tuples for MX records + @param serial the serial code to use + @param settings the TTL/SOA settings to use + @param include the optional zonefile to include + @param objs the optional other objects to add to the zonefile + """ + + super(Domain, self).__init__(domain, path) + + # the default TTL + self.add_directive(bindc.TTLDirective(settings.ttl)) + + # the SOA record + self.add_record(bindc.SOA(None, nameservers[0], + settings.hostmaster, serial, settings.refresh, settings.retry, settings.expire, settings.minimum + )) + + # the NS records + for label in nameservers : + self.add_record(bindc.NS(None, label)) + + # the MX records + for pref, label in mailservers : + self.add_record(bindc.MX(None, pref, label)) + + # include? + if include : + self.add_directive(bindc.IncludeDirective(include)) + + if objs : + for obj in objs : + self.add_obj(obj) diff -r 8b633782f02d -r 86b05c0ab5cd bind_conf.py --- a/bind_conf.py Thu Apr 02 22:52:26 2009 +0300 +++ b/bind_conf.py Thu Apr 02 23:59:31 2009 +0300 @@ -32,8 +32,8 @@ @param comment optional comments """ - Object.__init__(comment) - conf.File.__init__(name, path) + Object.__init__(self, comment) + conf.File.__init__(self, name, path) # init self.objects = [] @@ -51,6 +51,21 @@ # various aliases... add_comment = add_record = add_directive = add_obj + def fmt_lines (self) : + """ + Just format all our objects + """ + + # prefix comments + for line in self._fmt_comments() : + yield line + + # and then all objects + for obj in self.objects : + # ...and their lines + for line in obj.fmt_lines() : + yield line + class Comment (Object) : """ A comment, is, well, a comment :) @@ -176,7 +191,7 @@ # then format the line # XXX: TTL? - yield "%30s %4s%4s %8s %s" % (self.label if self.label is not None else '', str(self.ttl) if self.ttl else '', self.cls, self.type, self.rdata) + yield "%-30s %-4s%-4s %-8s %s" % (self.label if self.label is not None else '', str(self.ttl) if self.ttl else '', self.cls, self.type, self.rdata) class SOA (ResourceRecord) : """ diff -r 8b633782f02d -r 86b05c0ab5cd host.py --- a/host.py Thu Apr 02 22:52:26 2009 +0300 +++ b/host.py Thu Apr 02 23:59:31 2009 +0300 @@ -3,6 +3,7 @@ """ import dhcp +import bind_conf as bindc class Interface (object) : """ @@ -50,3 +51,10 @@ # build it yield dhcp.Host(name, iface.addr, self.address) + def build_bind_domain_records (self, origin) : + """ + Build and yield one or more forward records (A/AAAA) for the host, with the given domain as the origin + """ + + yield bindc.A(self.hostname, self.address) + diff -r 8b633782f02d -r 86b05c0ab5cd main.py --- a/main.py Thu Apr 02 22:52:26 2009 +0300 +++ b/main.py Thu Apr 02 23:59:31 2009 +0300 @@ -1,6 +1,6 @@ #!/usr/bin/env python2.5 -import data, dhcp_conf, dhcp +import data, dhcp, bind_conf, bind import optparse, itertools @@ -14,7 +14,9 @@ # define our options parser = optparse.OptionParser(usage=usage) - parser.add_option('--dhcpd-conf', dest='dhcpd_conf', metavar='PATH', help="path to dhcpd.conf", default='/etc/dhcp3/dhcpd.conf') + parser.add_option('--dhcpd-conf', dest='dhcpd_conf', metavar="PATH", help="path to dhcpd.conf", default='/etc/dhcp3/dhcpd.conf') + parser.add_option('--bind-zone', dest='bind_zone', metavar="PATH", help="path to bind zone file", default=None) + parser.add_option('--autoserial', dest='autoserial_path', metavar="PATH", help="path to autoserial file", default='autoserial') # parse them options, args = parser.parse_args(args=argv[1:]) @@ -30,7 +32,7 @@ Write the DHCP config module using the data loaded from the given module """ - # build the config f file + # build the config file config = dhcp.Config(path=options.dhcpd_conf, settings = settings.dhcp_settings, options = settings.dhcp_options, @@ -42,6 +44,28 @@ # write it out config.write() +def write_bind (options, settings) : + """ + Write a BIND config for a forward zone + """ + + assert options.bind_zone + + # load the serial + autoserial = bind.AutoSerial(options.autoserial_path) + + # build the zone file + zone = bind.Domain(domain=settings.domain, path=options.bind_zone, + nameservers = settings.nameservers, + mailservers = [((i+1)*10, label) for i, label in enumerate(settings.mailservers)], + serial = autoserial.serial(), + settings = settings.bind_settings, + objs = itertools.chain(*[host.build_bind_domain_records(settings.domain) for host in settings.hosts]), + ) + + # write it out + zone.write() + def main (argv) : """ Our app entry point, parse args, load data, write out the config files @@ -55,6 +79,7 @@ # write out the config files write_dhcp(options, data_module) + write_bind(options, data_module) if __name__ == '__main__' : from sys import argv diff -r 8b633782f02d -r 86b05c0ab5cd settings/hosts.py --- a/settings/hosts.py Thu Apr 02 22:52:26 2009 +0300 +++ b/settings/hosts.py Thu Apr 02 23:59:31 2009 +0300 @@ -1,14 +1,40 @@ from addr import IP, Network from host import Interface, Host from dhcp import Subnet +from bind import Settings as BindSettings +from bind_conf import Interval -dhcp_settings = { +# BIND stuff +domain = "paivola.fi" + +nameservers = [ + "ranssi.paivola.fi", + "misc1.idler.fi", + "misc2.idler.fi", + "srv.marttila.de", + ] + +mailservers = [ + "mail.paivola.fi", + ] + +bind_settings = BindSettings( + ttl = 3601, + hostmaster = "hostmaster", + refresh = Interval(h=1), + retry = Interval(m=3), + expire = Interval(d=28), + minimum = Interval(60) + ) + +# DHCP stuff +dhcp_settings = { 'default-lease-time': 43200, 'max-lease-time': 86400, 'authorative': None, } -dhcp_options = { +dhcp_options = { 'domain-name-servers': IP('194.197.235.145'), } @@ -18,6 +44,7 @@ Subnet(Network('192.168.0.0/23'), router_idx=1, unknown_clients='deny', comment="Internal network"), ] +# general stuff hosts = [ Host('jumpgate', IP('194.197.235.1'), [ ]), Host('mikk4', IP('194.197.235.72'), [