1 """ |
|
2 High-level BIND stuff |
|
3 """ |
|
4 |
|
5 from __future__ import with_statement |
|
6 |
|
7 import bind_conf as bindc |
|
8 |
|
9 import os.path, datetime |
|
10 |
|
11 DEFAULT_TTL = bindc.Interval(3600) |
|
12 |
|
13 class Settings (object) : |
|
14 """ |
|
15 A set of basic settings for a zone, mostly default TTL/refresh/retry/expire/minimum settings |
|
16 """ |
|
17 |
|
18 def __init__ (self, ttl, hostmaster, refresh, retry, expire, minimum) : |
|
19 self.ttl = ttl |
|
20 self.hostmaster = hostmaster |
|
21 self.refresh = refresh |
|
22 self.retry = retry |
|
23 self.expire = expire |
|
24 self.minimum = minimum |
|
25 |
|
26 class AutoSerial (object) : |
|
27 """ |
|
28 Automatically generate the next serial to use by loading it from a file. |
|
29 |
|
30 The generated serials are in YYYYMMDDXX format. |
|
31 """ |
|
32 |
|
33 def __init__ (self, path) : |
|
34 """ |
|
35 Load the current serial |
|
36 |
|
37 @param path the path to the serial file |
|
38 """ |
|
39 |
|
40 # store |
|
41 self.path = path |
|
42 |
|
43 # load it |
|
44 # XXX: locking |
|
45 serial = self.read() |
|
46 |
|
47 # current date |
|
48 today = datetime.date.today() |
|
49 |
|
50 # parse it |
|
51 if serial : |
|
52 date, code = self.parse(serial) |
|
53 |
|
54 else : |
|
55 date, code = today, 0 |
|
56 |
|
57 # increment it |
|
58 date, code = self.next(date, code) |
|
59 |
|
60 # format it |
|
61 self._serial = self.build(date, code) |
|
62 |
|
63 # write it out |
|
64 self.write(self._serial) |
|
65 |
|
66 def parse (self, serial) : |
|
67 """ |
|
68 Parse the given serial into a (datetime.date, code) format |
|
69 """ |
|
70 |
|
71 # build it into a date |
|
72 date = datetime.date( |
|
73 year = int(serial[0:4]), |
|
74 month = int(serial[4:6]), |
|
75 day = int(serial[6:8]) |
|
76 ) |
|
77 |
|
78 code = int(serial[8:]) |
|
79 |
|
80 return date, code |
|
81 |
|
82 def next (self, date, code) : |
|
83 """ |
|
84 Return the next valid serial following the given one |
|
85 """ |
|
86 |
|
87 # current date |
|
88 today = datetime.date.today() |
|
89 |
|
90 # now to increment? |
|
91 if date < today : |
|
92 # jump to today's first serial |
|
93 date = today |
|
94 code = 0 |
|
95 |
|
96 else : |
|
97 # today or overflowed into the future, just increment the code |
|
98 code += 1 |
|
99 |
|
100 # code overflowed into next day? |
|
101 if code > 99 : |
|
102 date += datetime.timedelta(days=1) |
|
103 code = 0 |
|
104 |
|
105 # ok |
|
106 return date, code |
|
107 |
|
108 def build (self, date, code) : |
|
109 """ |
|
110 Build a serial code the given date/code |
|
111 """ |
|
112 |
|
113 assert 0 <= code <= 99 |
|
114 |
|
115 return "%s%02d" % (date.strftime("%Y%m%d"), code) |
|
116 |
|
117 def read (self) : |
|
118 """ |
|
119 Read the current serial, returning it, or None, if not found... |
|
120 """ |
|
121 |
|
122 # if it doesn't exist, default |
|
123 if not os.path.exists(self.path) : |
|
124 return None |
|
125 |
|
126 # read it |
|
127 with open(self.path) as fh : |
|
128 return fh.read().strip() |
|
129 |
|
130 def write (self, serial) : |
|
131 """ |
|
132 Write a new serial |
|
133 """ |
|
134 |
|
135 with open(self.path, 'w') as fh : |
|
136 fh.write("%s\n" % (serial, )) |
|
137 |
|
138 def serial (self) : |
|
139 """ |
|
140 Return a new, unused serial code (before __init__) |
|
141 """ |
|
142 |
|
143 return self._serial |
|
144 |
|
145 class Domain (bindc.ZoneFile) : |
|
146 """ |
|
147 A domain has a skeleton of stuff defined, but the rest is $INCLUDE'd from elsewhere, which is useful for |
|
148 multi-domain setups where the domains are mostly similar |
|
149 """ |
|
150 |
|
151 def __init__ (self, domain, path, nameservers, mailservers, serial, settings, include=None, objs=None) : |
|
152 """ |
|
153 @param domain the domain name |
|
154 @param path the path to the zone file |
|
155 @param nameservers list of nameservers as labels |
|
156 @param mailservers list of (pref, label) tuples for MX records |
|
157 @param serial the serial code to use |
|
158 @param settings the TTL/SOA settings to use |
|
159 @param include the optional zonefile to include |
|
160 @param objs the optional other objects to add to the zonefile |
|
161 """ |
|
162 |
|
163 super(Domain, self).__init__(domain, path) |
|
164 |
|
165 # the default TTL |
|
166 self.add_directive(bindc.TTLDirective(settings.ttl)) |
|
167 |
|
168 # the SOA record |
|
169 self.add_record(bindc.SOA(None, nameservers[0], |
|
170 settings.hostmaster, serial, settings.refresh, settings.retry, settings.expire, settings.minimum |
|
171 )) |
|
172 |
|
173 # the NS records |
|
174 for label in nameservers : |
|
175 self.add_record(bindc.NS(None, label)) |
|
176 |
|
177 # the MX records |
|
178 for pref, label in mailservers : |
|
179 self.add_record(bindc.MX(None, pref, label)) |
|
180 |
|
181 # include? |
|
182 if include : |
|
183 self.add_directive(bindc.IncludeDirective(include)) |
|
184 |
|
185 if objs : |
|
186 for obj in objs : |
|
187 self.add_obj(obj) |
|