#! /usr/bin/env python # -*- coding: iso-8859-15 -*- """ Génération de la configuration pour bind9 Copyright (C) Frédéric Pauget Licence : GPLv2 """ import time, sys sys.path.append('/usr/scripts/gestion') from gen_confs import gen_config from iptools import AddrInNet, AddrInNets class dns(gen_config) : """ Génération des fichiers de configuration de bind9 : * fichier DNS_CONF qui contient les définitions de zone conformément à zone_template. Ce fichier doit être inclus à partir de la config statique de bind * les fichiers de zones, ce sont eux qui contiennent les données du dns, ils ont appellés par le fichier DNS_CONF et sont générés dans DNS_DIR Leur entète est générée à partir de zone_entete. Les fichiers générés placent bind comme autoritaire sur les noms de zones_direct et les adresses de zones_reverse. Les données proviennent de la base LDAP """ ######################################PARTIE DE CONFIGURATION ### Fichiers à écrire # Répertoire d'écriture des fichiers de zone DNS_DIR='/etc/bind/generated/' # Avec un / à la fin # Fichier de définition des zones pour le maître DNS_CONF=DNS_DIR + 'zones_crans' ### Sur quelles zones on a autorité ? ## En cas de modification de ces zones penser à regéner le fichier de ## zone des esclaves (python /usr/scripts/gestion/gen_confs/bind.py) # Résolution directe zones_direct = [ 'crans.org' , 'crans.ens-cachan.fr', 'wifi.crans.org' , 'ferme.crans.org' , 'clubs.ens-cachan.fr', 'adm.crans.org' ] # Résolution inverse zones_reverse = [ '138.231.136.0/21', '138.231.144.0/24', '138.231.148.0/22' ] ### Liste DNS # Le premier est doit être le maitre DNSs = [ 'rouge.crans.org' , 'sila.crans.org' , 'freebox.crans.org' ] ### Liste des délégations de zone # Pour les demandes des ces zones, le DNS dira d'aller voir les serveurs listés ici # Pour les noms des serveurs on met l'IP sans point ou le nom avec un point DELEG = { 'tv.crans.org' : ['rouge.crans.org.' , 'sila.crans.org.' , 'freebox.crans.org.', 'mouton.ferme.crans.org.'] } ### Serveurs de mail # format : [ priorité serveur , .... ] MXs = ['10 rouge.crans.org', '20 freebox.crans.org' ] ### Entète des fichiers de zone zone_entete=""" $ORIGIN %(zone)s. $TTL 86400 @\tIN\tSOA rouge.crans.org. root.crans.org. ( %(serial)i ; numero de serie 21600 ; refresh (s) 3600 ; retry (s) 1209600 ; expire (s) 86400 ; TTL (s) ) """ # Syntaxe utilisée dans le fichier DNS_CONF pour définir une zone sur le maître zone_template=""" zone "%(NOM_zone)s" { type master; file "%(FICHIER_zone)s"; }; """ # Syntaxe utilisée dans le fichier DNS_CONF pour définir une zone sur le maître zone_template_slave=""" zone "%(NOM_zone)s" { type slave; file "%(FICHIER_zone)s"; masters { 138.231.136.3; }; }; """ ### Verbosité # Si =2, ralera (chaine warnings) si machines hors zone trouvée # Si =1, comme ci-dessus, mais ne ralera pas pour freebox # Si =0, ralera seulement contre les machines ne pouvant être classées verbose = 1 restart_cmd = '/etc/init.d/bind9 reload' ######################################FIN PARTIE DE CONFIGURATION def __str__(self) : return "DNS" def gen_slave(self) : """ Génération du fichier de config de zone pour les esclaves """ zones = self.zones_direct # Ajout des zones reverse for net in self.zones_reverse : n = map(int,net.split('/')[0].split('.')[:3]) while 1 : if not AddrInNet("%d.%d.%d.1" % tuple(n),net): break else : n.reverse() zones.append("%d.%d.%d.in-addr.arpa" % tuple(n)) n.reverse() n[2] += 1 # Ecriture fd = self._open_conf(self.DNS_CONF,'//') for zone in zones : fd.write(self.zone_template_slave % { 'NOM_zone' : zone, 'FICHIER_zone' : self.DNS_DIR + 'db.' + zone }) fd.close() def _gen(self) : ### Génération du numéro de série # Le + 1000.... s'explique pas l'idée précédente et peu pratique d'avoir # le numéro de série du type AAAAMMJJNN (année, mois, jour, incrément par jour) serial = time.time() + 1000000000 ### DNS DNS='; DNS de la zone par ordre de priorité\n' for d in self.DNSs : DNS += '@\tIN\tNS %s.\n' % d DNS += '\n' ### Serveurs de mail MX='; Serveurs de mails\n' for m in self.MXs : MX += '%(zone)s.\t' # Sera remplacé par le nom de zone plus tard MX += 'IN\tMX\t%s.\n' % m MX += '\n' direct = {} # format : { zone : [ lignes correspondantes] } reverse = {} warnings = '' # P'tit lien vers irc.rezosup.org direct["crans.org"] = "\n; irc.crans.org -> irc.rezosup.org\n" direct["crans.org"] += "irc\tIN\tCNAME\tirc.rezosup.org.\n\n" ### Ajout des parametres SPF direct['crans.org'] +='; Parametres SPF\n' direct['crans.org'] +='crans.org.\tIN\tTXT\t"v=spf1 a +all"\n\n' direct['crans.ens-cachan.fr'] ='; Parametres SPF\n' direct['crans.ens-cachan.fr'] +='crans.ens-cachan.fr.\tIN\tTXT\t"v=spf1 a:crans.org +all"\n\n' ### Tri des machines self.anim.iter=len(self.machines) for machine in self.machines : self.anim.cycle() # Calculs préliminaires try : nom , zone = machine.nom().split('.',1) zone = zone.encode('iso-8859-1') except : warnings += u'Machine ignorée (mid=%s) : format nom incorrect (%s)\n' % ( machine.id().encode('iso-8859-1'), machine.nom().encode('iso-8859-1') ) continue # Le direct if zone in self.zones_direct : ligne = "%s\tIN\tA\t%s\n" % ( nom, machine.ip() ) try : direct[zone] += ligne except : direct[zone] = ligne elif self.verbose and machine.nom() != "ftp.federez.net": warnings += u'Résolution directe ignorée (mid=%s) : zone non autoritaire (%s)\n' % ( machine.id().encode('iso-8859-1'), zone.encode('iso-8859-1') ) # Le direct avec alias for alias in machine.alias() : # Cas particulier : nom de l'alias = nom de la zone if alias in self.zones_direct : ligne = "@\tIN\tA\t%s\n" % machine.ip() ligne = ligne.encode('iso-8859-1') alias = alias.encode('iso-8859-1') try : direct[alias] += ligne except : direct[alias] = ligne continue # Bon format ? alias_l = alias.split('.') ok = 0 for i in range(len(alias_l)) : zone_essai = '.'.join(alias_l[i:]) if zone_essai in self.zones_direct : # On est autoritaire sur cette zone # On place donc l'alias dans le fichier de cette zone zone = zone_essai nom = '.'.join(alias_l[:i]) ok = 1 break if not ok: warnings += u'Alias ignoré (mid=%s) : %s\n' % ( machine.id().encode('iso-8859-1'), alias.encode('iso-8859-1') ) continue zone = zone.encode('iso-8859-1') ligne = "%s\tIN\tCNAME\t%s.\n" % ( nom, machine.nom() ) try : direct[zone] += ligne except : direct[zone] = ligne # Le reverse if AddrInNets(machine.ip(), self.zones_reverse) : base_ip = machine.ip().split('.') base_ip.reverse() zone = "%s.%s.%s.in-addr.arpa" % tuple(base_ip[1:]) zone = zone.encode('iso-8859-1') ligne = '%s\tIN\tPTR\t%s.\n' % (base_ip[0],machine.nom()) try : reverse[zone] += ligne except : reverse[zone] = ligne elif self.verbose >= 2 or machine.nom() not in ('freebox.crans.org', 'mosan.crans.org', 'ovh.crans.org'): warnings += u'Résolution inverse ignorée (mid=%s) : ip sur zone non autoritaire (%s)\n' % ( machine.id().encode('iso-8859-1'), machine.ip().encode('iso-8859-1') ) ### Ajouts pour les fichiers de résolution directs for zone in direct.keys() : # MXs direct[zone] = MX % { 'zone' : zone } + direct[zone] ### Ajout des délégations de zones for deleg in self.DELEG.keys(): nom, zone = deleg.split('.',1) if not zone in direct.keys(): warnings += u'Délégation ignorée %s : on ne génère pas la zone parent\n' % deleg continue for serv in self.DELEG[deleg]: direct[zone] = direct[zone] + "%s\tIN\tNS\t%s\n" % ( nom, serv ) ### Ecriture des fichiers de zone et préparation du fichier de définition f = '' for zone, lignes in direct.items() + reverse.items() : file = self.DNS_DIR + 'db.' + zone fd = self._open_conf(file,';') fd.write(self.zone_entete % \ { 'zone' : zone, 'serveur_autoritaire' : self.DNSs[0] , 'serial' : serial } ) fd.write('\n') fd.write(DNS) fd.write(lignes) fd.close() f += self.zone_template % { 'NOM_zone' : zone, 'FICHIER_zone' : file } ### Ecriture fichier de définition fd = self._open_conf(self.DNS_CONF,'//') fd.write(f) fd.close() return warnings if __name__ == '__main__' : from socket import gethostname if gethostname().split(".")[0] == 'rouge' : print "Ce serveur est maître !, utiliser generate." else : print "Reconfiguration de bind en esclave (penser à le relancer)." c = dns() c.gen_slave()