NetBSD/external/bsd/nsd/dist/contrib/bind2nsd/scripts/nsd-sync

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()