bind.py 23.8 KB
Newer Older
bernat's avatar
bernat committed
1
#! /usr/bin/env python
2
# -*- coding: utf-8 -*-
3

4
""" Génération de la configuration pour bind9
bernat's avatar
bernat committed
5

6
Copyright (C) Frédéric Pauget
bernat's avatar
bernat committed
7 8
Licence : GPLv2
"""
9
import time, sys, re, hashlib, base64, os
10
sys.path.append('/usr/scripts/gestion')
11
from socket import gethostname
bernat's avatar
bernat committed
12
from gen_confs import gen_config
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
13 14

import config
15
import config.dns
16
from iptools import AddrInNet, AddrInNets
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
17 18 19
import ip6tools

import netaddr
20
import ldap_crans
bernat's avatar
bernat committed
21

22 23 24
def short_name(fullhostname):
    return fullhostname.split(".")[0]

25 26 27 28


def netv4_to_arpa(net):
    addr, prefixlen = net.split('/')
29 30
    if prefixlen == '8':
        return ["%s.in-addr.arpa" % addr.split('.')[0]]
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
    if prefixlen == '16':
        return ["%s.in-addr.arpa" % '.'.join(reversed(addr.split('.')[0:2]))]
    zones=[]
    n = map(int,net.split('/')[0].split('.')[:3])
    while 1 :
        try:
            innet = AddrInNet("%d.%d.%d.1" % tuple(n),net)
        except ValueError:
            break
        else:
            if not innet:
                break
            else :
                n.reverse()
                zones.append("%d.%d.%d.in-addr.arpa" % tuple(n))
                n.reverse()
        n[2] += 1
    return zones
    
def netv6_to_arpa(net):
    n = netaddr.IPNetwork(net)
    network_reverse = netaddr.IPAddress(n.first).reverse_dns
    zone = network_reverse.split('.')[(128-n.prefixlen)/4:-1]
    return '.'.join(zone)


bernat's avatar
bernat committed
57
class dns(gen_config) :
58
    """
59 60 61
    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
bernat's avatar
bernat committed
62
de bind
63 64 65
        * 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.
bernat's avatar
bernat committed
66

67 68
    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
bernat's avatar
bernat committed
69 70 71
la base LDAP
    """
    ######################################PARTIE DE CONFIGURATION
72

73 74 75 76 77
    ### Fichiers à écrire
    # Répertoire d'écriture des fichiers de zone
    DNS_DIR = '/etc/bind/generated/' # Avec un / à la fin
    DNSSEC_DIR = '/etc/bind/signed/' # Avec un / à la fin
    # Fichier de définition des zones pour le maître
78 79
    DNS_CONF = DNS_DIR + 'zones_crans'

80
    # Fichier de définition des zones pour les esclaves géré par BCfg2
81
    DNS_CONF_BCFG2 = "/var/lib/bcfg2/Cfg/etc/bind/generated/zones_crans/zones_crans"
82

83 84
    ### Sur quelles zones on a autorité ?
    ## En cas de modification de ces zones penser à regéner le fichier de
85
    ## zone des esclaves (sur le serveur principal de bcfg2 : python /usr/scripts/gestion/gen_confs/bind.py puis lancer bcfg2 sur les miroirs)
86
    # Résolution directe
87
    zones_direct = config.dns.zones_direct
88
    # Zones signée par opendnssec sur le serveur maitre
89
    zones_dnssec = config.dns.zones_dnssec
90
    # Zones alias pour les enregistrement A AAAA CNAME TXT et SSHFP
91
    zone_alias = config.dns.zone_alias
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
92
    zones_v4_to_v6 = {
93 94
        'wifi.crans.eu': 'wifi.v6.crans.eu',
        'crans.eu': 'v6.crans.eu',
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
95 96 97 98 99
        'crans.org': 'v6.crans.org',
        'wifi.crans.org': 'wifi.v6.crans.org',
        'adm.crans.org': 'adm.v6.crans.org',
        'ferme.crans.org': 'ferme.v6.crans.org',
        }
100 101 102 103 104 105 106
    zone_alias.update({
        'wifi.v6.crans.eu': ['v6.wifi.crans.eu'],
        'wifi.v6.crans.org': ['v6.wifi.crans.org'],
        'adm.v6.crans.org': ['v6.adm.crans.org'],
        'ferme.v6.crans.org': ['v6.ferme.crans.org'],
        })

