#!/usr/bin/env python
#
# Copyright C 2001-2007 by Isilon Systems, Inc. All rights not expressly
# granted herein are reserved.
# Created in the United States of America.
# 
# This digital media is protected by U.S. and international copyright and
# intellectual property laws. Unless otherwise specified, all information
# and screens appearing as part of this digital medium, including
# software, services, documents, text, images, icons, and logos design;
# the selection, assembly, arrangement, and design thereof; and the code
# that enables its presentation, are the sole property of Isilon Systems,
# Inc.
# 
# Except as otherwise required by applicable law, no part of any
# copyrighted material may be reproduced, modified, translated,
# distributed, published, or transmitted by any means, electronic or
# mechanical, for any purpose, without the prior written consent of Isilon
# Systems. 
#  
# Isilon and Isilon Systems are registered trademarks of Isilon Systems,
# Inc.  Legion is a trademark of Isilon Systems, Inc.  All other
# trademarks and registered trademarks are the property of their
# respective holders.
#
# $Id: flxwizard.py 79858 2007-08-21 21:55:29Z svanwoudenberg $

import os
import posix
import sys
import syslog
import traceback

import isi.net.flexnet as flx

syslog.openlog("isi_flexnet_d")

RESOLVCONF = "/etc/resolv.conf"
RESOLVCONFTMP = "/etc/resolv.conf.tmp"
DNSCHANGESCRIPT = "/etc/ifs/scripts/resolverchange.sh"
NSSWITCHCONF = "/etc/nsswitch.conf"
NSSWITCHCONFTMP = "/etc/nsswitch.conf.tmp"
HOSTCONF = "/etc/host.conf"
NSSWITCHRC = "/etc/rc.d/nsswitch start"

SEARCH_KWD = "search"
SERVER_KWD = "nameserver"
OPTION_KWD = "options"

def processResolvConf(domains, servers, options):
    rdcDirty = False
    counts = {
        SEARCH_KWD: [domains[:flx.FLX_MAX_DNS_DOMAINS], 0],
        SERVER_KWD: [servers[:flx.FLX_MAX_DNS_SERVERS], 0],
        OPTION_KWD: [options, 0],
    }

    try:
        oldrdc = file(RESOLVCONF, "r")
    except IOError:
        oldrdc = None
    newrdc = list()

    while oldrdc is not None:
        oldLine = oldrdc.readline()
        if oldLine == "":
            break

        # pass through any lines we don't own
        if not (oldLine[0:len(SEARCH_KWD)] == SEARCH_KWD or
                oldLine[0:len(SERVER_KWD)] == SERVER_KWD or
                oldLine[0:len(OPTION_KWD)] == OPTION_KWD):
            newrdc.append(oldLine)
            continue

        newLine = None
        keyval = oldLine.split(None, 1)
        if len(keyval) != 2:
            # Badly formatted line; discard.
            rdcDirty = True
            continue

        key, val = keyval
        if key == SEARCH_KWD:
            # 1 search line with up to 6 search domains
            if counts[key][1] == 0 and counts[key][0]:
                newLine = "%s\t%s\n" % (key, " ".join(counts[key][0]))
                counts[key][1] += 1

        elif key in [SERVER_KWD, OPTION_KWD]:
            # up to FLX_MAX_DNS_SERVERS nameservers, currently 3 as that's all 
            # libc will pay attention to (MAXNS in /usr/include/resolv.h), 
            # one per keyword/line

            # only accept a single server/option per line
            if len(val.split()) != 1:
                rdcDirty = True
                continue

            (values, count) = counts[key]
            if len(values) > count:
                newLine = "%s\t%s\n" % (key, values[count])
                counts[key][1] += 1


        # if newLine is still None, we're removing a line from the old
        # file. otherwise, it will be either a dup or a changed line.
        if newLine is None:
            # line was removed
            rdcDirty = True
        else:
            newrdc.append(newLine)
            if newLine != oldLine:
                rdcDirty = True

    # cover cases where info is added, e.g. this is a new file, a domain
    # was added, or a primary or secondary dns was added.

    key = SEARCH_KWD
    if counts[key][0] and counts[key][1] == 0:
        newrdc.append("%s\t%s\n" % (key, " ".join(counts[key][0])))
        rdcDirty = True

    for key in [SERVER_KWD, OPTION_KWD]:
        for i in range(counts[key][1], len(counts[key][0])):
            newrdc.append("%s\t%s\n" % (key, counts[key][0][i]))
            rdcDirty = True

    if oldrdc is not None:
        oldrdc.close()

    # Only create the temp file if necessary
    if rdcDirty == True:
        tmpfile = file(RESOLVCONFTMP, "w")
        for line in newrdc:
            tmpfile.write(line)
        tmpfile.close()

    return rdcDirty

