diff --git a/main.py b/main.py index c764821..e4acf5d 100755 --- a/main.py +++ b/main.py @@ -2,6 +2,7 @@ from configparser import ConfigParser import socket import datetime +import netaddr from re2oapi import Re2oAPIClient @@ -55,6 +56,8 @@ template_reverse = ("$TTL 2D\n" "\n" "{ns_records}\n" "\n" + "{mx_records}\n" + "\n" "{ptr_records}\n") def write_dns_files(api_client): @@ -149,11 +152,135 @@ def write_dns_files(api_client): with open(filename, 'w+') as f: f.write(zone_file_content) +def network_to_arpanets(nets): + """En ipv4, on ne pouvait définir + que des plages reverse en /24, /16 ou /8. Cette fonction vise à retourner + une liste des plages en tenant compte de ce critère (donc de taille + 32/24/16/8) + + Ne touche à rien pour l'IPv6. + """ + subnets = [] + for net in nets: + if not isinstance(net, netaddr.IPNetwork): + net = netaddr.IPNetwork(net) + # on fragmente les subnets + # dans les tailles qui vont bien. + if net.prefixlen > 24: + subnets.extend(net.subnet(32)) + elif net.prefixlen > 16: + subnets.extend(net.subnet(24)) + elif net.prefixlen > 8: + subnets.extend(net.subnet(16)) + else: + subnets.extend(net.subnet(8)) + return subnets + + +def get_arpa(cidr): + """ + Renvoie la zone DNS inverse correspondant au réseau donné. + """ + net = netaddr.IPNetwork(cidr) + byte_size = 8 if net.version == 4 else 4 + addr_bytes = 4 if net.version == 4 else 32 + net_class = max(((net.prefixlen - 1) // byte_size) + 1, 1) + return ".".join( + netaddr.IPAddress(net.first).reverse_dns.split('.')[addr_bytes - net_class:] + ) + +def get_ip_reverse(ip, prefix_length): + ip = netaddr.IPAddress(ip) + return '.'.join(ip.reverse_dns.split('.')[:prefix_length]) + + def write_dns_reverse_file(api_client): - pass - + for zone in api_client.list("dns/reverse-zones"): + now = datetime.datetime.now(datetime.timezone.utc) + serial = now.strftime("%Y%m%d") + str(int(100*(now.hour*3600 + now.minute*60 + now.second)/86400)) + + extension = zone['extension'] + soa_mail_fields = zone['soa']['mail'].split('@') + soa_mail = "{}.{}.".format(soa_mail_fields[0].replace('.', '\\.'), + soa_mail_fields[1]) + ns_records = "\n".join( + template_ns.format(target=x['target']) + for x in zone['ns_records'] + ) + + mx_records = "\n".join( + template_mx.format(priority=x['priority'], + target=x['target']) + for x in zone['mx_records'] + ) + + ### Start with ipv4 reverse + net = network_to_arpanets(zone['cidrs'])[0] + # Prend la première adresse ip de la plage, sauf si une est fournie + _address = netaddr.IPAddress(net.first) + # retourne le reverse splitté. (un reverse ressemble à 0.136.231.138.in-addr.arpa.) + rev_dns_a = _address.reverse_dns.split('.')[:-1] + + # En v4, le reverse étant de la forme 0.136.231.138.in-addr.arpa., soit + # on a un /8, soit un /16, soit un /24. + if net.prefixlen == 8: + zone_name,prefix_length = ('.'.join(rev_dns_a[3:]), 3) + elif net.prefixlen == 16: + zone_name,prefix_length = ('.'.join(rev_dns_a[2:]), 2) + elif net.prefixlen == 24: + zone_name,prefix_length = ('.'.join(rev_dns_a[1:]), 1) + + soa = template_soa.format(zone=zone_name, + mail=soa_mail, + serial=serial, + refresh=zone['soa']['refresh'], + retry=zone['soa']['retry'], + expire=zone['soa']['expire'], + ttl=zone['soa']['ttl']) + ptr_records = "\n".join( + template_ptr.format(hostname=host['hostname']+extension, + target=get_ip_reverse(host['ipv4'],prefix_length)) + for host in zone['ptr_records'] if host['ipv4'] + ) + zone_file_content = template_reverse.format(soa=soa, + ns_records=ns_records, + mx_records=mx_records, + ptr_records = ptr_records) + + filename = 'dns.{zone}zone'.format(zone=zone_name) + with open(filename, 'w+') as f: + f.write(zone_file_content) + + ### Continue with the ipv6 reverse + # hack because we do not have the /64 info + zone6_name = get_arpa(zone['prefix_v6']+"/64") + soa = template_soa.format(zone=zone6_name, + mail=soa_mail, + serial=serial, + refresh=zone['soa']['refresh'], + retry=zone['soa']['retry'], + expire=zone['soa']['expire'], + ttl=zone['soa']['ttl']) + + net = netaddr.IPNetwork(zone['prefix_v6']+"/64") + prefix_length = int((128 - net.prefixlen)/4) + ptr_records = "\n".join( + template_ptr.format(hostname=host['hostname']+extension, + target=get_ip_reverse(ip['ipv6'],prefix_length)) + for host in zone['ptr_v6_records'] for ip in host['ipv6'] + ) + + zone_file_content = template_reverse.format(soa=soa, + ns_records=ns_records, + mx_records=mx_records, + ptr_records = ptr_records) + + + filename = 'dns.{zone}zone'.format(zone=zone6_name) + with open(filename, 'w+') as f: + f.write(zone_file_content) api_client = Re2oAPIClient(api_hostname, api_username, api_password) @@ -163,6 +290,6 @@ for service in api_client.list("services/regen/"): # if service['hostname'] == client_hostname and \ # service['service_name'] == 'dns' and \ # service['need_regen']: - write_dns_files(api_client) - write_dns_reverse_file(api_client) + #write_dns_files(api_client) + write_dns_reverse_file(api_client) # api_client.patch(service['api_url'], data={'need_regen': False})