107
    # Résolution inverse
108
    zones_reverse = config.dns.zones_reverse
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
109
    zones_v6_to_net = {
110
        'crans.eu': config.prefix["fil"][0],
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
111 112 113 114
        'crans.org': config.prefix["fil"][0],
        'wifi.crans.org': config.prefix["wifi"][0],
        'adm.crans.org': config.prefix["adm"][0],
        'ferme.crans.org': config.prefix["fil"][0],
115
        # Hack pour générer un fichier de zone vide
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
116 117
        '##HACK##': config.prefix["subnet"][0],
        }
bernat's avatar
bernat committed
118
    ### Liste DNS
119
    # Le premier doit être le  maitre
120
    DNSs = ['sable.crans.org', 'freebox.crans.org', 'ovh.crans.org']
121 122
    DNSs_private = []
    ip_master_DNS = config.dns.master
123 124
    
    zone_multicast = 'tv.crans.org'
125

126 127
    ### Liste des délégations de zone
    # Pour les demandes de ces zones, le DNS dira d'aller voir les serveurs listés ici
128
    # Pour les noms des serveurs on met l'IP sans point ou le nom avec un point
129
    DELEG = { 
130
      zone_multicast : [ 'sable.crans.org.' , 'mdr.crans.org.', 'freebox.crans.org.', 'ovh.crans.org.'] ,
131
    }
132

bernat's avatar
bernat committed
133
    ### Serveurs de mail
134
    # format : [ priorité serveur , .... ]
135
    MXs = ['10 redisdead.crans.org', '20 ovh.crans.org', '25 freebox.crans.org']
136 137
    SRVs = [
            '_jabber._tcp.crans.org.         86400 IN SRV 5 0 5269 xmpp.crans.org.',
138
            '_xmpp-server._tcp.crans.org.    86400 IN SRV 5 0 5269 xmpp.crans.org.',
139 140 141 142 143
            '_xmpp-client._tcp.crans.org.    86400 IN SRV 5 0 5222 xmpp.crans.org.',
            '_sip._udp.crans.org.            86400 IN SRV 5 0 5060 asterisk.crans.org.',
            '_sip._tcp.crans.org.            86400 IN SRV 5 0 5060 asterisk.crans.org.',
            '_sips._tcp.crans.org.           86400 IN SRV 5 0 5061 asterisk.crans.org.',
           ]
144
    
145
    # DS à publier dans zone parentes : { parent : [ zone. TTL IN DS key_id algo_id 1 hash ] }
146 147
    # ex : { 'crans.eu' :  ['wifi.crans.eu.		86400	IN	DS	33131 8 1 3B573B0E2712D8A8B1B0C3'] }
    # /!\ Il faut faire attention au rollback des keys, il faudrait faire quelque chose d'automatique avec opendnssec
148 149 150 151
    DS = {
        'crans.eu': [
            'wifi.crans.eu.  3600    IN      DS      49739 8 2 de49579462a8f439c9b1853c2a06d337ae4e5ffbd41c21449d4c7112794254ed',
            'v6.crans.eu.    3600    IN      DS      81 8 2 36fa8d4782ecdbc25f0455e2c14aea899b86cf497ce78c7d90d1cf85647f5190',
152 153 154 155 156 157 158 159 160
          ],
        'v6.crans.eu' : [
            'wifi.v6.crans.eu.	3600	IN	DS	1799 8 2 52a40a7dfb3e9c88aee032c21c59be756c8d3de29149c408ed8b699d83e30032',
         ],
        'crans.org': [
            'v6.crans.org.       3600    IN      DS      23641 8 2 3fff97a2581f0f2f49257b4914d5badf8ccb0a49c5a6f4cbf2f520b97de332d0',
            'adm.crans.org.      3600    IN      DS      565 8 2 498f6cd5bcf291aae4129700a7569fa6e9a86821185bd655f0b9efc6a3bf547e',
            'ferme.crans.org.    3600    IN      DS      35156 8 2 b63a1443b3d7434429e879e046bc8ba89056cdcb4b9c3566853e64fd521895b8',
            'wifi.crans.org.     3600    IN      DS      41320 8 2 024799c1d53f1e827f03d17bc96709b85ee1c05d77eb0ebeadcfbe207ee776a4',
161
            'tv.crans.org.       3600    IN      DS      30910 8 2 3317f684081867ab94402804fbb3cd187e29655cc7f34cb92c938183fe0b71f5',
162 163 164 165 166 167
          ],
        'v6.crans.org' : [
            'adm.v6.crans.org.   3600    IN      DS      1711 8 2 f154eeb8eb346d2ca5cffb3f9cc464a17c0c4d69ee425b4fe44eaed7f5dd253b',
            'ferme.v6.crans.org. 3600    IN      DS      44434 8 2 fb87cb4216599cb6574add543078a9e48d0e50438483386585a9960557434ab0',
            'wifi.v6.crans.org.  3600    IN      DS      59539 8 2 dbe86f2f2e92d6a27bd1436f03ec1588f2948a2aa02124de0383be801cced85e',
         ]
168
    }
