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