]> git.llucax.com Git - software/pymin.git/blob - pymin/services/dns/__init__.py
Create a package named pymin with all the pymin modules.
[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
33     def __init__(self, message):
34         r"Initialize the Error object. See class documentation for more info."
35         self.message = message
36
37     def __str__(self):
38         return self.message
39
40 class ZoneError(Error, KeyError):
41     r"""
42     ZoneError(zonename) -> ZoneError instance
43
44     This is the base exception for all zone related errors.
45     """
46
47     def __init__(self, zonename):
48         r"Initialize the object. See class documentation for more info."
49         self.message = 'Zone error: "%s"' % zonename
50
51 class ZoneNotFoundError(ZoneError):
52     r"""
53     ZoneNotFoundError(hostname) -> ZoneNotFoundError instance
54
55     This exception is raised when trying to operate on a zone that doesn't
56     exists.
57     """
58
59     def __init__(self, zonename):
60         r"Initialize the object. See class documentation for more info."
61         self.message = 'zone not found: "%s"' % zonename
62
63 class ZoneAlreadyExistsError(ZoneError):
64     r"""
65     ZoneAlreadyExistsError(hostname) -> ZoneAlreadyExistsError instance
66
67     This exception is raised when trying to add a zonename that already exists.
68     """
69
70     def __init__(self, zonename):
71         r"Initialize the object. See class documentation for more info."
72         self.message = 'Zone already exists: "%s"' % zonename
73
74 class HostError(Error, KeyError):
75     r"""
76     HostError(hostname) -> HostError instance
77
78     This is the base exception for all host related errors.
79     """
80
81     def __init__(self, hostname):
82         r"Initialize the object. See class documentation for more info."
83         self.message = 'Host error: "%s"' % hostname
84
85 class HostAlreadyExistsError(HostError):
86     r"""
87     HostAlreadyExistsError(hostname) -> HostAlreadyExistsError instance
88
89     This exception is raised when trying to add a hostname that already exists.
90     """
91
92     def __init__(self, hostname):
93         r"Initialize the object. See class documentation for more info."
94         self.message = 'Host already exists: "%s"' % hostname
95
96 class HostNotFoundError(HostError):
97     r"""
98     HostNotFoundError(hostname) -> HostNotFoundError instance
99
100     This exception is raised when trying to operate on a hostname that doesn't
101     exists.
102     """
103
104     def __init__(self, hostname):
105         r"Initialize the object. See class documentation for more info."
106         self.message = 'Host not found: "%s"' % hostname
107
108 class MailExchangeError(Error, KeyError):
109     r"""
110     MailExchangeError(hostname) -> MailExchangeError instance
111
112     This is the base exception for all mail exchange related errors.
113     """
114
115     def __init__(self, mx):
116         r"Initialize the object. See class documentation for more info."
117         self.message = 'Mail Exchange error: "%s"' % mx
118
119 class MailExchangeAlreadyExistsError(MailExchangeError):
120     r"""
121     MailExchangeAlreadyExistsError(hostname) -> MailExchangeAlreadyExistsError instance
122
123     This exception is raised when trying to add a mail exchange that already exists.
124     """
125
126     def __init__(self, mx):
127         r"Initialize the object. See class documentation for more info."
128         self.message = 'Mail Exchange already exists: "%s"' % mx
129
130 class MailExchangeNotFoundError(MailExchangeError):
131     r"""
132     MailExchangeNotFoundError(hostname) -> MailExchangeNotFoundError instance
133
134     This exception is raised when trying to operate on a mail exchange that doesn't
135     exists.
136     """
137
138     def __init__(self, mx):
139         r"Initialize the object. See class documentation for more info."
140         self.message = 'Mail Exchange not found: "%s"' % mx
141
142 class NameServerError(Error, KeyError):
143     r"""
144     NameServerError(ns) -> NameServerError instance
145
146     This is the base exception for all name server related errors.
147     """
148
149     def __init__(self, ns):
150         r"Initialize the object. See class documentation for more info."
151         self.message = 'Name Server error: "%s"' % ns
152
153 class NameServerAlreadyExistsError(NameServerError):
154     r"""
155     NameServerAlreadyExistsError(hostname) -> NameServerAlreadyExistsError instance
156
157     This exception is raised when trying to add a name server that already exists.
158     """
159
160     def __init__(self, ns):
161         r"Initialize the object. See class documentation for more info."
162         self.message = 'Name server already exists: "%s"' % ns
163
164 class NameServerNotFoundError(NameServerError):
165     r"""
166     NameServerNotFoundError(hostname) -> NameServerNotFoundError instance
167
168     This exception is raised when trying to operate on a name server that doesn't
169     exists.
170     """
171
172     def __init__(self, ns):
173         r"Initialize the object. See class documentation for more info."
174         self.message = 'Mail Exchange not found: "%s"' % ns
175
176
177 class Host(Sequence):
178     def __init__(self, name, ip):
179         self.name = name
180         self.ip = ip
181
182     def as_tuple(self):
183         return (self.name, self.ip)
184
185 class HostHandler(Handler):
186     def __init__(self,zones):
187         self.zones = zones
188
189     @handler(u'Adds a host to a zone')
190     def add(self, name, hostname, ip):
191         if not name in self.zones:
192             raise ZoneNotFoundError(name)
193         if hostname in self.zones[name].hosts:
194             raise HostAlreadyExistsError(hostname)
195         self.zones[name].hosts[hostname] = Host(hostname, ip)
196         self.zones[name].mod = True
197
198     @handler(u'Updates a host ip in a zone')
199     def update(self, name, hostname, ip):
200         if not name in self.zones:
201             raise ZoneNotFoundError(name)
202         if not hostname in self.zones[name].hosts:
203              raise HostNotFoundError(name)
204         self.zones[name].hosts[hostname].ip = ip
205         self.zones[name].mod = True
206
207     @handler(u'Deletes a host from a zone')
208     def delete(self, name, hostname):
209         if not name in self.zones:
210             raise ZoneNotFoundError(name)
211         if not hostname in self.zones[name].hosts:
212              raise HostNotFoundError(name)
213         del self.zones[name].hosts[hostname]
214         self.zones[name].mod = True
215
216     @handler(u'Lists hosts')
217     def list(self):
218         return self.zones.keys()
219
220     @handler(u'Get insormation about all hosts')
221     def show(self):
222         return self.zones.values()
223
224
225 class MailExchange(Sequence):
226
227     def __init__(self, mx, prio):
228         self.mx = mx
229         self.prio = prio
230
231     def as_tuple(self):
232         return (self.mx, self.prio)
233
234 class MailExchangeHandler(Handler):
235
236     def __init__(self, zones):
237         self.zones = zones
238
239     @handler(u'Adds a mail exchange to a zone')
240     def add(self, zonename, mx, prio):
241         if not zonename in self.zones:
242             raise ZoneNotFoundError(zonename)
243         if mx in self.zones[zonename].mxs:
244             raise MailExchangeAlreadyExistsError(mx)
245         self.zones[zonename].mxs[mx] = MailExchange(mx, prio)
246         self.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.zones:
251             raise ZoneNotFoundError(zonename)
252         if not mx in self.zones[zonename].mxs:
253             raise MailExchangeNotFoundError(mx)
254         self.zones[zonename].mxs[mx].prio = prio
255         self.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.zones:
260             raise ZoneNotFoundError(zonename)
261         if not mx in self.zones[zonename].mxs:
262             raise MailExchangeNotFoundError(mx)
263         del self.zones[zonename].mxs[mx]
264         self.zones[zonename].mod = True
265
266     @handler(u'Lists mail exchangers')
267     def list(self):
268         return self.zones.keys()
269
270     @handler(u'Get information about all mail exchangers')
271     def show(self):
272         return self.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     def __init__(self, zones):
286         self.zones = zones
287
288     @handler(u'Adds a name server to a zone')
289     def add(self, zone, ns):
290         if not zone in self.zones:
291             raise ZoneNotFoundError(zone)
292         if ns in self.zones[zone].nss:
293             raise NameServerAlreadyExistsError(ns)
294         self.zones[zone].nss[ns] = NameServer(ns)
295         self.zones[zone].mod = True
296
297     @handler(u'Deletes a name server from a zone')
298     def delete(self, zone, ns):
299         if not zone in self.zones:
300             raise ZoneNotFoundError(zone)
301         if not ns in self.zones[zone].nss:
302             raise NameServerNotFoundError(ns)
303         del self.zones[zone].nss[ns]
304         self.zones[zone].mod = True
305
306     @handler(u'Lists name servers')
307     def list(self):
308         return self.zones.keys()
309
310     @handler(u'Get information about all name servers')
311     def show(self):
312         return self.zones.values()
313
314
315 class Zone(Sequence):
316     def __init__(self, name):
317         self.name = name
318         self.hosts = dict()
319         self.mxs = dict()
320         self.nss = dict()
321         self.new = False
322         self.mod = False
323         self.dele = False
324
325     def as_tuple(self):
326         return (self.name, self.hosts, self.mxs, self.nss)
327
328 class ZoneHandler(Handler):
329
330     r"""ZoneHandler(zones) -> ZoneHandler instance :: Handle a list of zones.
331
332     This class is a helper for DnsHandler to do all the work related to zone
333     administration.
334
335     zones - A dictionary with string keys (zone name) and Zone instances values.
336     """
337     def __init__(self, zones):
338         self.zones = zones
339
340     @handler(u'Adds a zone')
341     def add(self, name):
342         if name in self.zones:
343             if self.zones[name].dele == True:
344                 self.zones[name].dele = False
345             else:
346                 raise ZoneAlreadyExistsError(name)
347         self.zones[name] = Zone(name)
348         self.zones[name].mod = True
349         self.zones[name].new = True
350
351
352     @handler(u'Deletes a zone')
353     def delete(self, name):
354         r"delete(name) -> None :: Delete a zone from the zone list."
355         if not name in self.zones:
356             raise ZoneNotFoundError(name)
357         self.zones[name].dele = True
358
359     @handler(u'Lists zones')
360     def list(self):
361         return self.zones.keys()
362
363     @handler(u'Get information about all zones')
364     def show(self):
365         return self.zones.values()
366
367
368 class DnsHandler(Restorable, ConfigWriter, InitdHandler, TransactionalHandler,
369                  ParametersHandler):
370     r"""DnsHandler([pickle_dir[, config_dir]]) -> DnsHandler instance.
371
372     Handles DNS service commands for the dns program.
373
374     pickle_dir - Directory where to write the persistent configuration data.
375
376     config_dir - Directory where to store de generated configuration files.
377
378     Both defaults to the current working directory.
379     """
380
381     _initd_name = 'bind'
382
383     _persistent_attrs = ('params', 'zones')
384
385     _restorable_defaults = dict(
386             zones = dict(),
387             params  = dict(
388                 isp_dns1 = '',
389                 isp_dns2 = '',
390                 bind_addr1 = '',
391                 bind_addr2 = ''
392             ),
393     )
394
395     _config_writer_files = ('named.conf', 'zoneX.zone')
396     _config_writer_tpl_dir = path.join(path.dirname(__file__), 'templates')
397
398     def __init__(self, pickle_dir='.', config_dir='.'):
399         r"Initialize DnsHandler object, see class documentation for details."
400         self._persistent_dir = pickle_dir
401         self._config_writer_cfg_dir = config_dir
402         self.mod = False
403         self._config_build_templates()
404         self._restore()
405         self.host = HostHandler(self.zones)
406         self.zone = ZoneHandler(self.zones)
407         self.mx = MailExchangeHandler(self.zones)
408         self.ns = NameServerHandler(self.zones)
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(('dns', '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(('dns', 'unfreeze', 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