169
        
170

171
    ### Entète des fichiers de zone
bernat's avatar
bernat committed
172 173
    zone_entete="""
$ORIGIN %(zone)s.
174
$TTL 3600
175
@\tIN\tSOA %(serveur_autoritaire)s. root.crans.org. (
bernat's avatar
bernat committed
176
    %(serial)i ; numero de serie
177 178 179
    21600 ; refresh (s)
    3600 ; retry (s)
    1209600 ; expire (s)
180
    3600 ; TTL (s)
bernat's avatar
bernat committed
181 182
    )
"""
183

184
    # Syntaxe utilisée dans le fichier DNS_CONF pour définir une zone sur le maître
bernat's avatar
bernat committed
185 186 187 188 189 190
    zone_template="""
zone "%(NOM_zone)s" {
    type master;
    file "%(FICHIER_zone)s";
};
"""
191
    # Syntaxe utilisée dans le fichier DNS_CONF_BCFG2 pour définir une zone sur un esclave
192 193 194 195
    zone_template_slave="""
zone "%(NOM_zone)s" {
    type slave;
    file "%(FICHIER_zone)s";
196
    masters { %(ip_master_DNS)s; };
197 198 199
};
"""

200 201
    ### Verbosité
    # Si =2, ralera (chaine warnings) si machines hors zone trouvée
202
    # Si =1, comme ci-dessus, mais ne ralera pas pour freebox
203
    # Si =0, ralera seulement contre les machines ne pouvant être classées
bernat's avatar
bernat committed
204
    verbose = 1
205

206 207 208 209 210 211
    hostname = short_name(gethostname())
    if hostname ==  short_name(DNSs[0]):
        restart_cmd = '/usr/sbin/ods-signer sign --all && /etc/init.d/bind9 reload'
    else:
        restart_cmd = '/etc/init.d/bind9 reload'

bernat's avatar
bernat committed
212
    ######################################FIN PARTIE DE CONFIGURATION
213

bernat's avatar
bernat committed
214
    def __str__(self) :
bernat's avatar
bernat committed
215
        return "DNS"
216

Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
217
    def reverse(self, net, ip):
218 219
        """Renvoie la zone DNS inverse correspondant au réseau et à
        l'adresse donnés, ainsi que le nombre d'éléments de l'ip a
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
220 221 222 223 224 225 226 227 228 229 230 231
        mettre dans le fichier de zone."""
        n = netaddr.IPNetwork(net)
        a = netaddr.IPAddress(ip)
        rev_dns_a = a.reverse_dns.split('.')[:-1]
        assert a in n
        if n.version == 4:
            if n.prefixlen == 16:
                return ('.'.join(rev_dns_a[2:]), 2)
            else:
                return ('.'.join(rev_dns_a[1:]), 1)
        elif n.version == 6:
            return ('.'.join(rev_dns_a[(128-n.prefixlen)/4:]), (128-n.prefixlen)/4)
232
            
233 234 235 236 237
    def gen_tv(self):
        serial = time.time() + 1000000000
        zone_reverse=netv4_to_arpa(config.NETs['multicast'][0])[0]
        sap=open('/tmp/chaines_recup_sap.txt').readlines()
         
238
        DNS='; DNS de la zone par ordre de priorité\n'
239 240 241 242 243 244 245 246 247
        for d in self.DELEG[self.zone_multicast] :
            DNS += '@\tIN\tNS %s\n' % d
        DNS += '\n'
        
        lignes_d = '\n'
        lignes_r = '\n'
        lignes_d +='@\tIN\tA\t%s\n' % '138.231.136.243'
        for line in sap:
            [nom,ip]=line.split(':')
