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