#!/usr/bin/python3
# ------------------------------------------------------------------------------
# mk-named-dhcpd-conf.py
# ======================
#
# Scope Native
# Copyright (C) 2022 by RaySoft, Zurich, Switzerland
# License GNU General Public License (GPL) 2.0
# https://www.gnu.org/licenses/gpl2.txt
#
# ------------------------------------------------------------------------------
DNS_FILE = 'named-zones.conf'
DHCP_FILE = 'dhcpd-hosts.conf'
# ------------------------------------------------------------------------------
PROGRAM_NAME = 'mk-named-dhcpd-conf'
PROGRAM_VERSION = '0.1'
# ------------------------------------------------------------------------------
import argparse
from datetime import datetime
import json
import os
import os.path
import re
import sys
# ------------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description='Generate the BIND and ISC DHCP configuration based on '
'a JSON file.', prog=PROGRAM_NAME,
)
parser.add_argument(
'-V', '--version', action='version',
version='%(prog)s {!s}'.format(PROGRAM_VERSION),
)
group1 = parser.add_mutually_exclusive_group()
group1.add_argument(
'-a', '--append', action='store_true',
help='append if a needed file already exists',
)
group1.add_argument(
'-o', '--overwrite', action='store_true',
help='overwrite existing files',
)
parser.add_argument(
'-p', '--path', metavar='PATH', dest='path', action='store',
default=os.getcwd(), help="proceed in path (default is '.')",
)
parser.add_argument(
'file', metavar='FILE', action='store', nargs=1,
type=argparse.FileType(mode='r'), help='JSON file',
)
args = parser.parse_args()
if not os.path.isdir(args.path):
print("error finding directory '{!s}'!".format(args.path),
file=sys.stderr)
parser.print_usage()
sys.exit(1)
try:
json_data = json.load(args.file[0])
except (TypeError, ValueError, OverflowError) as error:
print('error parsing json file: {!s}'.format(error), file=sys.stderr)
try:
self = json_data['self']
except KeyError as error:
print('error getting self: {!s}'.format(error), file=sys.stderr)
try:
soa = json_data['soa']
except KeyError as error:
print('error getting soa: {!s}'.format(error), file=sys.stderr)
try:
zones = json_data['zones']
except KeyError as error:
print('error getting zones: {!s}'.format(error), file=sys.stderr)
try:
servers = json_data['servers']
except KeyError as error:
print('error getting ns & mx: {!s}'.format(error), file=sys.stderr)
try:
hosts = json_data['hosts']
except KeyError as error:
print('error getting hosts', file=sys.stderr)
for zone in ['forward', 'reverse']:
if not zones[zone]:
print('error getting {!s} zone'.format(zone), file=sys.stderr)
soa['serial'] = datetime.now().strftime('%Y%m%d01')
zones['forward']['origin'] = '{!s}.'.format(zones['forward']['name'])
zones['forward']['file'] = '{!s}.zone'.format(zones['forward']['name'])
zones['reverse']['origin'] = '.'.join(zones['reverse']['name']
.split('.')[::-1]) \
+ '.in-addr.arpa.'
zones['reverse']['file'] = '{!s}.zone'.format(zones['reverse']['name'])
configs = {
'dns': {},
'dhcp': {},
}
configs['dns']['file'] = DNS_FILE
configs['dhcp']['file'] = DHCP_FILE
for obj in [zones['forward'], zones['reverse'], configs['dns'],
configs['dhcp']]:
if args.overwrite:
obj['file-mode'] = 'w'
elif args.append:
obj['file-mode'] = 'a'
else:
if os.path.isfile(obj['file']):
print('warning: file already exists: {!s}'.format(obj['file']))
answer = input('[o]verwrite or [a]ppend: ')
if answer == 'o':
obj['file-mode'] = 'w'
elif answer == 'a':
obj['file-mode'] = 'a'
else:
print('error parsing answer: {!s}'.format(answer))
sys.exit(1)
else:
obj['file-mode'] = 'w'
with open(zones['forward']['file'], zones['forward']['file-mode']) \
as zones['forward']['fh'], \
open(zones['reverse']['file'], zones['reverse']['file-mode']) \
as zones['reverse']['fh'], \
open(configs['dns']['file'], configs['dns']['file-mode']) \
as configs['dns']['fh'], \
open(configs['dhcp']['file'], configs['dhcp']['file-mode']) \
as configs['dhcp']['fh']:
for config in configs.keys():
if configs[config]['file-mode'] == 'w':
write_header(configs[config], self, '#')
for zone in sorted(zones.keys()):
if zones[zone]['file-mode'] == 'w':
write_header(zones[zone], self, ';')
write_bind_var(zones[zone], 'origin')
write_bind_var(zones[zone], 'ttl')
write_bind_soa(zones[zone], soa)
write_bind_ns(zones[zone], servers['dns'])
write_bind_zone_config(configs['dns'], zones[zone])
if 'mail' in servers and zones[zone]['file-mode'] == 'w':
write_bind_mx(zones['forward'], servers['mail'])
for host in sorted(hosts.keys()):
hosts[host]['host'] = host
if host:
hosts[host]['fqdn'] = '{!s}.{!s}' \
.format(host, zones['forward']['origin'])
else:
hosts[host]['fqdn'] = zones['forward']['origin']
write_bind_forward_records(zones['forward'], hosts[host])
write_bind_reverse_records(zones['reverse'], hosts[host],
zones['forward']['origin'])
if 'mac' in hosts[host]:
write_dhcp_host_config(configs['dhcp'], hosts[host])
# ------------------------------------------------------------------------------
def write_header(obj, self, mark):
obj['fh'].write(
'{!s}{!s}\n'.format(mark, '-' * 79)
+ '{!s} {!s}\n'.format(mark, obj['file'])
+ '{!s} {!s}\n'.format(mark, '=' * len(obj['file']))
+ '{!s}\n'.format(mark)
+ '{!s} {:<9s} {!s}\n'.format(mark, 'Project', self['project'])
+ '{!s} {:<9s} {!s}\n'.format(mark, 'Scope', self['scope'])
+ '{!s} {:<9s} {!s}\n'.format(mark, 'Copyright', self['copyright'])
+ '{!s} {:<9s} {!s}\n'.format(mark, 'License', self['license'])
+ '{!s} {:<9s} {!s}\n'.format(mark, '', self['license_url'])
+ '{!s}\n'.format(mark)
+ '{!s}{!s}\n'.format(mark, '-' * 79)
+ '\n'
)
# ------------------------------------------------------------------------------
def write_bind_var(zone, var):
zone['fh'].write('${!s} {!s}\n'.format(var.upper(), zone[var]))
# ------------------------------------------------------------------------------
def write_bind_soa(zone, soa):
zone['fh'].write(
'\n'
+ '{:<11s} {:<11s} {!s} {!s} (\n'
.format('@', 'IN SOA', soa['master'], soa['admin'])
+ '{:<23s} {!s}\n'.format('', soa['serial'])
+ '{:<23s} {:d}\n'.format('', soa['refresh'])
+ '{:<23s} {:d}\n'.format('', soa['retry'])
+ '{:<23s} {:d}\n'.format('', soa['expire'])
+ '{:<23s} {:d} )\n'.format('', soa['ttl'])
+ '\n'
)
# ------------------------------------------------------------------------------
def write_bind_ns(zone, ns):
layout = '{:<11s} {:<11s} {!s}\n'
for server in sorted(ns):
zone['fh'].write(layout.format('', 'IN NS', server))
zone['fh'].write('\n')
# ------------------------------------------------------------------------------
def write_bind_mx(zone, mx):
layout = '{:<11s} {!s} {:<4d} {!s}\n'
for server in sorted(mx.keys()):
zone['fh'].write(layout.format('', 'IN MX', mx[server], server))
zone['fh'].write('\n')
# ------------------------------------------------------------------------------
def write_bind_forward_records(zone, host):
layout = '{:<11s} {:<11s} {!s}\n'
zone['fh'].write(layout.format(host['host'], 'IN A', host['ip']))
if not host['host']:
host['host'] = zone['origin']
for name in sorted(host['alias']):
if name in ['ns1', 'ns2', 'mx1', 'mx2', 'ntp']:
zone['fh'].write(layout.format(name, 'IN A', host['ip']))
else:
zone['fh'].write(layout.format(name, 'IN CNAME', host['host']))
zone['fh'].write('\n')
# ------------------------------------------------------------------------------
def write_bind_reverse_records(zone, host, origin):
layout = '{:<11s} {:<11s} {!s}\n'
ip_part = host['ip'].replace(zone['name'] + '.', '')
regex = re.compile('^mx.$')
zone['fh'].write(layout.format(ip_part, 'IN PTR', host['fqdn']))
for alias in host['alias']:
if regex.match(alias):
zone['fh'].write(layout.format(ip_part, 'IN PTR',
alias + '.' + origin))
zone['fh'].write('\n')
# ------------------------------------------------------------------------------
def write_bind_zone_config(config, zone):
config['fh'].write(
'zone {!s} IN {{\n'.format(zone['origin'])
+ ' type master;\n'
+ ' file "pri/{!s}";\n'.format(zone['file'])
+ '};\n'
+ '\n'
)
# ------------------------------------------------------------------------------
def write_dhcp_host_config(config, host):
config['fh'].write(
'host {!s} {{\n'.format(host['fqdn'])
+ ' hardware ethernet {!s};\n'.format(host['mac'])
+ ' fixed-address {!s};\n'.format(host['ip'])
+ '}\n'
+ '\n'
)
# ------------------------------------------------------------------------------
if __name__ == '__main__':
main()
sys.exit(0)