#!/usr/bin/env python # Copyright (c) 2007, Secure64 Software Corporation # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # # # When named.conf changes, update the NSD machine # # #-- imports import getopt import os import os.path import popen2 import re import sys import time if os.path.exists('../bind2nsd/Config.py'): sys.path.append('../bind2nsd') from Config import * from NamedConf import * from NsdConf import * from Utils import * else: from bind2nsd.Config import * from bind2nsd.NamedConf import * from bind2nsd.NsdConf import * from bind2nsd.Utils import * if os.path.exists('../pexpect-2.1'): sys.path.append('../pexpect-2.1') import pexpect import pxssh #-- globals conf = Config() #-- useful functions def usage(): print 'nsd-sync %s, copyright(c) 2007, Secure64 Software Corporation' \ % (conf.getValue('version')) print print 'usage: nsd-sync [-a|--analyze-only] [-h|--help] [-s|--sync-only]' print ' [-n|--now]' print ' -a | --analyze-only => look for and report errors, but do' print ' not sync with the NSD server' print ' -h | --help => print this message and quit' print ' -n | --now => do not poll, sync immediately' print ' -s | --sync-only => sync without translating BIND files' print ' -v | --verbose => output lots of info' return def rebuild_nsd_files(): result = False xlate = conf.getValue('bind2nsd') if os.path.exists(xlate): result = run_cmd(xlate, 'running bind2nsd...') else: report_error('? could not find "%s" and have got to have it' % (xlate)) report_error(' skipping rebuild of NSD files') return result def scp_target(): #-- do the scp to an actual NSD server report_info('=> using scp to transfer to NSD system...') tmpdir = conf.getValue('tmpdir') # must have trailing '/' if not os.path.exists(tmpdir) and not os.path.isdir(tmpdir): bail('? cannot find "%s"...' % (tmpdir)) #-- this feels a bit dodgy due to issues in pexpect when it goes # up against passwd and other such nastiness from scp/ssh -- all # we should have to do is child.wait() really, but that does not # work as it should. # # NB: it turn out that you can _not_ put a '*' at the end of the # source path; pexpect.spawn() screws up and the parsing of the string # and ends up ignoring everything up to the '*', meaning the command # does not have the 'scp' part in it and does not get executed properly. # pwd = os.getcwd() os.chdir(tmpdir) flist = os.listdir('.') fnames = ' '.join(flist) cmd = 'scp -r ' + fnames cmd += ' ' + conf.getValue('destuser') + '@' cmd += conf.getValue('dest-ip') + ':' report_info('=> ' + cmd) child = pexpect.spawn(cmd) if len(conf.getValue('dnspw')) > 0: child.expect('.*ssword:') child.sendline(conf.getValue('dnspw')) child.expect('.*' + conf.getValue('nsd_conf') + '.*') child.expect(pexpect.EOF) child.close() os.chdir(pwd) return def cp_files(analyze): #-- we assume everything has already been copied to the tmpdir by bind2nsd if analyze: return tmpdir = conf.getValue('tmpdir') # must have trailing '/' if not os.path.exists(tmpdir) and not os.path.isdir(tmpdir): bail('? cannot find "%s"...' % (tmpdir)) #-- scp the entire tmp directory if conf.getValue('DEMO-MODE'): report_info('** scp would go here, but cp -r for demonstration purposes') cmd = 'cp -r ' + tmpdir + '* ' + conf.getValue('destdir') run_cmd(cmd, 'using cp to transfer to demo system...') else: scp_target() return def restart_nsd(): if conf.getValue('DEMO-MODE'): cmd = conf.getValue('stop_cmd') run_cmd(cmd, 'stopping nsd...') # BOZO: rebuild is not behaving when there are errors, so the hack is # to remove the existing db, run the zone compiler and restart nsd #cmd = conf.getValue('rebuild_cmd') #os.system(cmd) cmd = 'rm -f ' + conf.getValue('database') run_cmd(cmd, 'removing old zonedb...') cmd = conf.getValue('zonec_cmd') run_cmd(cmd, 'rebuilding zonedb...') cmd = conf.getValue('start_cmd') run_cmd(cmd, 'starting nsd...') else: cmd = 'ssh -a -x ' cmd += conf.getValue('destuser') + '@' + conf.getValue('dest-ip') child = pexpect.spawn(cmd) if not child.isalive(): bail('? cannot login to NSD system at %s' % \ (conf.getValue('dest-ip'))) else: report_info('=> restarting NSD on %s' % \ (conf.getValue('dest-ip'))) child.expect('.*ssword:') child.sendline(conf.getValue('syspw')) child.expect('# ') report_info('=> now logged in') report_info('=> issuing zonec') child.sendline(conf.getValue('zonec_cmd')) if isVerbose(): child.logfile = sys.stdout child.expect('# ') report_info('=> issuing stop') child.sendline(conf.getValue('stop_cmd')) child.expect('# ') report_info('=> issuing start') child.sendline(conf.getValue('start_cmd')) child.expect('# ') child.sendline('exit') child.close() report_info('=> restart done') return def quick_parse(): #-- build an in-core representation of the named.conf file named_root = conf.getValue('named_root') named_fname = conf.getValue('named_conf') report_info('=> parsing named.conf file \"%s\"...' % (named_fname)) pwd = os.getcwd() if os.path.exists(named_root) and os.path.isdir(named_root): os.chdir(named_root) else: bail('? cannot find the named root directory "%s"' % (named_root)) named = NamedConf(named_fname) os.chdir(pwd) return named def run_named_check(named): #-- run named-checkconf on the config file and then run named-checkzone # on each zone file chkconf = conf.getValue('named-checkconf') if os.path.exists(chkconf): fname = conf.getValue('named_root') fname += '/' + conf.getValue('named_conf') report_info('=> running "%s" on "%s"...' % (chkconf, fname)) (output, errors) = run_cmd_capture(chkconf + ' ' + fname) if len(errors) > 0: report_info('? errors found --->') report_info(errors) else: report_info(' all is well.') else: report_error("? wanted to run named-checkconf, dude, but it's not there.") chkzone = conf.getValue('named-checkzone') if os.path.exists(chkzone): zdict = named.getZones() zlist = zdict.keys() zlist.sort() rname = named.getOptions().getDirectory().replace('"','') report_info('=> running "%s" on all zones...' % (chkzone)) prog = re.compile(':[0-9][0-9]*:') for ii in zlist: zone = zdict[ii].getName() zfile = rname + '/' + zdict[ii].getFile() (output, errors) = run_cmd_capture(chkzone + ' ' + zone + ' ' + zfile) if len(output) > 0 and prog.search(output) != None: report_info(output.strip()) else: report_error("? wanted to run named-checkzone, dude, but it's not there.") return def run_zonec(): zonec = conf.getValue('zonec_cmd') if os.path.exists(zonec): report_info('=> running the zone compiler "%s"...' % (zonec)) fname = conf.getValue('nsd_conf') tmpdir = conf.getValue('tmpdir') cmd = zonec + ' -c ' + tmpdir + '/' + fname + ' -d ' + tmpdir cmd += ' -f ' + tmpdir + '/zone.db' os.system('rm -f ' + tmpdir + '/zone.db') (output, errors) = run_cmd_capture(cmd) if len(errors) > 0: report_info('? errors found --->') report_info(errors) else: report_info(' all is well.') else: report_error("? hmph. wanted to run zonec, but it's not there.") return #-- main --------------------------------------------------------------- def main(): try: opts, args = getopt.getopt(sys.argv[1:], 'ahnsv', ['analyze-only', 'help', 'now', 'sync-only', 'verbose'] ) except getopt.GetoptError: usage() sys.exit(1) now = False analyze_only = False sync_only = False for ii, val in opts: if ii in ('-a', '--analyze-only'): analyze_only = True if ii in ('-h', '--help'): usage() sys.exit(0) if ii in ('-n', '--now'): now = True if ii in ('-s', '--sync-only'): sync_only = True if ii in ('-v', '--verbose'): set_verbosity(True) last_stat = {} this_stat = {} #-- don't poll unless we need to... if now: rebuild_nsd_files() cp_files(analyze_only) restart_nsd() sys.exit(0) #-- ...and don't poll if we just need to sync up to the machine... if sync_only: cp_files(analyze_only) restart_nsd() sys.exit(0) #-- ...and don't poll if we're just checking things out... if analyze_only: #-- well, and do a couple of extra things, too set_verbosity(True) report_info( \ 'nsd-sync %s, copyright(c) 2007, Secure64 Software Corporation' \ % (conf.getValue('version'))) named = quick_parse() rebuild_nsd_files() run_named_check(named) cp_files(analyze_only) run_zonec() sys.exit(0) #-- apparently we need to poll... tmplist = conf.getValue('named_watchlist').split() watchlist = [] for ii in tmplist: watchlist.append(ii.strip()) while True: for ii in watchlist: if ii in last_stat.keys(): statinfo = os.stat(ii) this_stat[ii] = (statinfo.st_size, statinfo.st_mtime) (old_size, old_mtime) = last_stat[ii] (new_size, new_mtime) = this_stat[ii] if old_size != new_size or old_mtime != new_mtime: report_info('aha! "%s" has changed!' % (ii)) last_stat[ii] = (new_size, new_mtime) rebuild_nsd_files() cp_files(analyze_only) restart_nsd() else: statinfo = os.stat(ii) last_stat[ii] = (statinfo.st_size, statinfo.st_mtime) this_stat[ii] = last_stat[ii] time.sleep(int(conf.getValue('sleep_time'))) sys.exit(0) #-- just in case if __name__ == '__main__': main()