248
            nom=re.sub('TNT([0-9]*) ','',nom) # on enlève les TNT## des noms
249
            nom=nom.replace('TNT%2lcn ','') # on enlève les TNT## des noms
250 251
            nom=re.sub(' +([^ ])','-\g<1>',nom) # on remplaces les espaces intérieur par un tiret
            nom=re.sub('[ .():,"\'+<>]','',nom) # on enlève tous les caractères illégaux
252 253 254 255 256 257 258
            nom=nom.lower()
            try:
                [ip1,ip2,ip3,ip4]=ip.strip().split('.')
                lignes_r += '%s.%s.%s\tIN\tPTR\t%s.%s.\n' % (ip4,ip3,ip2,nom,self.zone_multicast)
                lignes_d +='%s\tIN\tA\t%s' % (nom,ip)
            except:
                pass
259
        # Écriture de la zone directe
260 261 262 263 264 265 266 267 268
        file = self.DNS_DIR  + 'db.' + self.zone_multicast
        fd = self._open_conf(file,';')
        fd.write(self.zone_entete % \
        { 'zone' : self.zone_multicast, 'serveur_autoritaire' : self.DELEG[self.zone_multicast][0][0:-1] , 'serial' : serial } )
        fd.write('\n')
        fd.write(DNS)
        fd.write(lignes_d)
        fd.close()
        
269
        # Écriture du reverse
270 271 272 273 274 275 276 277 278 279
        file = self.DNS_DIR  + 'db.' + zone_reverse
        fd = self._open_conf(file,';')
        fd.write(self.zone_entete % \
        { 'zone' : zone_reverse, 'serveur_autoritaire' : self.DELEG[self.zone_multicast][0][0:-1] , 'serial' : serial } )
        fd.write('\n')
        fd.write(DNS)
        fd.write(lignes_r)
        fd.close()
        
    
280
    def gen_slave(self) :
281
        """ Génération du fichier de config de zone pour les esclaves """
bernat's avatar
bernat committed
282
        zones = self.zones_direct
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
283
        zones.extend(self.zones_v4_to_v6.values())
284 285 286
        zones.extend([z for l in self.zone_alias.values() for z in l])
        zones = list(set(zones))
        zones.sort()
287

bernat's avatar
bernat committed
288
        # Ajout des zones reverse
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
289 290
        for net in self.zones_reverse:
            # IPv4 reverse
291
            zones.extend(netv4_to_arpa(net))
292

Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
293 294
        for net in set(self.zones_v6_to_net.values()):
            # IPv6 reverse
295
            zones.append(netv6_to_arpa(net))
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
296

bernat's avatar
bernat committed
297
        # Ecriture
298
        fd = self._open_conf(self.DNS_CONF_BCFG2,'//')
bernat's avatar
bernat committed
299
        for zone in zones :
300 301 302 303
            if zone in self.zones_dnssec:
                path=self.DNSSEC_DIR  + 'db.' + zone
            else:
                path=self.DNS_DIR  + 'db.' + zone
bernat's avatar
bernat committed
304
            fd.write(self.zone_template_slave % { 'NOM_zone' : zone,
305
                       'FICHIER_zone' : path,
306
                       'ip_master_DNS': self.ip_master_DNS})
307

308
        fd.close()
309

bernat's avatar
bernat committed
310
    def _gen(self) :
311 312 313
        ### 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)
bernat's avatar
bernat committed
314
        serial = time.time() + 1000000000
315

bernat's avatar
bernat committed
316
        ### DNS
317
        DNS='; DNS de la zone par ordre de priorité\n'
bernat's avatar
bernat committed
318 319 320
        for d in self.DNSs :
            DNS += '@\tIN\tNS %s.\n' % d
        DNS += '\n'
321

bernat's avatar
bernat committed
322 323 324
        ### Serveurs de mail
        MX='; Serveurs de mails\n'
        for m in self.MXs :
325
            MX += '@\t' # Sera remplacé par le nom de zone plus tard
bernat's avatar
bernat committed
326 327
            MX += 'IN\tMX\t%s.\n' % m
        MX += '\n'
328

bernat's avatar
bernat committed
329 330 331
        direct = {} # format : { zone : [ lignes correspondantes] }
        reverse = {}
        warnings = ''
