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 DNS 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 Utils import * 41*d83a80eeSchristos from NamedConf import * 42*d83a80eeSchristos from NsdConf import * 43*d83a80eeSchristoselse: 44*d83a80eeSchristos from bind2nsd.Config import * 45*d83a80eeSchristos from bind2nsd.Utils import * 46*d83a80eeSchristos from bind2nsd.NamedConf import * 47*d83a80eeSchristos from bind2nsd.NsdConf 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 's64-sync %s, copyright(c) 2007, Secure64 Software Corporation' \ 60*d83a80eeSchristos % (conf.getValue('version')) 61*d83a80eeSchristos print 62*d83a80eeSchristos print 'usage: s64-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 Secure64 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_s64(): 83*d83a80eeSchristos #-- do the scp to an actual Secure64 server 84*d83a80eeSchristos report_info('=> using scp to transfer to Secure64 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 #-- this is what i wanted to do... 102*d83a80eeSchristos #cmd = 'scp -r ' + tmpdir + ' dns@' + conf.getValue('dest-ip') + ':' 103*d83a80eeSchristos #-- this is what works... 104*d83a80eeSchristos flist = os.listdir('.') 105*d83a80eeSchristos fnames = ' '.join(flist) 106*d83a80eeSchristos cmd = 'scp -r ' + fnames 107*d83a80eeSchristos cmd += ' ' + conf.getValue('destuser') + '@' 108*d83a80eeSchristos cmd += conf.getValue('dest-ip') + ':' 109*d83a80eeSchristos report_info('=> ' + cmd) 110*d83a80eeSchristos child = pexpect.spawn(cmd) 111*d83a80eeSchristos if len(conf.getValue('dnspw')) > 0: 112*d83a80eeSchristos child.expect('.*ssword:') 113*d83a80eeSchristos child.sendline(conf.getValue('dnspw')) 114*d83a80eeSchristos child.expect('.*' + conf.getValue('nsd_conf') + '.*') 115*d83a80eeSchristos child.expect(pexpect.EOF) 116*d83a80eeSchristos child.close() 117*d83a80eeSchristos os.chdir(pwd) 118*d83a80eeSchristos 119*d83a80eeSchristos return 120*d83a80eeSchristos 121*d83a80eeSchristosdef cp_files(analyze): 122*d83a80eeSchristos #-- we assume everything has already been copied to the tmpdir by bind2nsd 123*d83a80eeSchristos 124*d83a80eeSchristos if analyze: 125*d83a80eeSchristos return 126*d83a80eeSchristos 127*d83a80eeSchristos tmpdir = conf.getValue('tmpdir') # must have trailing '/' 128*d83a80eeSchristos if not os.path.exists(tmpdir) and not os.path.isdir(tmpdir): 129*d83a80eeSchristos bail('? cannot find "%s"...' % (tmpdir)) 130*d83a80eeSchristos 131*d83a80eeSchristos #-- scp the entire tmp directory 132*d83a80eeSchristos if conf.getValue('DEMO-MODE'): 133*d83a80eeSchristos report_info('** scp would go here, but cp -r for demonstration purposes') 134*d83a80eeSchristos cmd = 'cp -r ' + tmpdir + '* ' + conf.getValue('destdir') 135*d83a80eeSchristos run_cmd(cmd, 'using cp to transfer to demo system...') 136*d83a80eeSchristos else: 137*d83a80eeSchristos scp_s64() 138*d83a80eeSchristos 139*d83a80eeSchristos return 140*d83a80eeSchristos 141*d83a80eeSchristosdef restart_nsd(): 142*d83a80eeSchristos if conf.getValue('DEMO-MODE'): 143*d83a80eeSchristos cmd = conf.getValue('stop_cmd') 144*d83a80eeSchristos run_cmd(cmd, 'stopping nsd...') 145*d83a80eeSchristos 146*d83a80eeSchristos # BOZO: rebuild is not behaving when there are errors, so the hack is 147*d83a80eeSchristos # to remove the existing db, run the zone compiler and restart nsd 148*d83a80eeSchristos #cmd = conf.getValue('rebuild_cmd') 149*d83a80eeSchristos #os.system(cmd) 150*d83a80eeSchristos cmd = 'rm -f ' + conf.getValue('database') 151*d83a80eeSchristos run_cmd(cmd, 'removing old zonedb...') 152*d83a80eeSchristos cmd = conf.getValue('zonec_cmd') 153*d83a80eeSchristos run_cmd(cmd, 'rebuilding zonedb...') 154*d83a80eeSchristos 155*d83a80eeSchristos cmd = conf.getValue('start_cmd') 156*d83a80eeSchristos run_cmd(cmd, 'starting nsd...') 157*d83a80eeSchristos else: 158*d83a80eeSchristos cmd = 'ssh -a -x dns@' + conf.getValue('dest-ip') 159*d83a80eeSchristos child = pexpect.spawn(cmd) 160*d83a80eeSchristos if not child.isalive(): 161*d83a80eeSchristos bail('? cannot login to Secure64 system at %s' % \ 162*d83a80eeSchristos (conf.getValue('dest-ip'))) 163*d83a80eeSchristos else: 164*d83a80eeSchristos report_info('=> restarting Secure64 NSD on %s' % \ 165*d83a80eeSchristos (conf.getValue('dest-ip'))) 166*d83a80eeSchristos child.expect('.*ssword:') 167*d83a80eeSchristos child.sendline(conf.getValue('syspw')) 168*d83a80eeSchristos child.expect('\[view@.*> ') 169*d83a80eeSchristos report_info('=> now logged in') 170*d83a80eeSchristos child.sendline('enable dnsconfig') 171*d83a80eeSchristos child.expect('.*ssword:') 172*d83a80eeSchristos child.sendline(conf.getValue('dnspw')) 173*d83a80eeSchristos child.expect('\*.*') 174*d83a80eeSchristos report_info('=> issuing zonec') 175*d83a80eeSchristos child.sendline('zonec') 176*d83a80eeSchristos if isVerbose(): 177*d83a80eeSchristos child.logfile = sys.stdout 178*d83a80eeSchristos child.expect('\[dnsconfig@.*# ') 179*d83a80eeSchristos report_info('=> issuing stop') 180*d83a80eeSchristos child.sendline('stop') 181*d83a80eeSchristos child.expect('\[dnsconfig@.*# ') 182*d83a80eeSchristos report_info('=> issuing start') 183*d83a80eeSchristos child.sendline('start') 184*d83a80eeSchristos child.expect('\[dnsconfig@.*# ') 185*d83a80eeSchristos child.sendline('exit') 186*d83a80eeSchristos child.expect('\[view@.*> ') 187*d83a80eeSchristos child.sendline('exit') 188*d83a80eeSchristos child.close() 189*d83a80eeSchristos report_info('=> restart done') 190*d83a80eeSchristos 191*d83a80eeSchristos return 192*d83a80eeSchristos 193*d83a80eeSchristosdef quick_parse(): 194*d83a80eeSchristos #-- build an in-core representation of the named.conf file 195*d83a80eeSchristos named_root = conf.getValue('named_root') 196*d83a80eeSchristos named_fname = conf.getValue('named_conf') 197*d83a80eeSchristos report_info('=> parsing named.conf file \"%s\"...' % (named_fname)) 198*d83a80eeSchristos 199*d83a80eeSchristos pwd = os.getcwd() 200*d83a80eeSchristos if os.path.exists(named_root) and os.path.isdir(named_root): 201*d83a80eeSchristos os.chdir(named_root) 202*d83a80eeSchristos else: 203*d83a80eeSchristos bail('? cannot find the named root directory "%s"' % (named_root)) 204*d83a80eeSchristos named = NamedConf(named_fname) 205*d83a80eeSchristos os.chdir(pwd) 206*d83a80eeSchristos 207*d83a80eeSchristos return named 208*d83a80eeSchristos 209*d83a80eeSchristosdef run_named_check(named): 210*d83a80eeSchristos #-- run named-checkconf on the config file and then run named-checkzone 211*d83a80eeSchristos # on each zone file 212*d83a80eeSchristos chkconf = conf.getValue('named-checkconf') 213*d83a80eeSchristos if os.path.exists(chkconf): 214*d83a80eeSchristos fname = conf.getValue('named_root') 215*d83a80eeSchristos fname += '/' + conf.getValue('named_conf') 216*d83a80eeSchristos report_info('=> running "%s" on "%s"...' % (chkconf, fname)) 217*d83a80eeSchristos (output, errors) = run_cmd_capture(chkconf + ' ' + fname) 218*d83a80eeSchristos if len(errors) > 0: 219*d83a80eeSchristos report_info('? errors found --->') 220*d83a80eeSchristos report_info(errors) 221*d83a80eeSchristos else: 222*d83a80eeSchristos report_info(' all is well.') 223*d83a80eeSchristos else: 224*d83a80eeSchristos report_error("? wanted to run named-checkconf, dude, but it's not there.") 225*d83a80eeSchristos 226*d83a80eeSchristos chkzone = conf.getValue('named-checkzone') 227*d83a80eeSchristos if os.path.exists(chkzone): 228*d83a80eeSchristos zdict = named.getZones() 229*d83a80eeSchristos zlist = zdict.keys() 230*d83a80eeSchristos zlist.sort() 231*d83a80eeSchristos rname = named.getOptions().getDirectory().replace('"','') 232*d83a80eeSchristos report_info('=> running "%s" on all zones...' % (chkzone)) 233*d83a80eeSchristos prog = re.compile(':[0-9][0-9]*:') 234*d83a80eeSchristos for ii in zlist: 235*d83a80eeSchristos zone = zdict[ii].getName() 236*d83a80eeSchristos zfile = rname + '/' + zdict[ii].getFile() 237*d83a80eeSchristos (output, errors) = run_cmd_capture(chkzone + ' ' + zone + ' ' + zfile) 238*d83a80eeSchristos if len(output) > 0 and prog.search(output) != None: 239*d83a80eeSchristos report_info(output.strip()) 240*d83a80eeSchristos else: 241*d83a80eeSchristos report_error("? wanted to run named-checkzone, dude, but it's not there.") 242*d83a80eeSchristos 243*d83a80eeSchristos return 244*d83a80eeSchristos 245*d83a80eeSchristosdef run_zonec(): 246*d83a80eeSchristos zonec = conf.getValue('zonec_cmd') 247*d83a80eeSchristos if os.path.exists(zonec): 248*d83a80eeSchristos report_info('=> running the zone compiler "%s"...' % (zonec)) 249*d83a80eeSchristos fname = conf.getValue('nsd_conf') 250*d83a80eeSchristos tmpdir = conf.getValue('tmpdir') 251*d83a80eeSchristos cmd = zonec + ' -c ' + tmpdir + '/' + fname + ' -d ' + tmpdir 252*d83a80eeSchristos cmd += ' -f ' + tmpdir + '/zone.db' 253*d83a80eeSchristos os.system('rm -f ' + tmpdir + '/zone.db') 254*d83a80eeSchristos (output, errors) = run_cmd_capture(cmd) 255*d83a80eeSchristos if len(errors) > 0: 256*d83a80eeSchristos report_info('? errors found --->') 257*d83a80eeSchristos report_info(errors) 258*d83a80eeSchristos else: 259*d83a80eeSchristos report_info(' all is well.') 260*d83a80eeSchristos else: 261*d83a80eeSchristos report_error("? hmph. wanted to run zonec, but it's not there.") 262*d83a80eeSchristos 263*d83a80eeSchristos return 264*d83a80eeSchristos 265*d83a80eeSchristos 266*d83a80eeSchristos#-- main --------------------------------------------------------------- 267*d83a80eeSchristosdef main(): 268*d83a80eeSchristos 269*d83a80eeSchristos try: 270*d83a80eeSchristos opts, args = getopt.getopt(sys.argv[1:], 271*d83a80eeSchristos 'ahnsv', 272*d83a80eeSchristos ['analyze-only', 'help', 'now', 'sync-only', 273*d83a80eeSchristos 'verbose'] 274*d83a80eeSchristos ) 275*d83a80eeSchristos except getopt.GetoptError: 276*d83a80eeSchristos usage() 277*d83a80eeSchristos sys.exit(1) 278*d83a80eeSchristos 279*d83a80eeSchristos now = False 280*d83a80eeSchristos analyze_only = False 281*d83a80eeSchristos sync_only = False 282*d83a80eeSchristos for ii, val in opts: 283*d83a80eeSchristos if ii in ('-a', '--analyze-only'): 284*d83a80eeSchristos analyze_only = True 285*d83a80eeSchristos if ii in ('-h', '--help'): 286*d83a80eeSchristos usage() 287*d83a80eeSchristos sys.exit(0) 288*d83a80eeSchristos if ii in ('-n', '--now'): 289*d83a80eeSchristos now = True 290*d83a80eeSchristos if ii in ('-s', '--sync-only'): 291*d83a80eeSchristos sync_only = True 292*d83a80eeSchristos if ii in ('-v', '--verbose'): 293*d83a80eeSchristos set_verbosity(True) 294*d83a80eeSchristos 295*d83a80eeSchristos last_stat = {} 296*d83a80eeSchristos this_stat = {} 297*d83a80eeSchristos 298*d83a80eeSchristos #-- don't poll unless we need to... 299*d83a80eeSchristos if now: 300*d83a80eeSchristos rebuild_nsd_files() 301*d83a80eeSchristos cp_files(analyze_only) 302*d83a80eeSchristos restart_nsd() 303*d83a80eeSchristos sys.exit(0) 304*d83a80eeSchristos 305*d83a80eeSchristos #-- ...and don't poll if we just need to sync up to the Secure64 machine... 306*d83a80eeSchristos if sync_only: 307*d83a80eeSchristos cp_files(analyze_only) 308*d83a80eeSchristos restart_nsd() 309*d83a80eeSchristos sys.exit(0) 310*d83a80eeSchristos 311*d83a80eeSchristos #-- ...and don't poll if we're just checking things out... 312*d83a80eeSchristos if analyze_only: 313*d83a80eeSchristos #-- well, and do a couple of extra things, too 314*d83a80eeSchristos set_verbosity(True) 315*d83a80eeSchristos report_info( \ 316*d83a80eeSchristos 's64-sync %s, copyright(c) 2007, Secure64 Software Corporation' \ 317*d83a80eeSchristos % (conf.getValue('version'))) 318*d83a80eeSchristos named = quick_parse() 319*d83a80eeSchristos rebuild_nsd_files() 320*d83a80eeSchristos run_named_check(named) 321*d83a80eeSchristos cp_files(analyze_only) 322*d83a80eeSchristos run_zonec() 323*d83a80eeSchristos sys.exit(0) 324*d83a80eeSchristos 325*d83a80eeSchristos #-- apparently we need to poll... 326*d83a80eeSchristos tmplist = conf.getValue('named_watchlist').split() 327*d83a80eeSchristos watchlist = [] 328*d83a80eeSchristos for ii in tmplist: 329*d83a80eeSchristos watchlist.append(ii.strip()) 330*d83a80eeSchristos while True: 331*d83a80eeSchristos for ii in watchlist: 332*d83a80eeSchristos if ii in last_stat.keys(): 333*d83a80eeSchristos statinfo = os.stat(ii) 334*d83a80eeSchristos this_stat[ii] = (statinfo.st_size, statinfo.st_mtime) 335*d83a80eeSchristos (old_size, old_mtime) = last_stat[ii] 336*d83a80eeSchristos (new_size, new_mtime) = this_stat[ii] 337*d83a80eeSchristos if old_size != new_size or old_mtime != new_mtime: 338*d83a80eeSchristos report_info('aha! "%s" has changed!' % (ii)) 339*d83a80eeSchristos last_stat[ii] = (new_size, new_mtime) 340*d83a80eeSchristos rebuild_nsd_files() 341*d83a80eeSchristos cp_files(analyze_only) 342*d83a80eeSchristos restart_nsd() 343*d83a80eeSchristos else: 344*d83a80eeSchristos statinfo = os.stat(ii) 345*d83a80eeSchristos last_stat[ii] = (statinfo.st_size, statinfo.st_mtime) 346*d83a80eeSchristos this_stat[ii] = last_stat[ii] 347*d83a80eeSchristos 348*d83a80eeSchristos time.sleep(int(conf.getValue('sleep_time'))) 349*d83a80eeSchristos 350*d83a80eeSchristos sys.exit(0) 351*d83a80eeSchristos 352*d83a80eeSchristos#-- just in case 353*d83a80eeSchristosif __name__ == '__main__': 354*d83a80eeSchristos main() 355