--- /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)
--- 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) :
"""
--- 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)
+
--- 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
--- 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'), [