347 lines
11 KiB
Python
347 lines
11 KiB
Python
#!/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()
|