bind_conf.py
author Tero Marttila <terom@fixme.fi>
Sun, 12 Jul 2009 00:43:36 +0300
changeset 6 57e8168ba8c4
parent 5 86b05c0ab5cd
permissions -rw-r--r--
use FQDN for zone hosts
"""
    Configuration file output for the ISC DNS server: BIND
"""

import conf

class Object (conf.ConfObject) :
    """
        Our own version of ConfObject that knows how to format comments
    """

    def _fmt_comments (self) :
        """
            Format comments using the standard format:
                ";" <comment>
        """

        for comment in self.comments :
            if comment is not None :
                yield "; %s" % (comment, )

class ZoneFile (Object, conf.File) :
    """
        A zone file containing a bunch of directives, resource records and comments
    """

    def __init__ (self, name, path, ttl=None, comment=None) :
        """
            @param name the name of the zonefile, for status stuff
            @param path the path to the zonefile
            @param ttl default TTL to use
            @param comment optional comments
        """
        
        Object.__init__(self, comment)
        conf.File.__init__(self, name, path)

        # init
        self.objects = []

        if ttl :
            self.add_directive(TTLDirective(ttl))

    def add_obj (self, obj) :
        """
            Add an Object onto the list of things to include in this zone file
        """

        self.objects.append(obj)

    # 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 :)
    """

    def __init__ (self, comment) :
        """
            @param comment the comment string
        """

        Object.__init__(self, [comment])
    
    def fmt_lines (self) :
        """
            Yield a single line with the comment
        """

        return self._fmt_comments()

class Label (object) :
    """
        A label, as used in a ResourceRecord, either as the label, or the rdata for various resource types

        You can also use strs, this just implements a __str__ method
    """

    def __init__ (self, label) :
        """
            @param label the literal label to use
        """

        self.label = label

    def __str__ (self) :
        return self.label

class Origin (Label) :
    """
        A label that represents the zone's origin
    """

    def __init__ (self) :
        pass

    def __str__ (self) :
        return '@'

class FQDN (Label) :
    """
        A label that represents the given external domain (i.e. this adds the . to the end that people always forget).
    """

    def __init__ (self, fqdn) :
        self.fqdn = fqdn

    def __str__ (self) :
        return "%s." % (self.fqdn, )

class Interval (object) :
    """
        A time interval suitable for use in SOA records
    """

    def __init__ (self, s=None, m=None, h=None, d=None) :
        """
            @param s seconds
            @param m minutes
            @param h hours
            @param d days
        """

        self.s = s
        self.m = m
        self.h = h
        self.d = d

    def __str__ (self) :
        """
            If only seconds were given, just return those directly, otherwise, apply units
        """

        if self.s and not self.m and not self.h and not self.d :
            return str(self.s)

        else :
            return "%s%s%s%s" % (
                    "%ds" % self.s if self.s else '',
                    "%dm" % self.m if self.m else '',
                    "%dh" % self.h if self.h else '',
                    "%dd" % self.d if self.d else ''
                )

class ResourceRecord (Object) :
    """
        A generic resource record for a BIND zone file
    """

    def __init__ (self, label, type, rdata, cls='IN', ttl=None, **kwargs) :
        """
            @param label the "name" of this record, or None to referr to the previous record's label
            @param type the type as a string ('A', 'TXT', etc.)
            @param rdata the rdata, as a raw string
            @param cls the class, e.g. 'IN'
            @param ttl the time-to-live value in seconds, or None to omit it
        """

        super(ResourceRecord, self).__init__(**kwargs)

        self.label = label
        self.type = type
        self.rdata = rdata
        self.cls = cls
        self.ttl = ttl

    def fmt_lines (self) :
        """
            Just format the lines, eh
        """
        
        # prefix comments
        for line in self._fmt_comments() :
            yield line

        # 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)

class SOA (ResourceRecord) :
    """
        "Identifies the start of a zone of authority", must be the first record
    """

    def __init__ (self, label, primary_ns, hostmaster, serial, refresh, retry, expire, minimum, **kwargs) :
        """
            @param label the "name" of the zone, usually ORIGIN
            @param primary_ns the address of the primary NS server
            @param hostmaster the mailbox of the zone's hostmaster
            @param serial the serial number of the zone as an integer
            @param refresh time interval between zone refreshes in seconds
            @param retry time interval between retrys for failed refreshes
            @param expire time interval before zone data can no longer be considered authorative
            @param minimum minimum TTL for RRs in this zone
        """

        super(SOA, self).__init__(label, 'SOA', "%s %s ( %s %s %s %s %s )" % (
                primary_ns, hostmaster, serial, refresh, retry, expire, minimum
            ), **kwargs)

class A (ResourceRecord) :
    """
        An IPv4 forward address
    """

    def __init__ (self, label, addr, **kwargs) :
        """
            @param label the "name" of the address
            @param addr the IPv4 target address
        """

        assert(addr.is_v4())

        super(A, self).__init__(label, 'A', addr, **kwargs)

class AAAA (ResourceRecord) :
    """
        An IPv6 forward address
    """

    def __init__ (self, label, addr, **kwargs) :
        """
            @param label the "name" of the address
            @param addr the IPv6 target address
        """
        
        assert(addr.is_v6())

        super(AAAA, self).__init__(label, 'AAAA', addr.strCompressed(), **kwargs)

class CNAME (ResourceRecord) :
    """
        A canonical-name alias
    """

    def __init__ (self, label, target, **kwargs) :
        """
            @param label the "name" of the alias
            @param target the alias target
        """

        super(CNAME, self).__init__(label, 'CNAME', target, **kwargs)

class TXT (ResourceRecord) :
    """
        A human-readable information record
    """

    def __init__ (self, label, text, **kwargs) :
        """
            @param label the "name" of the text record
            @param text the text data, shouldn't contain any quotes...
        """

        super(TXT, self).__init__(label, 'TXT', '"%s"' % text, **kwargs)

class MX (ResourceRecord) :
    """
        A mail-exchange definition
    """

    def __init__ (self, label, pref, exchange, **kwargs) :
        """
            @param label the "name" of the domain to handle mail for
            @param pref the numerical preference for this exchange
            @param exchange the domain name of the mail exchange (SMTP server)
        """

        super(MX, self).__init__(label, 'MX', "%d %s" % (pref, exchange), **kwargs)

class NS (ResourceRecord) :
    """
        An authorative name server
    """

    def __init__ (self, label, nsname, **kwargs) :
        """
            @param label the "name" of the domain to have a nameserver for
            @param nsname the name of the nameserver
        """

        super(NS, self).__init__(label, 'NS', nsname)

class PTR (ResourceRecord) :
    """
        An IPv4/IPv6 reverse address
    """

    def __init__ (self, addr, name, **kwargs) :
        """
            @param addr the addr.IP to map via in-addr.arpa
            @param name the name to map the address to
        """

        # XXX: quick hack, this gives an absolute name
        label = addr.reverseName()
        
        super(PTR, self).__init__(label, 'PTR', name)

class Directive (Object) :
    """
        Special directives that can be used in zone files to control behaviour
    """

    def __init__ (self, name, *args, **kwargs) :
        """
            @param name the $NAME bit
            @param args optional list of space-seprated arguments, Nones are ignored
        """

        super(Directive, self).__init__(**kwargs)

        self.name = name
        self.args = [arg for arg in args if arg is not None]

    def fmt_lines (self) :
        # prefix comments
        for line in self._fmt_comments() :
            yield line

        # then format it
        yield "$%s%s" % (self.name, (' ' + ' '.join(str(arg) for arg in self.args)) if self.args else '')

class OriginDirective (Directive) :
    """
        Set the origin used to resolve the zone's labels.

        Note that the origin label is not absolute by default - use FQDN for that
    """

    def __init__ (self, origin, **kwargs) :
        """
            @param origin the origin label
        """

        super(OriginDirective, self).__init__('ORIGIN', origin, **kwargs)

class TTLDirective (Directive) :
    """
        Set the TTL used for records by default
    """

    def __init__ (self, ttl, **kwargs) :
        """
            @param ttl the new ttl to use
        """

        super(TTLDirective, self).__init__('TTL', ttl, **kwargs)

class IncludeDirective (Directive) :
    """
        Include another zoen file, optionally with a different origin
    """

    def __init__ (self, filename, origin=None, **kwargs) :
        """
            @param filename the zone file to include
            @param origin the optional origin to process the zonefile with
        """

        super(IncludeDirective, self).__init__('INCLUDE', filename, origin, **kwargs)

class GenerateDirective (Directive) :
    """
        Generate a bunch of numbered records using an expression for the label and rdata.

        At the simplest, any "$" in the expression is replaced with the value of the iterator.
    """

    def __init__ (self, range, lhs, type, rhs, ttl=None, cls=None, **kwargs) :
        """
            @param range (start, stop, step) tuple
            @param lhs expression to generate the label
            @param type the resource record type
            @param rhs expression to generate the rdata
        """

        super(GenerateDirective, self).__init__('GENERATE', '%d-%d' % range, lhs, ttl, cls, type, rhs, **kwargs)