#!/usr/bin/env python3
#
# Copyright (C) 2018 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#

import sys
import os
import time
import netifaces

from vyos.config import Config
from vyos.util import ConfigError

config_file = r'/etc/powerdns/recursor.conf'

# borrowed from: https://github.com/donjajo/py-world/blob/master/resolvconfReader.py, THX!
def get_resolvers(file):
    resolvers = []
    try:
        with open(file, 'r') as resolvconf:
            for line in resolvconf.readlines():
                line = line.split('#',1)[0];
                line = line.rstrip();
                if 'nameserver' in line:
                    resolvers.append(line.split()[1])
        return resolvers
    except IOError:
        return []

def get_config():
    dns = {}
    conf = Config()
    conf.set_level('service dns forwarding')
    if not conf.exists(''):
        return dns

    if conf.exists('cache-size'):
        cache = conf.return_value('cache-size')
        dns.setdefault('cache-size', cache)

    if conf.exists('dhcp'):
        dns.setdefault('dhcp', [])
        interfaces = []
        interfaces = conf.return_values('dhcp')
        for interface in interfaces:
            resolvers = get_resolvers("/etc/resolv.conf.dhclient-new-{0}".format(interface))
            if len(resolvers) > 0:
                dhcp = {
                    "interface": interface,
                    "resolvers": resolvers
                }
                dns['dhcp'].append(dhcp)

    if conf.exists('domain'):
        dns.setdefault('domain', [])
        for node in conf.list_nodes('domain'):
            server = conf.return_values("domain {0} server".format(node))
            domain = {
                "name": node,
                "server": server
            }
            dns['domain'].append(domain)

    if conf.exists('ignore-hosts-file'):
        dns.setdefault('ignore-hosts-file', True)

    if conf.exists('listen-on'):
        interfaces = []
        interfaces = conf.return_values('listen-on')
        dns.setdefault('listen-on', interfaces)

    if conf.exists('name-server'):
        nameservers = []
        nameservers = conf.return_values('name-server')
        dns.setdefault('name-server', nameservers)

    if conf.exists('system'):
        conf.set_level('system')
        nameservers = []
        nameservers = conf.return_values('name-server')
        if len(nameservers) == 0:
            print("DNS forwarding warning: No name-servers set under 'system name-server'\n")
        else:
            dns.setdefault('system-name-server', nameservers)

    return dns

def verify(dns):
    if len(dns) > 0:
        if 'listen-on' not in dns.keys():
            raise ConfigError('Error: DNS forwarding requires a configured listen interface!')

        for interface in dns['listen-on']:
            try:
                netifaces.ifaddresses(interface)[netifaces.AF_INET]
            except KeyError as e:
                raise ConfigError('Error: Interface {0} has no IP address assigned'.format(interface))

        if 'domain' in dns.keys():
            for domain in dns['domain']:
                if len(domain['server']) == 0:
                    raise ConfigError('Error: No server configured for domain {0}'.format(domain['name']))

    return None

def generate(dns):
    config_header = '### Autogenerated by vyos-config-dns-forwarding.py on {tm} ###\n'.format(tm=time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime()))

    fwds = []
    fwds_src = []

    # write new configuration file
    f = open(config_file, 'w')
    f.write(config_header)
    f.write('daemon=yes\n')
    f.write('threads=1\n')
    f.write('allow-from=0.0.0.0/0\n')
    f.write('log-common-errors=yes\n')

    if 'listen-on' in dns.keys():
        listen4 = []
        listen6 = []
        for interface in dns['listen-on']:
            addrs = netifaces.ifaddresses(interface)
            for ip4 in addrs[netifaces.AF_INET]:
                listen4.append(ip4['addr'])

            for ip6 in addrs[netifaces.AF_INET6]:
                listen6.append(ip6['addr'])

        # build local-address string, example:
        # local-address=172.16.37.240, 127.0.0.1, 172.16.254.35, 172.16.77.1, 2001:DB8::1:25, ::1
        f.write('local-address=' + ', '.join(listen4) + ', ' + ', '.join(listen6) + '\n')

    if 'cache-size' in dns.keys():
        f.write("max-cache-entries={0}\n".format(dns['cache-size']))

    if 'dhcp' in dns.keys():
        for dhcp in dns['dhcp']:
            fwds_src.append('DHCP: {0} ({1})'.format(dhcp['interface'], ', '.join(str(ns) for ns in dhcp['resolvers'])))
            fwds.append(', '.join(str(ns) for ns in dhcp['resolvers']))

    if 'domain' in dns.keys():
        zones = []
        for domain in dns['domain']:
            zones.append('{0}={1}'.format(domain['name'], ';'.join(str(ns) for ns in domain['server'])))
        f.write('forward-zones=' + ', '.join(zones) + '\n')

    if 'ignore-hosts-file' in dns.keys():
        f.write("export-etc-hosts=no\n")
    else:
        f.write("export-etc-hosts=yes\n")

    if 'name-server' in dns.keys():
        fwds_src.append('statically configured: {0}'.format(', '.join(str(ns) for ns in dns['name-server'])))
        fwds.append(', '.join(str(ns) for ns in dns['name-server']))

    if 'system-name-server' in dns.keys():
        fwds_src.append('system: {0}'.format(', '.join(str(ns) for ns in dns['system-name-server'])))
        fwds.append(', '.join(str(ns) for ns in dns['system-name-server']))

    if len(fwds) > 0:
        f.write('\n')
        f.write('# ' + '\n# '.join(fwds_src) + '\n')
        f.write('forward-zones-recurse=.=' + '; '.join(fwds) + '\n')

    f.close()
    return None

def apply(dns):
    if len(dns) == 0:
        cmd = "sudo systemctl stop pdns-recursor"
    else:
        cmd = "sudo systemctl restart pdns-recursor"

    os.system(cmd)
    return None

if __name__ == '__main__':
    try:
        c = get_config()
        verify(c)
        generate(c)
        apply(c)
    except ConfigError as e:
        print(e)
        sys.exit(1)
