#!/usr/bin/python # Generate one big configuration file and does not care about the servers defined in the DB # XXX: To check: Take care of treating the numeric in the DB correctly and not string (convert if needed) # XXX: Sauron check as well the modification date to only write modified files, we do not as those query are really fast # XXX: we do not set the record as "exported" neither (so the interface still show them as not-exported) import sys import os import pgdb as db # XXX: This is IPV4 only ... # quick hack as we have lib somewhere def iptoarpa (data): return '.'.join(data.split('.')[::-1]) + ".in-addr.arpa." option = {} option['export-tinydns'] = True option['export-bind'] = False option['all-in-one'] = True option['output-stdout'] = False option['tmp-directory'] = './export' option['one-zone-only'] = False BIND_LINE = "%-32s %6s %2s %-6s %s\n" domain_template = """ select zones.name, servers.hostname as ns, coalesce(zones.hostmaster,servers.hostmaster) as hostmaster, zones.serial, coalesce(zones.refresh,servers.refresh) as refresh, coalesce(zones.retry,servers.retry) as retry, coalesce(zones.expire,servers.expire) as expire, coalesce(zones.minimum,servers.minimum) as minimum, coalesce(zones.ttl,servers.ttl), zones.type, zones.cuser, zones.cdate, zones.mdate, zones.muser, zones.active, zones.serial_date, zones.comment from servers join zones on servers.id = zones.server where zones.active = true %s order by zones.name; """ domain_query = domain_template % ("and zones.dummy = 'f'\n\tand zones.reverse = 'f'\n\t%s") domain_reverse = domain_template % ("and zones.reverse = 't'\n\t%s") ns_query = """ select hosts.domain as host, zones.name as zone, ns_entries.ns, coalesce(hosts.ttl,zones.ttl) as ttl, coalesce(zones.ttl,servers.ttl) as default_ttl from servers join zones on servers.id = zones.server join hosts on zones.id = hosts.zone join ns_entries on ns_entries.ref = hosts.id where (hosts.type = 10 or hosts.type = 2) and (zones.active = true) %s order by zones.name, hosts.domain; """ mx_query = """ select hosts.domain as host, zones.name as zone, mx_entries.mx as mx_entries, mx_entries.pri as mx_pri, coalesce(hosts.ttl,zones.ttl) as ttl, coalesce(zones.ttl,servers.ttl) as default_ttl from servers join zones on servers.id = zones.server join hosts on zones.id = hosts.zone join mx_entries on hosts.type in (3,10) and mx_entries.type = 2 and mx_entries.ref = hosts.id where zones.active = true %s order by zones.name, mx_entries.pri, hosts.domain; """ a_query = """ select hosts.domain as host, zones.name as zone, a_entries.ip as a_ip, a_entries.forward, coalesce(hosts.ttl,zones.ttl) as ttl, coalesce(zones.ttl,servers.ttl) as default_ttl, hosts.type from servers join zones on servers.id = zones.server join hosts on zones.id = hosts.zone join a_entries on hosts.type in (1,10) and a_entries.host = hosts.id where zones.active = true %s order by zones.name, hosts.domain; """ a_reverse = a_query % "and a_entries.reverse = 't' %s" a_forward = a_query % "and a_entries.forward = 't' %s" txt_query = """ select hosts.domain as host, zones.name as zone, txt_entries.txt as txt, coalesce(hosts.ttl,zones.ttl) as ttl, coalesce(zones.ttl,servers.ttl) as default_ttl from servers join zones on servers.id = zones.server join hosts on zones.id = hosts.zone join txt_entries on txt_entries.ref = hosts.id and txt_entries.txt != '' where zones.active = true %s order by zones.name, hosts.domain; """ # This query takes AGES ... :( internal_cname_query = """ select hosts.domain as host, zones.name as zone, int_hosts.domain as cname_host, int_zones.name as cname_zone, coalesce(hosts.ttl,zones.ttl) as ttl, coalesce(zones.ttl,servers.ttl) as default_ttl from servers join zones on servers.id = zones.server join hosts on zones.id = hosts.zone join hosts as int_hosts on hosts.alias = int_hosts.id join zones as int_zones on int_zones.id = int_hosts.zone where hosts.alias > 0 and zones.active = true %s order by zones.name, hosts.domain; """ external_cname_query = """ select hosts.domain as host, zones.name as zone, hosts.cname_txt as host_cname, coalesce(hosts.ttl,zones.ttl) as ttl, coalesce(zones.ttl,servers.ttl) as default_ttl from servers join zones on servers.id = zones.server join hosts on zones.id = hosts.zone where hosts.cname_txt != '' and zones.active = true %s order by zones.name, hosts.domain; """ def err (code,*str): sys.stderr.write(*str) sys.exit(code) class ExportZone: def __init__ (self,server,database,user,password): self.file = None self.sql = None self.path = None self.remove_file = False try: self.sql = db.connect(dsn='%s:%s' % (server,database),user=user) except: err(1,'could not establish connection to database\n') if option['export-tinydns']: option['export-bind'] = False if option['export-bind']: option['all-in-one'] = False if option['output-stdout']: option['one-zone-only'] = True if option['all-in-one'] and not option['output-stdout']: # clear the file self._create('data') def __del__ (self): if self.file: try: self.file.close() except: err(1,'could not close the file') if self.sql: try: self.sql.close() except: err(1,'issue closing DB connection') if self.remove_file and self.path and self.file: try: self.file.close() self.file = None self.unlink(self.path) self.path = None except: err(1,'could not removed the files created') def _fqdn (self,*args): host = args[0] if host[-1] == '.': return host fqdn = '.'.join(args) if fqdn[-1] != '.': fqdn += '.' fqdn = fqdn.replace('@.','') return fqdn def _ttl (self,ttl,default=None): if option['export-bind']: if ttl == default: return '' if ttl == None: if default == None: return '' else: return default else: return ttl def _make_path (self,name): if name[-1] == '.': name = name[:-1] if option['output-stdout'] == True: path = '/dev/stdout' else: path = os.path.join(option['tmp-directory'],name) return path def _create (self,name): path = self._make_path(name) if self.path == path: return try: if self.file: self.file.close() if option['output-stdout'] == True: self.file = os.fdopen(os.dup(sys.stdout.fileno()),'w') else: self.file = open(path,'w') self.path = path except: err(1,'issue opening file for creation %s\n' % path) def _append (self,name): try: if option['all-in-one']: name = 'data' path = self._make_path(name) if self.path == path: return if self.file: self.file.close() if option['output-stdout'] == True: self.file = os.fdopen(os.dup(sys.stdout.fileno()),'w') else: self.file = open(path,'a') self.path = path except: err(1,'issue opening file for creation') def process (self,zones = None): if zones == None: if option['one-zone-only']: sys.stderr.write('can only export one zone on stdout\n') sys.exit(1) self._process("") else: for zone in zones: select_and = "and zones.name = '%s'" % zone self._process(select_and) def _process (self,select_and): actions = [] actions.append((domain_query,select_and,self.SOA)) actions.append((ns_query,select_and,self.NS)) actions.append((mx_query,select_and,self.MX)) actions.append((a_forward,select_and,self.A)) actions.append((txt_query,select_and,self.TXT)) actions.append((internal_cname_query,select_and,self.CNAME_internal)) actions.append((external_cname_query,select_and,self.CNAME_external)) if option['one-zone-only'] == False: actions.append((domain_reverse,"",self.ARPA)) actions.append((a_reverse,"",self.REVERSE)) for query,select,function in actions: query = query % select cursor = self.sql.cursor() execution = cursor.execute(query) row = cursor.fetchone () while row != None: function(row) row = cursor.fetchone () def SOA(self,*args): value = args[0] name = value[0] ns = value[1] hostmaster = value[2] serial = value[3] refresh = self._ttl(value[4]) retry = self._ttl(value[5]) expire = self._ttl(value[6]) minimum = self._ttl(value[7]) ttl = self._ttl(value[8]) name = self._fqdn(name) # XXX: IS THE TTL done correctly ? use the self._ttl function if option['export-tinydns']: if option['all-in-one']: self._append(name) else: self._create(name) self.file.write('Z%s:%s:%s:%s:%s:%s:%s:%s::\n' % (name,ns,hostmaster,serial,refresh,retry,expire,minimum)) if option['export-bind']: zone_ttl = '' # Always blank in sauron self._create(name) # XXX: This is always IN as class in the DB for the moment, for every type of record self.file.write('; %s - exa dns export tool version %s\n' % (name,'0.1')) self.file.write('; generated by %s %s\n' % ('thomas','around now')) self.file.write(';\n') self.file.write('$TTL %s; default TTL for this zone\n' % ttl) self.file.write('$ORIGIN %s\n' % name) self.file.write('@\t%s\t%s\tSOA\t%s %s (\n' % (zone_ttl,'IN',name,hostmaster)) self.file.write('\t\t\t\t\t%s\t; %s\n' % (refresh,'refresh')) self.file.write('\t\t\t\t\t%s\t; %s\n' % (retry,'retry')) self.file.write('\t\t\t\t\t%s\t; %s\n' % (expire,'expire')) self.file.write('\t\t\t\t\t%s\t; %s\n' % (minimum,'minimum')) self.file.write('\t\t\t\t)\n') def NS (self,*args): value = args[0] host = value[0] zone = value[1] ns = value[2] ttl = self._ttl(value[3],value[4]) name = self._fqdn(host,zone) zone = self._fqdn(zone) ns = self._fqdn(ns) self._append(zone) if option['export-tinydns']: self.file.write('&%s::%s:%s::\n' % (name,ns,ttl)) if option['export-bind']: self.file.write(BIND_LINE % ('',ttl,'IN','NS',ns)) def MX (self,*args): value = args[0] host = value[0] zone = value[1] destination = value[2] priority = value[3] ttl = self._ttl(value[4],value[5]) name = self._fqdn(host,zone) zone = self._fqdn(zone) self._append(zone) if option['export-tinydns']: self.file.write('@%s::%s:%s:%s::\n' % (name,destination,priority,ttl)) if option['export-bind']: self.file.write(BIND_LINE % (name,ttl,'IN','MX %s' % priority,destination)) def A (self,*args): # True for TTL in the file value = args[0] host = value[0] zone = value[1] ip = value[2] forward = value[3] ttl = self._ttl(value[4],value[5]) type = value[6] name = self._fqdn(host,zone) zone = self._fqdn(zone) self._append(zone) if option['export-tinydns']: self.file.write('+%s:%s:%s::\n' % (name,ip,ttl)) if option['export-bind']: self.file.write(BIND_LINE % (name,ttl,'IN','A',ip)) def TXT (self,*args): value = args[0] host = value[0] zone = value[1] string = value[2] ttl = self._ttl(value[3],value[4]) name = self._fqdn(host,zone) zone = self._fqdn(zone) self._append(zone) if option['export-tinydns']: string = string.replace(':','\\072') string = string.replace('\t','\\011') string = string.replace('\r','\\015') string = string.replace('\n','\\012') self.file.write("'%s:%s:%s::\n" % (name,string,ttl)) if option['export-bind']: string = '"' + string + '"' self.file.write(BIND_LINE % (name,ttl,'IN','TXT',string)) def CNAME_external (self,*args): value = args[0] host = value[0] zone = value[1] cname = value[2] ttl = self._ttl(value[3],value[4]) name = self._fqdn(host,zone) cname = self._fqdn(cname) zone = self._fqdn(zone) self._append(zone) if option['export-tinydns']: self.file.write("C%s:%s:%s::\n" % (name,cname,ttl)) if option['export-bind']: self.file.write(BIND_LINE % (name,ttl,'IN','CNAME',cname)) def CNAME_internal (self,*args): value = args[0] host = value[0] zone = value[1] cname_host = value[2] cname_zone = value[3] ttl = self._ttl(value[4],value[5]) name = self._fqdn(host,zone) cname = self._fqdn(cname_host,cname_zone) zone = self._fqdn(zone) self._append(zone) if option['export-tinydns']: self.file.write("C%s:%s:%s::\n" % (name,cname,ttl)) if option['export-bind']: self.file.write(BIND_LINE % (name,ttl,'IN','CNAME',cname)) def ARPA (self,*args): value = args[0] name = value[0] ns = value[1] hostmaster = value[2] serial = value[3] refresh = self._ttl(value[4]) retry = self._ttl(value[5]) expire = self._ttl(value[6]) minimum = self._ttl(value[7]) ttl = self._ttl(value [8]) name = self._fqdn(name) if option['export-tinydns']: if option['all-in-one']: self._append(name) else: self._create(name) self.file.write('Z%s:%s:%s:%s:%s:%s:%s:%s::\n' % (name,ns,hostmaster,serial,refresh,retry,expire,minimum)) def REVERSE (self,*args): # True for TTL in the file value = args[0] host = value[0] zone = value[1] ip = value[2] forward = value[3] ttl = self._ttl(value[4],value[5]) type = value[6] name = self._fqdn(host,zone) zone = self._fqdn(zone) arpa = iptoarpa(ip) self._append(zone) if option['export-tinydns']: self.file.write('^%s:%s:%s::\n' % (arpa,name,ttl)) export = ExportZone('127.0.0.1','dbuser','dbpassword','') export.process(None) #export.process(["workingtestzone.net"]) sys.exit(0)