bernat's avatar
bernat committed
332

333
        direct['crans.org'] = ""
334

bernat's avatar
bernat committed
335
        # P'tit lien vers irc.rezosup.org
336 337
        #direct["crans.org"]  = "\n; irc.crans.org -> irc.rezosup.org\n"
        #direct["crans.org"] += "irc\tIN\tCNAME\tirc.rezosup.org.\n\n"
338

bernat's avatar
bernat committed
339
        ### Tri des machines
340
        self.anim.iter=len(self.machines)
bernat's avatar
bernat committed
341 342
        for machine in self.machines :
            self.anim.cycle()
343
            # Calculs préliminaires
344
            try :
bernat's avatar
bernat committed
345
                nom , zone = machine.nom().split('.',1)
346
                zone = zone.encode('utf-8')
bernat's avatar
bernat committed
347
            except :
348
                warnings += u'Machine ignorée (mid=%s) : format nom incorrect (%s)\n' % ( machine.id().encode('utf-8'), machine.nom().encode('utf-8') )
bernat's avatar
bernat committed
349
                continue
350

bernat's avatar
bernat committed
351 352 353
            # Le direct
            if zone in self.zones_direct :
                ligne = "%s\tIN\tA\t%s\n" % ( nom, machine.ip() )
354
                # Si la machine est une borne wifi, on ajoute la position
355
                if isinstance(machine,ldap_crans.BorneWifi) and machine.position():
356
                    ligne +="%s\tIN\tTXT\t\"LOC %s,%s \"\n" %  (nom,machine.position()[0],machine.position()[1])
357
                # Si la machine à des clefs ssh, on ajoute les champs SSFP correspondant
358
                for sshkey in machine.sshFingerprint():
359
                    try:
360 361 362 363 364 365 366 367
                        [algo_txt,key]=sshkey.split()[:2]
                        algo=None
                        for value in config.sshfp_algo.values():
                            if algo_txt == value[1]:
                                algo=value[0]
                                break
                        if not algo: 
                            raise ValueError("Invalid Algorithms %s" % algo_txt)
368 369 370
                        key=hashlib.sha1(base64.b64decode(key)).hexdigest()
                        ligne +="%s\tIN\tSSHFP\t%s\t1\t%s\n" % (nom,algo,key)
                    except(ValueError,TypeError): pass
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
371
                direct[zone] = direct.get(zone, "") + ligne
372 373
                if isinstance(machine,ldap_crans.BorneWifi):
                    direct['ap.crans.org'] = direct.get('ap.crans.org', "") + ligne
374
            elif self.verbose and machine.nom() != "ftp.federez.net":
375
                warnings += u'Résolution directe ignorée (mid=%s) : zone non autoritaire (%s)\n' % ( machine.id().encode('utf-8'), zone.encode('utf-8') )
376

Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
            # IPv6
            if zone in self.zones_v4_to_v6:
                # Direct
                zone_v6 = self.zones_v4_to_v6[zone]
                ipv6 = machine.ipv6()
                net_v6 = machine.netv6()
                ligne = "%s\tIN\tAAAA\t%s\n" % (nom, ipv6)
                direct[zone_v6] = direct.get(zone_v6, "") + ligne
                if machine.dnsIpv6():
                    direct[zone] = direct.get(zone, "") + ligne
                # Reverse
                zone_rev, length = self.reverse(net_v6, ipv6)
                rev = '.'.join(ipv6.reverse_dns.split('.')[:length])
                ligne = "%s\tIN\tPTR\t%s.\n" % (rev, machine.nom6())
                reverse[zone_rev] = reverse.get(zone_rev, "") + ligne


bernat's avatar
bernat committed
394 395
            # Le direct avec alias
            for alias in machine.alias() :
396
                alias = alias.encode('utf-8')
bernat's avatar
bernat committed
397 398 399
                # Cas particulier : nom de l'alias = nom de la zone
                if alias in self.zones_direct :
                    ligne = "@\tIN\tA\t%s\n" % machine.ip()
400
                    ligne = ligne.encode('utf-8')
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
401 402 403
                    direct[alias] = direct.get(alias, "") + ligne
                    if machine.dnsIpv6():
                        ligne = "@\tIN\tAAAA\t%s\n" % machine.ipv6()
