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