2018-02-05 06:03:08 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2018-02-05 06:17:16 +00:00
|
|
|
import difflib
|
|
|
|
import hashlib
|
2018-02-05 06:03:08 +00:00
|
|
|
import os
|
2018-02-05 06:17:16 +00:00
|
|
|
import os.path
|
|
|
|
import sys
|
2018-02-05 07:37:55 +00:00
|
|
|
import time
|
2018-02-05 06:03:08 +00:00
|
|
|
|
|
|
|
# Third-party imports
|
|
|
|
import dns.resolver
|
|
|
|
import dns.rdatatype
|
|
|
|
import jinja2
|
|
|
|
import yaml
|
|
|
|
|
|
|
|
DEFAULT_TTL = 3600
|
|
|
|
|
|
|
|
|
|
|
|
def default(d, key, default=None):
|
|
|
|
if key in d:
|
|
|
|
return d[key]
|
|
|
|
return default
|
|
|
|
|
|
|
|
|
2018-02-05 06:17:16 +00:00
|
|
|
def diff_files(file1, file2):
|
|
|
|
fromlines = []
|
|
|
|
tolines = []
|
|
|
|
if os.path.isfile(file1):
|
|
|
|
with open(file1) as ff:
|
|
|
|
fromlines = ff.readlines()
|
|
|
|
if os.path.isfile(file2):
|
|
|
|
with open(file2) as tf:
|
|
|
|
tolines = tf.readlines()
|
|
|
|
|
|
|
|
print(''.join(difflib.unified_diff(
|
|
|
|
fromlines, tolines, file1, file2)))
|
|
|
|
|
|
|
|
|
|
|
|
def hash_file(filename):
|
|
|
|
if not os.path.isfile(filename):
|
|
|
|
return ""
|
|
|
|
|
|
|
|
hasher = hashlib.sha1()
|
2018-02-05 07:37:55 +00:00
|
|
|
with open(filename, 'r') as afile:
|
|
|
|
lines = afile.readlines()
|
|
|
|
|
|
|
|
lines = map(replace_soa_line, lines)
|
|
|
|
|
|
|
|
hasher.update(''.join(lines).encode('utf-8'))
|
2018-02-05 06:17:16 +00:00
|
|
|
return hasher.hexdigest()
|
|
|
|
|
|
|
|
|
2018-02-05 07:37:55 +00:00
|
|
|
def replace_soa_line(line):
|
|
|
|
if 'SOA' in line:
|
|
|
|
return '; SOA line replaced'
|
|
|
|
return line
|
|
|
|
|
|
|
|
|
2018-02-05 06:03:08 +00:00
|
|
|
def resolve_alias(entry):
|
|
|
|
result = []
|
|
|
|
|
|
|
|
answers = []
|
|
|
|
answers.extend(dns.resolver.query(
|
|
|
|
entry['alias'], 'A', raise_on_no_answer=False))
|
|
|
|
answers.extend(dns.resolver.query(
|
|
|
|
entry['alias'], 'AAAA', raise_on_no_answer=False))
|
|
|
|
|
|
|
|
if len(answers) == 0:
|
|
|
|
raise Exception(
|
|
|
|
"Alias {} was not resolvable: No answers!".format(entry['alias']))
|
|
|
|
|
|
|
|
for rdata in answers:
|
|
|
|
new_entry = entry.copy()
|
|
|
|
del new_entry['alias']
|
|
|
|
new_entry['type'] = dns.rdatatype.to_text(rdata.rdtype)
|
|
|
|
new_entry['data'] = rdata.address
|
|
|
|
result.append(new_entry)
|
|
|
|
|
2018-02-05 06:34:59 +00:00
|
|
|
return sorted(result, key=lambda k: k['data'])
|
2018-02-05 06:03:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
def sanitize(entry):
|
|
|
|
result = []
|
|
|
|
|
|
|
|
if entry['name'] == '':
|
|
|
|
entry['name'] = '@'
|
|
|
|
|
|
|
|
if 'alias' in entry:
|
|
|
|
return resolve_alias(entry)
|
|
|
|
|
|
|
|
for rr in entry['records']:
|
|
|
|
new_entry = entry.copy()
|
|
|
|
del new_entry['records']
|
|
|
|
new_entry['data'] = rr
|
|
|
|
|
|
|
|
if new_entry['type'] == 'TXT' and new_entry['data'][0] != '"':
|
|
|
|
new_entry['data'] = '"{}"'.format(new_entry['data'])
|
|
|
|
|
|
|
|
result.append(new_entry)
|
|
|
|
|
2018-02-18 12:47:31 +00:00
|
|
|
result.sort(key=lambda k: k['data'])
|
|
|
|
|
2018-02-05 06:03:08 +00:00
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
def write_zone(zone, ttl, soa, nameserver, mailserver, entries):
|
2018-02-05 07:37:55 +00:00
|
|
|
soa['serial'] = int(time.time()) - 946681200 # 2000-01-01
|
|
|
|
|
2018-02-05 06:03:08 +00:00
|
|
|
tpl = jinja2.Template(open("zone_template.j2").read())
|
|
|
|
zone_content = tpl.render({
|
|
|
|
"zone": zone,
|
|
|
|
"ttl": ttl,
|
|
|
|
"soa": soa,
|
|
|
|
"nameserver": nameserver,
|
|
|
|
"mailserver": mailserver,
|
|
|
|
"entries": entries,
|
|
|
|
})
|
|
|
|
|
|
|
|
with open("zones/tmp.{}".format(zone), 'w') as zf:
|
|
|
|
zf.write(zone_content)
|
|
|
|
|
2018-02-05 06:17:16 +00:00
|
|
|
if hash_file("zones/tmp.{}".format(zone)) != hash_file("zones/db.{}".format(zone)):
|
|
|
|
print("Generated and replaced zone file for {}".format(zone))
|
|
|
|
diff_files("zones/db.{}".format(zone), "zones/tmp.{}".format(zone))
|
|
|
|
os.rename("zones/tmp.{}".format(zone), "zones/db.{}".format(zone))
|
2018-02-06 19:06:34 +00:00
|
|
|
else:
|
|
|
|
os.unlink("zones/tmp.{}".format(zone))
|
2018-02-05 06:03:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
zone_data = yaml.load(open("zones.yml"))
|
|
|
|
|
|
|
|
for zone, config in zone_data['zones'].items():
|
|
|
|
ttl = default(config, "default_ttl", DEFAULT_TTL)
|
|
|
|
|
|
|
|
entries = []
|
2018-02-06 19:05:06 +00:00
|
|
|
for entry in default(config, 'entries', []):
|
2018-02-05 06:03:08 +00:00
|
|
|
entries.extend(sanitize(entry))
|
|
|
|
|
2018-02-18 12:49:51 +00:00
|
|
|
mailserver = default(config, 'mailserver', {})
|
|
|
|
if 'mailserver_set' in config and config['mailserver_set'] in zone_data['mailserver_sets']:
|
|
|
|
mailserver = zone_data['mailserver_sets'][config['mailserver_set']]
|
|
|
|
|
2018-02-05 06:03:08 +00:00
|
|
|
write_zone(zone, ttl, zone_data['soa'],
|
2018-02-18 12:49:51 +00:00
|
|
|
zone_data['nameserver'], mailserver, entries)
|
2018-02-05 06:03:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|