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