pvl/config/bind/zone.py
changeset 9 2156906bfbf1
equal deleted inserted replaced
8:46d36bc33086 9:2156906bfbf1
       
     1 """
       
     2     Configuration file output for the ISC DNS server: BIND
       
     3 """
       
     4 
       
     5 from .. import file
       
     6 
       
     7 class Item (file.Item) :
       
     8     """
       
     9         Item that knows how to format comments
       
    10     """
       
    11 
       
    12     COMMENT_PREFIX = ';'
       
    13 
       
    14 class Zone (Item, file.Contents) :
       
    15     """
       
    16         A zone containing a bunch of directives, resource records and comments
       
    17     """
       
    18 
       
    19     def __init__ (self, ttl=None, comment=None) :
       
    20         """
       
    21             @param name the name of the zonefile, for status stuff
       
    22             @param path the path to the zonefile
       
    23             @param ttl default TTL to use
       
    24         """
       
    25         
       
    26         Item.__init__(self, [comment])
       
    27         file.Contents.__init__(self)
       
    28 
       
    29         if ttl :
       
    30             self.add(TTLDirective(ttl))
       
    31     
       
    32 class Comment (Item) :
       
    33     """
       
    34         A comment, is, well, a comment :)
       
    35     """
       
    36 
       
    37     def __init__ (self, comment) :
       
    38         """
       
    39             @param comment the comment string
       
    40         """
       
    41 
       
    42         Item.__init__(self, [comment])
       
    43     
       
    44     # only need to iterate over our comments
       
    45     iter_lines = Item.iter_comments
       
    46 
       
    47 class Label (object) :
       
    48     """
       
    49         A label, as used in a ResourceRecord, either as the label, or the rdata for various resource types
       
    50 
       
    51         You can also use strs, this just implements a __str__ method
       
    52     """
       
    53 
       
    54     def __init__ (self, label) :
       
    55         """
       
    56             @param label the literal label to use
       
    57         """
       
    58 
       
    59         self.label = label
       
    60 
       
    61     def __str__ (self) :
       
    62         return self.label
       
    63 
       
    64 class Origin (Label) :
       
    65     """
       
    66         A label that represents the zone's origin
       
    67     """
       
    68 
       
    69     def __init__ (self) :
       
    70         pass
       
    71 
       
    72     def __str__ (self) :
       
    73         return '@'
       
    74 
       
    75 class FQDN (Label) :
       
    76     """
       
    77         A label that represents the given external domain (i.e. this adds the . to the end that people always forget).
       
    78     """
       
    79 
       
    80     def __init__ (self, fqdn) :
       
    81         self.fqdn = fqdn
       
    82 
       
    83     def __str__ (self) :
       
    84         return "%s." % (self.fqdn, )
       
    85 
       
    86 class Interval (object) :
       
    87     """
       
    88         A time interval suitable for use in SOA records
       
    89     """
       
    90 
       
    91     def __init__ (self, s=None, m=None, h=None, d=None) :
       
    92         """
       
    93             @param s seconds
       
    94             @param m minutes
       
    95             @param h hours
       
    96             @param d days
       
    97         """
       
    98 
       
    99         self.s = s
       
   100         self.m = m
       
   101         self.h = h
       
   102         self.d = d
       
   103 
       
   104     def __str__ (self) :
       
   105         """
       
   106             If only seconds were given, just return those directly, otherwise, apply units
       
   107         """
       
   108 
       
   109         if self.s and not self.m and not self.h and not self.d :
       
   110             return str(self.s)
       
   111 
       
   112         else :
       
   113             return "%s%s%s%s" % (
       
   114                     "%ds" % self.s if self.s else '',
       
   115                     "%dm" % self.m if self.m else '',
       
   116                     "%dh" % self.h if self.h else '',
       
   117                     "%dd" % self.d if self.d else ''
       
   118                 )
       
   119 
       
   120 class ResourceRecord (Item) :
       
   121     """
       
   122         A generic resource record for a BIND zone file
       
   123     """
       
   124 
       
   125     def __init__ (self, label, type, rdata, cls='IN', ttl=None, **kwargs) :
       
   126         """
       
   127             @param label the "name" of this record, or None to referr to the previous record's label
       
   128             @param type the type as a string ('A', 'TXT', etc.)
       
   129             @param rdata the rdata, as a raw string
       
   130             @param cls the class, e.g. 'IN'
       
   131             @param ttl the time-to-live value in seconds, or None to omit it
       
   132         """
       
   133 
       
   134         super(ResourceRecord, self).__init__(**kwargs)
       
   135 
       
   136         self.label = label
       
   137         self.type = type
       
   138         self.rdata = rdata
       
   139         self.cls = cls
       
   140         self.ttl = ttl
       
   141 
       
   142     def iter_lines (self) :
       
   143         """
       
   144             Just format the lines, eh
       
   145         """
       
   146         
       
   147         # prefix comments
       
   148         for line in self.iter_comments() :
       
   149             yield line
       
   150 
       
   151         # then format the line
       
   152         # XXX: TTL?
       
   153         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)
       
   154 
       
   155 class SOA (ResourceRecord) :
       
   156     """
       
   157         "Identifies the start of a zone of authority", must be the first record
       
   158     """
       
   159 
       
   160     def __init__ (self, label, primary_ns, hostmaster, serial, refresh, retry, expire, minimum, **kwargs) :
       
   161         """
       
   162             @param label the "name" of the zone, usually ORIGIN
       
   163             @param primary_ns the address of the primary NS server
       
   164             @param hostmaster the mailbox of the zone's hostmaster
       
   165             @param serial the serial number of the zone as an integer
       
   166             @param refresh time interval between zone refreshes in seconds
       
   167             @param retry time interval between retrys for failed refreshes
       
   168             @param expire time interval before zone data can no longer be considered authorative
       
   169             @param minimum minimum TTL for RRs in this zone
       
   170         """
       
   171 
       
   172         super(SOA, self).__init__(label, 'SOA', "%s %s ( %s %s %s %s %s )" % (
       
   173                 primary_ns, hostmaster, serial, refresh, retry, expire, minimum
       
   174             ), **kwargs)
       
   175 
       
   176 class A (ResourceRecord) :
       
   177     """
       
   178         An IPv4 forward address
       
   179     """
       
   180 
       
   181     def __init__ (self, label, addr, **kwargs) :
       
   182         """
       
   183             @param label the "name" of the address
       
   184             @param addr the IPv4 target address
       
   185         """
       
   186 
       
   187         assert(addr.is_v4())
       
   188 
       
   189         super(A, self).__init__(label, 'A', addr, **kwargs)
       
   190 
       
   191 class AAAA (ResourceRecord) :
       
   192     """
       
   193         An IPv6 forward address
       
   194     """
       
   195 
       
   196     def __init__ (self, label, addr, **kwargs) :
       
   197         """
       
   198             @param label the "name" of the address
       
   199             @param addr the IPv6 target address
       
   200         """
       
   201         
       
   202         assert(addr.is_v6())
       
   203 
       
   204         super(AAAA, self).__init__(label, 'AAAA', addr.strCompressed(), **kwargs)
       
   205 
       
   206 class CNAME (ResourceRecord) :
       
   207     """
       
   208         A canonical-name alias
       
   209     """
       
   210 
       
   211     def __init__ (self, label, target, **kwargs) :
       
   212         """
       
   213             @param label the "name" of the alias
       
   214             @param target the alias target
       
   215         """
       
   216 
       
   217         super(CNAME, self).__init__(label, 'CNAME', target, **kwargs)
       
   218 
       
   219 class TXT (ResourceRecord) :
       
   220     """
       
   221         A human-readable information record
       
   222     """
       
   223 
       
   224     def __init__ (self, label, text, **kwargs) :
       
   225         """
       
   226             @param label the "name" of the text record
       
   227             @param text the text data, shouldn't contain any quotes...
       
   228         """
       
   229         
       
   230         # XXX: escaping?
       
   231         super(TXT, self).__init__(label, 'TXT', '"%s"' % text, **kwargs)
       
   232 
       
   233 class MX (ResourceRecord) :
       
   234     """
       
   235         A mail-exchange definition
       
   236     """
       
   237 
       
   238     def __init__ (self, label, pref, exchange, **kwargs) :
       
   239         """
       
   240             @param label the "name" of the domain to handle mail for
       
   241             @param pref the numerical preference for this exchange
       
   242             @param exchange the domain name of the mail exchange (SMTP server)
       
   243         """
       
   244 
       
   245         super(MX, self).__init__(label, 'MX', "%d %s" % (pref, exchange), **kwargs)
       
   246 
       
   247 class NS (ResourceRecord) :
       
   248     """
       
   249         An authorative name server
       
   250     """
       
   251 
       
   252     def __init__ (self, label, nsname, **kwargs) :
       
   253         """
       
   254             @param label the "name" of the domain to have a nameserver for
       
   255             @param nsname the name of the nameserver
       
   256         """
       
   257 
       
   258         super(NS, self).__init__(label, 'NS', nsname)
       
   259 
       
   260 class PTR (ResourceRecord) :
       
   261     """
       
   262         An IPv4/IPv6 reverse address
       
   263     """
       
   264 
       
   265     def __init__ (self, addr, name, **kwargs) :
       
   266         """
       
   267             @param addr the addr.IP to map via in-addr.arpa
       
   268             @param name the name to map the address to
       
   269         """
       
   270 
       
   271         # XXX: quick hack, this gives an absolute name
       
   272         label = addr.reverseName()
       
   273         
       
   274         super(PTR, self).__init__(label, 'PTR', name)
       
   275 
       
   276 class Directive (Item) :
       
   277     """
       
   278         Special directives that can be used in zone files to control behaviour
       
   279     """
       
   280 
       
   281     def __init__ (self, name, *args, **kwargs) :
       
   282         """
       
   283             @param name the $NAME bit
       
   284             @param args optional list of space-seprated arguments, Nones are ignored
       
   285         """
       
   286 
       
   287         super(Directive, self).__init__(**kwargs)
       
   288 
       
   289         self.name = name
       
   290         self.args = [arg for arg in args if arg is not None]
       
   291 
       
   292     def iter_lines (self) :
       
   293         # prefix comments
       
   294         for line in self.iter_comments() :
       
   295             yield line
       
   296 
       
   297         # then format it
       
   298         yield "$%s%s" % (self.name, (' ' + ' '.join(str(arg) for arg in self.args)) if self.args else '')
       
   299 
       
   300 class OriginDirective (Directive) :
       
   301     """
       
   302         Set the origin used to resolve the zone's labels.
       
   303 
       
   304         Note that the origin label is not absolute by default - use FQDN for that
       
   305     """
       
   306 
       
   307     def __init__ (self, origin, **kwargs) :
       
   308         """
       
   309             @param origin the origin label
       
   310         """
       
   311 
       
   312         super(OriginDirective, self).__init__('ORIGIN', origin, **kwargs)
       
   313 
       
   314 class TTLDirective (Directive) :
       
   315     """
       
   316         Set the TTL used for records by default
       
   317     """
       
   318 
       
   319     def __init__ (self, ttl, **kwargs) :
       
   320         """
       
   321             @param ttl the new ttl to use
       
   322         """
       
   323 
       
   324         super(TTLDirective, self).__init__('TTL', ttl, **kwargs)
       
   325 
       
   326 class IncludeDirective (Directive) :
       
   327     """
       
   328         Include another zoen file, optionally with a different origin
       
   329     """
       
   330 
       
   331     def __init__ (self, filename, origin=None, **kwargs) :
       
   332         """
       
   333             @param filename the zone file to include
       
   334             @param origin the optional origin to process the zonefile with
       
   335         """
       
   336 
       
   337         super(IncludeDirective, self).__init__('INCLUDE', filename, origin, **kwargs)
       
   338 
       
   339 class GenerateDirective (Directive) :
       
   340     """
       
   341         Generate a bunch of numbered records using an expression for the label and rdata.
       
   342 
       
   343         At the simplest, any "$" in the expression is replaced with the value of the iterator.
       
   344     """
       
   345 
       
   346     def __init__ (self, range, lhs, type, rhs, ttl=None, cls=None, **kwargs) :
       
   347         """
       
   348             @param range (start, stop, step) tuple
       
   349             @param lhs expression to generate the label
       
   350             @param type the resource record type
       
   351             @param rhs expression to generate the rdata
       
   352         """
       
   353 
       
   354         super(GenerateDirective, self).__init__('GENERATE', '%d-%d' % range, lhs, ttl, cls, type, rhs, **kwargs)
       
   355