]> git.llucax.com Git - software/pymin.git/blob - pymin/services/dns/__init__.py
32e177221607975ac820a0ad4774cb0a2f96ef3b
[software/pymin.git] / pymin / services / dns / __init__.py
1 # vim: set encoding=utf-8 et sw=4 sts=4 :
2
3 # TODO COMMENT
4 from os import path
5 from os import unlink
6 from new import instancemethod
7
8 from pymin.seqtools import Sequence
9 from pymin.dispatcher import handler, HandlerError, Handler
10 from pymin.services.util import Restorable, ConfigWriter, InitdHandler, \
11                                 TransactionalHandler, ParametersHandler, \
12                                 SubHandler, call
13
14 __ALL__ = ('DnsHandler', 'Error',
15             'ZoneError', 'ZoneNotFoundError', 'ZoneAlreadyExistsError',
16             'HostError', 'HostAlreadyExistsError', 'HostNotFoundError',
17             'MailExchangeError', 'MailExchangeAlreadyExistsError',
18             'MailExchangeNotFoundError', 'NameServerError',
19             'NameServerAlreadyExistsError', 'NameServerNotFoundError')
20
21 template_dir = path.join(path.dirname(__file__), 'templates')
22
23
24 class Error(HandlerError):
25     r"""
26     Error(command) -> Error instance :: Base DnsHandler exception class.
27
28     All exceptions raised by the DnsHandler inherits from this one, so you can
29     easily catch any DnsHandler exception.
30
31     message - A descriptive error message.
32     """
33     pass
34
35 class ZoneError(Error, KeyError):
36     r"""
37     ZoneError(zonename) -> ZoneError instance
38
39     This is the base exception for all zone related errors.
40     """
41
42     def __init__(self, zonename):
43         r"Initialize the object. See class documentation for more info."
44         self.message = u'Zone error: "%s"' % zonename
45
46 class ZoneNotFoundError(ZoneError):
47     r"""
48     ZoneNotFoundError(hostname) -> ZoneNotFoundError instance
49
50     This exception is raised when trying to operate on a zone that doesn't
51     exists.
52     """
53
54     def __init__(self, zonename):
55         r"Initialize the object. See class documentation for more info."
56         self.message = u'zone not found: "%s"' % zonename
57
58 class ZoneAlreadyExistsError(ZoneError):
59     r"""
60     ZoneAlreadyExistsError(hostname) -> ZoneAlreadyExistsError instance
61
62     This exception is raised when trying to add a zonename that already exists.
63     """
64
65     def __init__(self, zonename):
66         r"Initialize the object. See class documentation for more info."
67         self.message = u'Zone already exists: "%s"' % zonename
68
69 class HostError(Error, KeyError):
70     r"""
71     HostError(hostname) -> HostError instance
72
73     This is the base exception for all host related errors.
74     """
75
76     def __init__(self, hostname):
77         r"Initialize the object. See class documentation for more info."
78         self.message = u'Host error: "%s"' % hostname
79
80 class HostAlreadyExistsError(HostError):
81     r"""
82     HostAlreadyExistsError(hostname) -> HostAlreadyExistsError instance
83
84     This exception is raised when trying to add a hostname that already exists.
85     """
86
87     def __init__(self, hostname):
88         r"Initialize the object. See class documentation for more info."
89         self.message = u'Host already exists: "%s"' % hostname
90
91 class HostNotFoundError(HostError):
92     r"""
93     HostNotFoundError(hostname) -> HostNotFoundError instance.
94
95     This exception is raised when trying to operate on a hostname that doesn't
96     exists.
97     """
98
99     def __init__(self, hostname):
100         r"Initialize the object. See class documentation for more info."
101         self.message = u'Host not found: "%s"' % hostname
102
103 class MailExchangeError(Error, KeyError):
104     r"""
105     MailExchangeError(hostname) -> MailExchangeError instance.
106
107     This is the base exception for all mail exchange related errors.
108     """
109
110     def __init__(self, mx):
111         r"Initialize the object. See class documentation for more info."
112         self.message = u'Mail Exchange error: "%s"' % mx
113
114 class MailExchangeAlreadyExistsError(MailExchangeError):
115     r"""
116     MailExchangeAlreadyExistsError(hostname) -> MailExchangeAlreadyExistsError.
117
118     This exception is raised when trying to add a mail exchange that already exists.
119     """
120
121     def __init__(self, mx):
122         r"Initialize the object. See class documentation for more info."
123         self.message = u'Mail Exchange already exists: "%s"' % mx
124
125 class MailExchangeNotFoundError(MailExchangeError):
126     r"""
127     MailExchangeNotFoundError(hostname) -> MailExchangeNotFoundError instance.
128
129     This exception is raised when trying to operate on a mail exchange that
130     doesn't exists.
131     """
132
133     def __init__(self, mx):
134         r"Initialize the object. See class documentation for more info."
135         self.message = 'Mail Exchange not found: "%s"' % mx
136
137 class NameServerError(Error, KeyError):
138     r"""
139     NameServerError(ns) -> NameServerError instance
140
141     This is the base exception for all name server related errors.
142     """
143
144     def __init__(self, ns):
145         r"Initialize the object. See class documentation for more info."
146         self.message = 'Name Server error: "%s"' % ns
147
148 class NameServerAlreadyExistsError(NameServerError):
149     r"""
150     NameServerAlreadyExistsError(hostname) -> NameServerAlreadyExistsError.
151
152     This exception is raised when trying to add a name server that already
153     exists.
154     """
155
156     def __init__(self, ns):
157         r"Initialize the object. See class documentation for more info."
158         self.message = 'Name server already exists: "%s"' % ns
159
160 class NameServerNotFoundError(NameServerError):
161     r"""
162     NameServerNotFoundError(hostname) -> NameServerNotFoundError instance.
163
164     This exception is raised when trying to operate on a name server that
165     doesn't exists.
166     """
167
168     def __init__(self, ns):
169         r"Initialize the object. See class documentation for more info."
170         self.message = 'Mail Exchange not found: "%s"' % ns
171
172
173 class Host(Sequence):
174     def __init__(self, name, ip):
175         self.name = name
176         self.ip = ip
177
178     def as_tuple(self):
179         return (self.name, self.ip)
180
181 class HostHandler(SubHandler):
182
183     handler_help = u"Manage DNS hosts"
184
185     @handler(u'Adds a host to a zone')
186     def add(self, name, hostname, ip):
187         if not name in self.parent.zones:
188             raise ZoneNotFoundError(name)
189         if hostname in self.parent.zones[name].hosts:
190             raise HostAlreadyExistsError(hostname)
191         self.parent.zones[name].hosts[hostname] = Host(hostname, ip)
192         self.parent.zones[name].mod = True
193
194     @handler(u'Updates a host ip in a zone')
195     def update(self, name, hostname, ip):
196         if not name in self.parent.zones:
197             raise ZoneNotFoundError(name)
198         if not hostname in self.parent.zones[name].hosts:
199              raise HostNotFoundError(name)
200         self.parent.zones[name].hosts[hostname].ip = ip
201         self.parent.zones[name].mod = True
202
203     @handler(u'Deletes a host from a zone')
204     def delete(self, name, hostname):
205         if not name in self.parent.zones:
206             raise ZoneNotFoundError(name)
207         if not hostname in self.parent.zones[name].hosts:
208              raise HostNotFoundError(name)
209         del self.parent.zones[name].hosts[hostname]
210         self.parent.zones[name].mod = True
211
212     @handler(u'Lists hosts')
213     def list(self):
214         return self.parent.zones.keys()
215
216     @handler(u'Get insormation about all hosts')
217     def show(self):
218         return self.parent.zones.values()
219
220
221 class MailExchange(Sequence):
222
223     def __init__(self, mx, prio):
224         self.mx = mx
225         self.prio = prio
226
227     def as_tuple(self):
228         return (self.mx, self.prio)
229
230 class MailExchangeHandler(SubHandler):
231
232     handler_help = u"Manage DNS mail exchangers (MX)"
233
234     @handler(u'Adds a mail exchange to a zone')
235     def add(self, zonename, mx, prio):
236         if not zonename in self.parent.zones:
237             raise ZoneNotFoundError(zonename)
238         if mx in self.parent.zones[zonename].mxs:
239             raise MailExchangeAlreadyExistsError(mx)
240         self.parent.zones[zonename].mxs[mx] = MailExchange(mx, prio)
241         self.parent.zones[zonename].mod = True
242
243     @handler(u'Updates a mail exchange priority')
244     def update(self, zonename, mx, prio):
245         if not zonename in self.parent.zones:
246             raise ZoneNotFoundError(zonename)
247         if not mx in self.parent.zones[zonename].mxs:
248             raise MailExchangeNotFoundError(mx)
249         self.parent.zones[zonename].mxs[mx].prio = prio
250         self.parent.zones[zonename].mod = True
251
252     @handler(u'Deletes a mail exchange from a zone')
253     def delete(self, zonename, mx):
254         if not zonename in self.parent.zones:
255             raise ZoneNotFoundError(zonename)
256         if not mx in self.parent.zones[zonename].mxs:
257             raise MailExchangeNotFoundError(mx)
258         del self.parent.zones[zonename].mxs[mx]
259         self.parent.zones[zonename].mod = True
260
261     @handler(u'Lists mail exchangers')
262     def list(self):
263         return self.parent.zones.keys()
264
265     @handler(u'Get information about all mail exchangers')
266     def show(self):
267         return self.parent.zones.values()
268
269
270 class NameServer(Sequence):
271
272     def __init__(self, name):
273         self.name = name
274
275     def as_tuple(self):
276         return (self.name)
277
278 class NameServerHandler(SubHandler):
279
280     handler_help = u"Manage DNS name servers (NS)"
281
282     @handler(u'Adds a name server to a zone')
283     def add(self, zone, ns):
284         if not zone in self.parent.zones:
285             raise ZoneNotFoundError(zone)
286         if ns in self.parent.zones[zone].nss:
287             raise NameServerAlreadyExistsError(ns)
288         self.parent.zones[zone].nss[ns] = NameServer(ns)
289         self.parent.zones[zone].mod = True
290
291     @handler(u'Deletes a name server from a zone')
292     def delete(self, zone, ns):
293         if not zone in self.parent.zones:
294             raise ZoneNotFoundError(zone)
295         if not ns in self.parent.zones[zone].nss:
296             raise NameServerNotFoundError(ns)
297         del self.parent.zones[zone].nss[ns]
298         self.parent.zones[zone].mod = True
299
300     @handler(u'Lists name servers')
301     def list(self):
302         return self.parent.zones.keys()
303
304     @handler(u'Get information about all name servers')
305     def show(self):
306         return self.parent.zones.values()
307
308
309 class Zone(Sequence):
310     def __init__(self, name):
311         self.name = name
312         self.hosts = dict()
313         self.mxs = dict()
314         self.nss = dict()
315         self.new = False
316         self.mod = False
317         self.dele = False
318
319     def as_tuple(self):
320         return (self.name, self.hosts, self.mxs, self.nss)
321
322 class ZoneHandler(SubHandler):
323     r"""ZoneHandler(parent.zones) -> ZoneHandler instance :: Handle a list of zones.
324
325     This class is a helper for DnsHandler to do all the work related to zone
326     administration.
327
328     parent - The parent service handler.
329     """
330
331     handler_help = u"Manage DNS zones"
332
333     @handler(u'Adds a zone')
334     def add(self, name):
335         if name in self.parent.zones:
336             if self.parent.zones[name].dele == True:
337                 self.parent.zones[name].dele = False
338             else:
339                 raise ZoneAlreadyExistsError(name)
340         self.parent.zones[name] = Zone(name)
341         self.parent.zones[name].mod = True
342         self.parent.zones[name].new = True
343
344     @handler(u'Deletes a zone')
345     def delete(self, name):
346         r"delete(name) -> None :: Delete a zone from the zone list."
347         if not name in self.parent.zones:
348             raise ZoneNotFoundError(name)
349         self.parent.zones[name].dele = True
350
351     @handler(u'Lists zones')
352     def list(self):
353         return self.parent.zones.keys()
354
355     @handler(u'Get information about all zones')
356     def show(self):
357         return self.parent.zones.values()
358
359
360 class DnsHandler(Restorable, ConfigWriter, InitdHandler, TransactionalHandler,
361                  ParametersHandler):
362     r"""DnsHandler([pickle_dir[, config_dir]]) -> DnsHandler instance.
363
364     Handles DNS service commands for the dns program.
365
366     pickle_dir - Directory where to write the persistent configuration data.
367
368     config_dir - Directory where to store de generated configuration files.
369
370     Both defaults to the current working directory.
371     """
372
373     handler_help = u"Manage DNS service"
374
375     _initd_name = 'named'
376
377     _persistent_attrs = ('params', 'zones')
378
379     _restorable_defaults = dict(
380             zones = dict(),
381             params  = dict(
382                 isp_dns1 = '',
383                 isp_dns2 = '',
384                 bind_addr1 = '',
385                 bind_addr2 = ''
386             ),
387     )
388
389     _config_writer_files = ('named.conf', 'zoneX.zone')
390     _config_writer_tpl_dir = path.join(path.dirname(__file__), 'templates')
391
392     def __init__(self, pickle_dir='.', config_dir={'named.conf':'.', 'zoneX.zone':'.'}):
393         r"Initialize DnsHandler object, see class documentation for details."
394         self._persistent_dir = pickle_dir
395         self._config_writer_cfg_dir = config_dir
396         self.mod = False
397         self._config_build_templates()
398         self._restore()
399         # FIXME self.mod = True
400         #if not self._restore():
401         #r = self._restore()
402         #print r
403         #if not r:
404         #    self.mod = True
405         self.host = HostHandler(self)
406         self.zone = ZoneHandler(self)
407         self.mx = MailExchangeHandler(self)
408         self.ns = NameServerHandler(self)
409
410     def _zone_filename(self, zone):
411         return zone.name + '.zone'
412
413     def _get_config_vars(self, config_file):
414         return dict(zones=self.zones.values(), **self.params)
415
416     def _write_config(self):
417         r"_write_config() -> None :: Generate all the configuration files."
418         delete_zones = list()
419         for a_zone in self.zones.values():
420             if a_zone.mod:
421                 if not a_zone.new:
422                     # TODO freeze de la zona
423                     call(('rndc', 'freeze', a_zone.name))
424                 vars = dict(
425                     zone = a_zone,
426                     hosts = a_zone.hosts.values(),
427                     mxs = a_zone.mxs.values(),
428                     nss = a_zone.nss.values()
429                 )
430                 self._write_single_config('zoneX.zone',
431                                             self._zone_filename(a_zone), vars)
432                 a_zone.mod = False
433                 if not a_zone.new:
434                     # TODO unfreeze de la zona
435                     call(('rndc', 'thaw', a_zone.name))
436                 else :
437                     self.mod = True
438                     a_zone.new = False
439             if a_zone.dele:
440                 #borro el archivo .zone
441                 try:
442                     self.mod = True
443                     unlink(self._zone_filename(a_zone))
444                 except OSError:
445                     #la excepcion pude darse en caso que haga un add de una zona y
446                     #luego el del, como no hice commit, no se crea el archivo
447                     pass
448                 delete_zones.append(a_zone.name)
449         #borro las zonas
450         for z in delete_zones:
451             del self.zones[z]
452         #archivo general
453         if self.mod:
454             self._write_single_config('named.conf')
455             self.mod = False
456             self.reload()
457
458
459 if __name__ == '__main__':
460
461     dns = DnsHandler();
462
463     dns.set('isp_dns1','la_garcha.com')
464     dns.set('bind_addr1','localhost')
465     dns.zone.add('zona_loca.com')
466     #dns.zone.update('zona_loca.com','ns1.dominio.com')
467
468     dns.host.add('zona_loca.com','hostname_loco','192.168.0.23')
469     dns.host.update('zona_loca.com','hostname_loco','192.168.0.66')
470
471     dns.host.add('zona_loca.com','hostname_kuak','192.168.0.23')
472     dns.host.delete('zona_loca.com','hostname_kuak')
473
474     dns.host.add('zona_loca.com','hostname_kuang','192.168.0.24')
475     dns.host.add('zona_loca.com','hostname_chan','192.168.0.25')
476     dns.host.add('zona_loca.com','hostname_kaine','192.168.0.26')
477
478     dns.mx.add('zona_loca.com','mx1.sarasa.com',10)
479     dns.mx.update('zona_loca.com','mx1.sarasa.com',20)
480     dns.mx.add('zona_loca.com','mx2.sarasa.com',30)
481     dns.mx.add('zona_loca.com','mx3.sarasa.com',40)
482     dns.mx.delete('zona_loca.com','mx3.sarasa.com')
483
484     dns.ns.add('zona_loca.com','ns1.jua.com')
485     dns.ns.add('zona_loca.com','ns2.jua.com')
486     dns.ns.add('zona_loca.com','ns3.jua.com')
487     dns.ns.delete('zona_loca.com','ns3.jua.com')
488
489     dns.zone.add('zona_oscura')
490
491     dns.host.add('zona_oscura','hostname_a','192.168.0.24')
492     dns.host.add('zona_oscura','hostname_b','192.168.0.25')
493     dns.host.add('zona_oscura','hostname_c','192.168.0.26')
494
495     dns.zone.delete('zona_oscura')
496
497     dns.commit()
498
499     print 'ZONAS :', dns.zone.show()
500     print 'HOSTS :', dns.host.show()
501
502     #test zone errors
503     #try:
504     #    dns.zone.update('zone-sarasa','lalal')
505     #except ZoneNotFoundError, inst:
506     #    print 'Error: ', inst
507
508     try:
509         dns.zone.delete('zone-sarasa')
510     except ZoneNotFoundError, inst:
511         print 'Error: ', inst
512
513     #try:
514     #    dns.zone.add('zona_loca.com','ns1.dom.com','ns2.dom.com')
515     #except ZoneAlreadyExistsError, inst:
516     #    print 'Error: ', inst
517
518     #test hosts errors
519     try:
520         dns.host.update('zone-sarasa','kuak','192.68')
521     except ZoneNotFoundError, inst:
522         print 'Error: ', inst
523
524     try:
525         dns.host.update('zona_loca.com','kuak','192.68')
526     except HostNotFoundError, inst:
527         print 'Error: ', inst
528
529     try:
530         dns.host.delete('zone-sarasa','lala')
531     except ZoneNotFoundError, inst:
532         print 'Error: ', inst
533
534     try:
535         dns.host.delete('zona_loca.com','lala')
536     except HostNotFoundError, inst:
537         print 'Error: ', inst
538
539     try:
540         dns.host.add('zona','hostname_loco','192.168.0.23')
541     except ZoneNotFoundError, inst:
542         print 'Error: ', inst
543
544     try:
545         dns.host.add('zona_loca.com','hostname_loco','192.168.0.23')
546     except HostAlreadyExistsError, inst:
547         print 'Error: ', inst