404
                        ligne = ligne.encode('utf-8')
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
405 406 407
                        direct[alias]= direct.get(alias, "") + ligne
                    if alias in self.zones_v4_to_v6:
                        ligne = "@\tIN\tAAAA\t%s\n" % machine.ipv6()
408
                        ligne = ligne.encode('utf-8')
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
409 410
                        zone6 = self.zones_v4_to_v6[alias]
                        direct[zone6] = direct.get(zone6, '') + ligne
bernat's avatar
bernat committed
411
                    continue
412

bernat's avatar
bernat committed
413 414 415 416 417 418 419 420 421 422 423 424
                # 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
425
                if not ok:
426
                    warnings += u'Alias ignoré (mid=%s) : %s\n' % ( machine.id().encode('utf-8'), alias.encode('utf-8') )
bernat's avatar
bernat committed
427
                    continue
428
                zone = zone.encode('utf-8')
bernat's avatar
bernat committed
429
                ligne = "%s\tIN\tCNAME\t%s.\n" % ( nom, machine.nom() )
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
430 431 432 433 434
                direct[zone] = direct.get(zone, '') + ligne
                if zone in self.zones_v4_to_v6:
                    zone6 = self.zones_v4_to_v6[zone]
                    ligne = "%s\tIN\tCNAME\t%s.\n" % ( nom, machine.nom6() )
                    direct[zone6] = direct.get(zone6, '') + ligne
435

bernat's avatar
bernat committed
436
            # Le reverse
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
437 438 439 440
            ip = machine.ip()
            net = AddrInNets(ip, self.zones_reverse)
            if net:
                base_ip = ip.split('.')
bernat's avatar
bernat committed
441
                base_ip.reverse()
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
442
                zone, length = self.reverse(net, ip)
443
                zone = zone.encode('utf-8')
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
444
                ligne = '%s\tIN\tPTR\t%s.\n' % ('.'.join(base_ip[:length]), machine.nom())
bernat's avatar
bernat committed
445 446
                try : reverse[zone] += ligne
                except : reverse[zone] = ligne
447
            elif self.verbose >= 2 or machine.nom() not in ('freebox.crans.org', 'ovh.crans.org', 'kokarde.crans.org'):
448
                warnings += u'Résolution inverse ignorée (mid=%s) : ip sur zone non autoritaire (%s)\n' % ( machine.id().encode('utf-8'), machine.ip().encode('utf-8') )
449

450
        ### Ajouts pour les fichiers de résolution directs
pauget's avatar
pauget committed
451
        for zone in direct.keys() :
bernat's avatar
bernat committed
452
            # MXs
453
            direct[zone] = MX + direct[zone]
454

455
        ### XXX: création de la zone inverse pour le /48 IPv6 complet du Cr@ns
Nicolas Dandrimont's avatar
Nicolas Dandrimont committed
456 457 458
        full_net_v6 = self.zones_v6_to_net["##HACK##"]
        zone_rev, length = self.reverse(full_net_v6, netaddr.IPNetwork(full_net_v6).first)
        reverse[zone_rev] = reverse.get(zone_rev, "")
459

460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
        ### Alias de zone
        zone_todo = [zone for zone in self.zone_alias]
        while zone_todo:
            for zone in zone_todo:
                for alias in self.zone_alias[zone]:
                    try:
                        direct[alias] = direct[zone]
                        zone_todo.remove(zone)
                    except KeyError:
                        pass
                    if alias in self.zones_v4_to_v6:
                        alias_v6=self.zones_v4_to_v6[alias]
                        zone_v6 = self.zones_v4_to_v6[zone]
                        direct[alias_v6] = direct[zone_v6]

        ### Ajout des parametres SPF
        direct['crans.org'] +='; Parametres SPF\n'
        direct['crans.org'] +='crans.org.\tIN\tTXT\t"v=spf1 a mx ?all"\n'
        for m in self.MXs:
            direct['crans.org'] +='%s.\tIN\tTXT\t"v=spf1 a ?all"\n' % m.split()[-1]
        direct['crans.org'] += '\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 mx ?all"\n\n'

        ### Ajout d'eventuels champs SRV
        direct['crans.org'] +='; Champs SRV\n'
        for s in self.SRVs:
            direct['crans.org'] += s + '\n'
        direct['crans.org'] += '\n'

491
        ### Ajout des délégations de zones
