1*d83a80eeSchristos#!/usr/bin/env python 2*d83a80eeSchristos# Copyright (c) 2007, Secure64 Software Corporation 3*d83a80eeSchristos# 4*d83a80eeSchristos# Permission is hereby granted, free of charge, to any person obtaining a copy 5*d83a80eeSchristos# of this software and associated documentation files (the "Software"), to deal 6*d83a80eeSchristos# in the Software without restriction, including without limitation the rights 7*d83a80eeSchristos# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8*d83a80eeSchristos# copies of the Software, and to permit persons to whom the Software is 9*d83a80eeSchristos# furnished to do so, subject to the following conditions: 10*d83a80eeSchristos# 11*d83a80eeSchristos# The above copyright notice and this permission notice shall be included in 12*d83a80eeSchristos# all copies or substantial portions of the Software. 13*d83a80eeSchristos# 14*d83a80eeSchristos# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15*d83a80eeSchristos# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16*d83a80eeSchristos# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17*d83a80eeSchristos# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18*d83a80eeSchristos# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19*d83a80eeSchristos# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20*d83a80eeSchristos# THE SOFTWARE. 21*d83a80eeSchristos# 22*d83a80eeSchristos# 23*d83a80eeSchristos# 24*d83a80eeSchristos# When named.conf changes, update the NSD machine 25*d83a80eeSchristos# 26*d83a80eeSchristos# 27*d83a80eeSchristos 28*d83a80eeSchristos#-- imports 29*d83a80eeSchristosimport getopt 30*d83a80eeSchristosimport os 31*d83a80eeSchristosimport os.path 32*d83a80eeSchristosimport popen2 33*d83a80eeSchristosimport re 34*d83a80eeSchristosimport sys 35*d83a80eeSchristosimport time 36*d83a80eeSchristos 37*d83a80eeSchristosif os.path.exists('../bind2nsd/Config.py'): 38*d83a80eeSchristos sys.path.append('../bind2nsd') 39*d83a80eeSchristos from Config import * 40*d83a80eeSchristos from NamedConf import * 41*d83a80eeSchristos from NsdConf import * 42*d83a80eeSchristos from Utils import * 43*d83a80eeSchristoselse: 44*d83a80eeSchristos from bind2nsd.Config import * 45*d83a80eeSchristos from bind2nsd.NamedConf import * 46*d83a80eeSchristos from bind2nsd.NsdConf import * 47*d83a80eeSchristos from bind2nsd.Utils import * 48*d83a80eeSchristos 49*d83a80eeSchristosif os.path.exists('../pexpect-2.1'): 50*d83a80eeSchristos sys.path.append('../pexpect-2.1') 51*d83a80eeSchristosimport pexpect 52*d83a80eeSchristosimport pxssh 53*d83a80eeSchristos 54*d83a80eeSchristos#-- globals 55*d83a80eeSchristosconf = Config() 56*d83a80eeSchristos 57*d83a80eeSchristos#-- useful functions 58*d83a80eeSchristosdef usage(): 59*d83a80eeSchristos print 'nsd-sync %s, copyright(c) 2007, Secure64 Software Corporation' \ 60*d83a80eeSchristos % (conf.getValue('version')) 61*d83a80eeSchristos print 62*d83a80eeSchristos print 'usage: nsd-sync [-a|--analyze-only] [-h|--help] [-s|--sync-only]' 63*d83a80eeSchristos print ' [-n|--now]' 64*d83a80eeSchristos print ' -a | --analyze-only => look for and report errors, but do' 65*d83a80eeSchristos print ' not sync with the NSD server' 66*d83a80eeSchristos print ' -h | --help => print this message and quit' 67*d83a80eeSchristos print ' -n | --now => do not poll, sync immediately' 68*d83a80eeSchristos print ' -s | --sync-only => sync without translating BIND files' 69*d83a80eeSchristos print ' -v | --verbose => output lots of info' 70*d83a80eeSchristos return 71*d83a80eeSchristos 72*d83a80eeSchristosdef rebuild_nsd_files(): 73*d83a80eeSchristos result = False 74*d83a80eeSchristos xlate = conf.getValue('bind2nsd') 75*d83a80eeSchristos if os.path.exists(xlate): 76*d83a80eeSchristos result = run_cmd(xlate, 'running bind2nsd...') 77*d83a80eeSchristos else: 78*d83a80eeSchristos report_error('? could not find "%s" and have got to have it' % (xlate)) 79*d83a80eeSchristos report_error(' skipping rebuild of NSD files') 80*d83a80eeSchristos return result 81*d83a80eeSchristos 82*d83a80eeSchristosdef scp_target(): 83*d83a80eeSchristos #-- do the scp to an actual NSD server 84*d83a80eeSchristos report_info('=> using scp to transfer to NSD system...') 85*d83a80eeSchristos tmpdir = conf.getValue('tmpdir') # must have trailing '/' 86*d83a80eeSchristos if not os.path.exists(tmpdir) and not os.path.isdir(tmpdir): 87*d83a80eeSchristos bail('? cannot find "%s"...' % (tmpdir)) 88*d83a80eeSchristos 89*d83a80eeSchristos #-- this feels a bit dodgy due to issues in pexpect when it goes 90*d83a80eeSchristos # up against passwd and other such nastiness from scp/ssh -- all 91*d83a80eeSchristos # we should have to do is child.wait() really, but that does not 92*d83a80eeSchristos # work as it should. 93*d83a80eeSchristos # 94*d83a80eeSchristos # NB: it turn out that you can _not_ put a '*' at the end of the 95*d83a80eeSchristos # source path; pexpect.spawn() screws up and the parsing of the string 96*d83a80eeSchristos # and ends up ignoring everything up to the '*', meaning the command 97*d83a80eeSchristos # does not have the 'scp' part in it and does not get executed properly. 98*d83a80eeSchristos # 99*d83a80eeSchristos pwd = os.getcwd() 100*d83a80eeSchristos os.chdir(tmpdir) 101*d83a80eeSchristos flist = os.listdir('.') 102*d83a80eeSchristos fnames = ' '.join(flist) 103*d83a80eeSchristos cmd = 'scp -r ' + fnames 104*d83a80eeSchristos cmd += ' ' + conf.getValue('destuser') + '@' 105*d83a80eeSchristos cmd += conf.getValue('dest-ip') + ':' 106*d83a80eeSchristos report_info('=> ' + cmd) 107*d83a80eeSchristos child = pexpect.spawn(cmd) 108*d83a80eeSchristos if len(conf.getValue('dnspw')) > 0: 109*d83a80eeSchristos child.expect('.*ssword:') 110*d83a80eeSchristos child.sendline(conf.getValue('dnspw')) 111*d83a80eeSchristos child.expect('.*' + conf.getValue('nsd_conf') + '.*') 112*d83a80eeSchristos child.expect(pexpect.EOF) 113*d83a80eeSchristos child.close() 114*d83a80eeSchristos os.chdir(pwd) 115*d83a80eeSchristos 116*d83a80eeSchristos return 117*d83a80eeSchristos 118*d83a80eeSchristosdef cp_files(analyze): 119*d83a80eeSchristos #-- we assume everything has already been copied to the tmpdir by bind2nsd 120*d83a80eeSchristos 121*d83a80eeSchristos if analyze: 122*d83a80eeSchristos return 123*d83a80eeSchristos 124*d83a80eeSchristos tmpdir = conf.getValue('tmpdir') # must have trailing '/' 125*d83a80eeSchristos if not os.path.exists(tmpdir) and not os.path.isdir(tmpdir): 126*d83a80eeSchristos bail('? cannot find "%s"...' % (tmpdir)) 127*d83a80eeSchristos 128*d83a80eeSchristos #-- scp the entire tmp directory 129*d83a80eeSchristos if conf.getValue('DEMO-MODE'): 130*d83a80eeSchristos report_info('** scp would go here, but cp -r for demonstration purposes') 131*d83a80eeSchristos cmd = 'cp -r ' + tmpdir + '* ' + conf.getValue('destdir') 132*d83a80eeSchristos run_cmd(cmd, 'using cp to transfer to demo system...') 133*d83a80eeSchristos else: 134*d83a80eeSchristos scp_target() 135*d83a80eeSchristos 136*d83a80eeSchristos return 137*d83a80eeSchristos 138*d83a80eeSchristosdef restart_nsd(): 139*d83a80eeSchristos if conf.getValue('DEMO-MODE'): 140*d83a80eeSchristos cmd = conf.getValue('stop_cmd') 141*d83a80eeSchristos run_cmd(cmd, 'stopping nsd...') 142*d83a80eeSchristos 143*d83a80eeSchristos # BOZO: rebuild is not behaving when there are errors, so the hack is 144*d83a80eeSchristos # to remove the existing db, run the zone compiler and restart nsd 145*d83a80eeSchristos #cmd = conf.getValue('rebuild_cmd') 146*d83a80eeSchristos #os.system(cmd) 147*d83a80eeSchristos cmd = 'rm -f ' + conf.getValue('database') 148*d83a80eeSchristos run_cmd(cmd, 'removing old zonedb...') 149*d83a80eeSchristos cmd = conf.getValue('zonec_cmd') 150*d83a80eeSchristos run_cmd(cmd, 'rebuilding zonedb...') 151*d83a80eeSchristos 152*d83a80eeSchristos cmd = conf.getValue('start_cmd') 153*d83a80eeSchristos run_cmd(cmd, 'starting nsd...') 154*d83a80eeSchristos else: 155*d83a80eeSchristos cmd = 'ssh -a -x ' 156*d83a80eeSchristos cmd += conf.getValue('destuser') + '@' + conf.getValue('dest-ip') 157*d83a80eeSchristos child = pexpect.spawn(cmd) 158*d83a80eeSchristos if not child.isalive(): 159*d83a80eeSchristos bail('? cannot login to NSD system at %s' % \ 160*d83a80eeSchristos (conf.getValue('dest-ip'))) 161*d83a80eeSchristos else: 162*d83a80eeSchristos report_info('=> restarting NSD on %s' % \ 163*d83a80eeSchristos (conf.getValue('dest-ip'))) 164*d83a80eeSchristos child.expect('.*ssword:') 165*d83a80eeSchristos child.sendline(conf.getValue('syspw')) 166*d83a80eeSchristos child.expect('# ') 167*d83a80eeSchristos report_info('=> now logged in') 168*d83a80eeSchristos report_info('=> issuing zonec') 169*d83a80eeSchristos child.sendline(conf.getValue('zonec_cmd')) 170*d83a80eeSchristos if isVerbose(): 171*d83a80eeSchristos child.logfile = sys.stdout 172*d83a80eeSchristos child.expect('# ') 173*d83a80eeSchristos report_info('=> issuing stop') 174*d83a80eeSchristos child.sendline(conf.getValue('stop_cmd')) 175*d83a80eeSchristos child.expect('# ') 176*d83a80eeSchristos report_info('=> issuing start') 177*d83a80eeSchristos child.sendline(conf.getValue('start_cmd')) 178*d83a80eeSchristos child.expect('# ') 179*d83a80eeSchristos child.sendline('exit') 180*d83a80eeSchristos child.close() 181*d83a80eeSchristos report_info('=> restart done') 182*d83a80eeSchristos 183*d83a80eeSchristos return 184*d83a80eeSchristos 185*d83a80eeSchristosdef quick_parse(): 186*d83a80eeSchristos #-- build an in-core representation of the named.conf file 187*d83a80eeSchristos named_root = conf.getValue('named_root') 188*d83a80eeSchristos named_fname = conf.getValue('named_conf') 189*d83a80eeSchristos report_info('=> parsing named.conf file \"%s\"...' % (named_fname)) 190*d83a80eeSchristos 191*d83a80eeSchristos pwd = os.getcwd() 192*d83a80eeSchristos if os.path.exists(named_root) and os.path.isdir(named_root): 193*d83a80eeSchristos os.chdir(named_root) 194*d83a80eeSchristos else: 195*d83a80eeSchristos bail('? cannot find the named root directory "%s"' % (named_root)) 196*d83a80eeSchristos named = NamedConf(named_fname) 197*d83a80eeSchristos os.chdir(pwd) 198*d83a80eeSchristos 199*d83a80eeSchristos return named 200*d83a80eeSchristos 201*d83a80eeSchristosdef run_named_check(named): 202*d83a80eeSchristos #-- run named-checkconf on the config file and then run named-checkzone 203*d83a80eeSchristos # on each zone file 204*d83a80eeSchristos chkconf = conf.getValue('named-checkconf') 205*d83a80eeSchristos if os.path.exists(chkconf): 206*d83a80eeSchristos fname = conf.getValue('named_root') 207*d83a80eeSchristos fname += '/' + conf.getValue('named_conf') 208*d83a80eeSchristos report_info('=> running "%s" on "%s"...' % (chkconf, fname)) 209*d83a80eeSchristos (output, errors) = run_cmd_capture(chkconf + ' ' + fname) 210*d83a80eeSchristos if len(errors) > 0: 211*d83a80eeSchristos report_info('? errors found --->') 212*d83a80eeSchristos report_info(errors) 213*d83a80eeSchristos else: 214*d83a80eeSchristos report_info(' all is well.') 215*d83a80eeSchristos else: 216*d83a80eeSchristos report_error("? wanted to run named-checkconf, dude, but it's not there.") 217*d83a80eeSchristos 218*d83a80eeSchristos chkzone = conf.getValue('named-checkzone') 219*d83a80eeSchristos if os.path.exists(chkzone): 220*d83a80eeSchristos zdict = named.getZones() 221*d83a80eeSchristos zlist = zdict.keys() 222*d83a80eeSchristos zlist.sort() 223*d83a80eeSchristos rname = named.getOptions().getDirectory().replace('"','') 224*d83a80eeSchristos report_info('=> running "%s" on all zones...' % (chkzone)) 225*d83a80eeSchristos prog = re.compile(':[0-9][0-9]*:') 226*d83a80eeSchristos for ii in zlist: 227*d83a80eeSchristos zone = zdict[ii].getName() 228*d83a80eeSchristos zfile = rname + '/' + zdict[ii].getFile() 229*d83a80eeSchristos (output, errors) = run_cmd_capture(chkzone + ' ' + zone + ' ' + zfile) 230*d83a80eeSchristos if len(output) > 0 and prog.search(output) != None: 231*d83a80eeSchristos report_info(output.strip()) 232*d83a80eeSchristos else: 233*d83a80eeSchristos report_error("? wanted to run named-checkzone, dude, but it's not there.") 234*d83a80eeSchristos 235*d83a80eeSchristos return 236*d83a80eeSchristos 237*d83a80eeSchristosdef run_zonec(): 238*d83a80eeSchristos zonec = conf.getValue('zonec_cmd') 239*d83a80eeSchristos if os.path.exists(zonec): 240*d83a80eeSchristos report_info('=> running the zone compiler "%s"...' % (zonec)) 241*d83a80eeSchristos fname = conf.getValue('nsd_conf') 242*d83a80eeSchristos tmpdir = conf.getValue('tmpdir') 243*d83a80eeSchristos cmd = zonec + ' -c ' + tmpdir + '/' + fname + ' -d ' + tmpdir 244*d83a80eeSchristos cmd += ' -f ' + tmpdir + '/zone.db' 245*d83a80eeSchristos os.system('rm -f ' + tmpdir + '/zone.db') 246*d83a80eeSchristos (output, errors) = run_cmd_capture(cmd) 247*d83a80eeSchristos if len(errors) > 0: 248*d83a80eeSchristos report_info('? errors found --->') 249*d83a80eeSchristos report_info(errors) 250*d83a80eeSchristos else: 251*d83a80eeSchristos report_info(' all is well.') 252*d83a80eeSchristos else: 253*d83a80eeSchristos report_error("? hmph. wanted to run zonec, but it's not there.") 254*d83a80eeSchristos 255*d83a80eeSchristos return 256*d83a80eeSchristos 257*d83a80eeSchristos 258*d83a80eeSchristos#-- main --------------------------------------------------------------- 259*d83a80eeSchristosdef main(): 260*d83a80eeSchristos 261*d83a80eeSchristos try: 262*d83a80eeSchristos opts, args = getopt.getopt(sys.argv[1:], 263*d83a80eeSchristos 'ahnsv', 264*d83a80eeSchristos ['analyze-only', 'help', 'now', 'sync-only', 265*d83a80eeSchristos 'verbose'] 266*d83a80eeSchristos ) 267*d83a80eeSchristos except getopt.GetoptError: 268*d83a80eeSchristos usage() 269*d83a80eeSchristos sys.exit(1) 270*d83a80eeSchristos 271*d83a80eeSchristos now = False 272*d83a80eeSchristos analyze_only = False 273*d83a80eeSchristos sync_only = False 274*d83a80eeSchristos for ii, val in opts: 275*d83a80eeSchristos if ii in ('-a', '--analyze-only'): 276*d83a80eeSchristos analyze_only = True 277*d83a80eeSchristos if ii in ('-h', '--help'): 278*d83a80eeSchristos usage() 279*d83a80eeSchristos sys.exit(0) 280*d83a80eeSchristos if ii in ('-n', '--now'): 281*d83a80eeSchristos now = True 282*d83a80eeSchristos if ii in ('-s', '--sync-only'): 283*d83a80eeSchristos sync_only = True 284*d83a80eeSchristos if ii in ('-v', '--verbose'): 285*d83a80eeSchristos set_verbosity(True) 286*d83a80eeSchristos 287*d83a80eeSchristos last_stat = {} 288*d83a80eeSchristos this_stat = {} 289*d83a80eeSchristos 290*d83a80eeSchristos #-- don't poll unless we need to... 291*d83a80eeSchristos if now: 292*d83a80eeSchristos rebuild_nsd_files() 293*d83a80eeSchristos cp_files(analyze_only) 294*d83a80eeSchristos restart_nsd() 295*d83a80eeSchristos sys.exit(0) 296*d83a80eeSchristos 297*d83a80eeSchristos #-- ...and don't poll if we just need to sync up to the machine... 298*d83a80eeSchristos if sync_only: 299*d83a80eeSchristos cp_files(analyze_only) 300*d83a80eeSchristos restart_nsd() 301*d83a80eeSchristos sys.exit(0) 302*d83a80eeSchristos 303*d83a80eeSchristos #-- ...and don't poll if we're just checking things out... 304*d83a80eeSchristos if analyze_only: 305*d83a80eeSchristos #-- well, and do a couple of extra things, too 306*d83a80eeSchristos set_verbosity(True) 307*d83a80eeSchristos report_info( \ 308*d83a80eeSchristos 'nsd-sync %s, copyright(c) 2007, Secure64 Software Corporation' \ 309*d83a80eeSchristos % (conf.getValue('version'))) 310*d83a80eeSchristos named = quick_parse() 311*d83a80eeSchristos rebuild_nsd_files() 312*d83a80eeSchristos run_named_check(named) 313*d83a80eeSchristos cp_files(analyze_only) 314*d83a80eeSchristos run_zonec() 315*d83a80eeSchristos sys.exit(0) 316*d83a80eeSchristos 317*d83a80eeSchristos #-- apparently we need to poll... 318*d83a80eeSchristos tmplist = conf.getValue('named_watchlist').split() 319*d83a80eeSchristos watchlist = [] 320*d83a80eeSchristos for ii in tmplist: 321*d83a80eeSchristos watchlist.append(ii.strip()) 322*d83a80eeSchristos while True: 323*d83a80eeSchristos for ii in watchlist: 324*d83a80eeSchristos if ii in last_stat.keys(): 325*d83a80eeSchristos statinfo = os.stat(ii) 326*d83a80eeSchristos this_stat[ii] = (statinfo.st_size, statinfo.st_mtime) 327*d83a80eeSchristos (old_size, old_mtime) = last_stat[ii] 328*d83a80eeSchristos (new_size, new_mtime) = this_stat[ii] 329*d83a80eeSchristos if old_size != new_size or old_mtime != new_mtime: 330*d83a80eeSchristos report_info('aha! "%s" has changed!' % (ii)) 331*d83a80eeSchristos last_stat[ii] = (new_size, new_mtime) 332*d83a80eeSchristos rebuild_nsd_files() 333*d83a80eeSchristos cp_files(analyze_only) 334*d83a80eeSchristos restart_nsd() 335*d83a80eeSchristos else: 336*d83a80eeSchristos statinfo = os.stat(ii) 337*d83a80eeSchristos last_stat[ii] = (statinfo.st_size, statinfo.st_mtime) 338*d83a80eeSchristos this_stat[ii] = last_stat[ii] 339*d83a80eeSchristos 340*d83a80eeSchristos time.sleep(int(conf.getValue('sleep_time'))) 341*d83a80eeSchristos 342*d83a80eeSchristos sys.exit(0) 343*d83a80eeSchristos 344*d83a80eeSchristos#-- just in case 345*d83a80eeSchristosif __name__ == '__main__': 346*d83a80eeSchristos main() 347