def processNsswitchConf(dnsEnabled):
    onsf = file(NSSWITCHCONF, "r")
    nnsf = list()
    done = 0
    nssDirty = False
    while not done:
        nssLine = onsf.readline()

        if nssLine == "":
            done = 1
            continue

        if nssLine[0:6] != "hosts:":
            nnsf.append(nssLine)
            continue

        if dnsEnabled == 0:
            chgLine = nssLine.replace("dns", "")
        else:
            if nssLine.count("dns") == 0:
                chgLine = nssLine.replace("files", "files dns")
            else:
                chgLine = nssLine

        nnsf.append(chgLine)

        # keep track of whether we've actually changed anything
        if chgLine != nssLine:
            nssDirty = True

    onsf.close()
   
    if nssDirty == True:
        tmpfile = file(NSSWITCHCONFTMP, "w")
        for line in nnsf:
            tmpfile.write(line)
        nnsf.close()

    return nssDirty

def setDnsInfo(domains, servers, options):
    rdcDirty = False
    if len(servers) == 0:
        # No DNS servers means we are deleting the DNS info
        syslog.syslog(syslog.LOG_INFO, "removing DNS settings")
        if os.path.exists(RESOLVCONF):
            os.remove(RESOLVCONF)
        dnsON = 0
    else:
        # We assume we own this file, wipe if it doesn't look right
        syslog.syslog(syslog.LOG_DEBUG, "evaluating DNS settings")
        dnsON = 1
        rdcDirty = processResolvConf(domains, servers, options)

    nssDirty = processNsswitchConf(dnsON)

    # only run the scripts if anything has changed
    if nssDirty:
        syslog.syslog(syslog.LOG_INFO, "updating nsswitch and host info")
        os.rename(NSSWITCHCONFTMP, NSSWITCHCONF)
        os.remove(HOSTCONF);
        os.system(NSSWITCHRC);
    if rdcDirty:
        syslog.syslog(syslog.LOG_INFO, "updating dns info")
        if dnsON:
            os.rename(RESOLVCONFTMP, RESOLVCONF)
        rc = os.system(DNSCHANGESCRIPT)
        if rc != 0:
            syslog.syslog(syslog.LOG_ERR, "error executing DNS change "
                    "script (0x%X)" % rc)

    # remove any temporary files we may have created
    try:
        os.remove(RESOLVCONFTMP)
    except:
        pass
    try:
        os.remove(NSSWITCHCONFTMP)
    except:
        pass

def main(argv):
    if len(argv) < 1 or argv[0] != '-f':
        print "%s: For internal use only." % sys.argv[0]
        return posix.EX_USAGE

    i = argv.index(",")
    domains = argv[1:i]
    argv = argv[i+1:]

    try:
        i = argv.index(",")
        servers = argv[0:i]
        options = argv[i+1:]
    except ValueError:
        servers = argv
        options = []

    try:
        setDnsInfo(domains, servers, options)
    except Exception, e:
        syslog.syslog(syslog.LOG_ERR, "%s caught '%s'; traceback:\n%s" % \
                (sys.argv[0], sys.exc_type,
                 "".join(traceback.format_list(traceback.extract_tb(
                         sys.exc_traceback)))))
        return posix.EX_SOFTWARE

    return posix.EX_OK

#
# Start running here
#
if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))