492 493 494
        for deleg in self.DELEG.keys():
            nom, zone = deleg.split('.',1)
            if not zone in direct.keys():
495
                warnings += u'Délégation ignorée %s : on ne génère pas la zone parent\n' % deleg
496 497 498
                continue
            for serv in self.DELEG[deleg]:
                direct[zone] = direct[zone] + "%s\tIN\tNS\t%s\n" % ( nom, serv )
499

500
        for zone in direct.keys():
501
            child, parent = zone.split('.',1)
502
            if not zone in self.DELEG.keys() and parent in self.zones_direct + [z for l in self.zone_alias.values() for z in l] + self.zones_v4_to_v6.values():
503
                for d in self.DNSs:
504 505
                    direct[parent] = direct.get(parent, "") + '%s\tIN\tNS %s.\n' % (child, d)

506

507
        ### Ajout d'eventuel champs DS pour les délégation dnssec
508 509 510 511
        for zone,ds in self.DS.items():
            for s in ds:
              direct[zone] += s + '\n'
            direct[zone] += '\n'
512

513
        ### Ecriture des fichiers de zone et préparation du fichier de définition
bernat's avatar
bernat committed
514 515
        f = ''
        for zone, lignes in direct.items() + reverse.items() :
516 517 518 519 520
            if zone in self.zones_dnssec:
                path = self.DNSSEC_DIR  + 'db.' + zone
            else:
                path = self.DNS_DIR  + 'db.' + zone
            file = self.DNS_DIR  + 'db.' + zone
bernat's avatar
bernat committed
521
            fd = self._open_conf(file,';')
pauget's avatar
pauget committed
522 523
            fd.write(self.zone_entete % \
            { 'zone' : zone, 'serveur_autoritaire' : self.DNSs[0] , 'serial' : serial } )
bernat's avatar
bernat committed
524 525 526 527
            fd.write('\n')
            fd.write(DNS)
            fd.write(lignes)
            fd.close()
528
            os.chmod(file,0664)
529
            if short_name(gethostname()) in map(short_name, dns.DNSs[1:]):
530
                f += self.zone_template_slave % {'NOM_zone': zone, 'FICHIER_zone': path,
531 532
                        'ip_master_DNS': self.ip_master_DNS}
            else:
533
                f += self.zone_template % { 'NOM_zone' : zone, 'FICHIER_zone' : path }
534

535
        ### Ecriture fichier de définition
bernat's avatar
bernat committed
536 537 538
        fd = self._open_conf(self.DNS_CONF,'//')
        fd.write(f)
        fd.close()
539

bernat's avatar
bernat committed
540
        return warnings
541

542

543
if __name__ == '__main__' :
544
    from config import bcfg2_main
545
    hostname = short_name(gethostname())
546
    if hostname == short_name(bcfg2_main):
547
        print "Reconfiguration du fichier de BCfg2 pour configurer le bind d'un serveur en esclave (pensez à lancer bcfg2 sur les esclaves)."
bernat's avatar
bernat committed
548 549
        c = dns()
        c.gen_slave()
550
        if hostname ==  short_name(dns.DNSs[0]):
551
            print "Ce serveur est également serveur maitre, mais la reconfiguration du DNS maitre se fait par generate."
552
    elif hostname == short_name(dns.DELEG['tv.crans.org'][0][0:-1]):
553
        print "Serveur maᅵtre pour tv.crans.org, génération de la zone"
554 555 556
        c = dns()
        c.gen_tv()
        import subprocess
557
        args="/usr/sbin/ods-signer sign tv.crans.org".split()
558
        p=subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
559 560 561 562
        ret=p.communicate()
        print ret[0].strip()
        print ret[1].strip()
        if hostname ==  short_name(dns.DNSs[0]):
563
            print "Ce serveur est également serveur maitre, mais la reconfiguration du DNS maitre se fait par generate."
564
    elif hostname ==  short_name(dns.DNSs[0]):
565
        print "Ce serveur est maître ! Utilisez generate."
566
    elif hostname in map(lambda fullhostname : short_name(fullhostname),dns.DNSs[1:]+dns.DNSs_private):
567
        print "Ce serveur est esclave! Lancez ce script sur %s, puis lancez bcfg2 ici" % bcfg2_main
568
    else:
569
        print "Ce serveur ne correspond à rien pour la configuration DNS."