Home | History | Annotate | Line # | Download | only in scripts
      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
     29 import getopt
     30 import os
     31 import os.path
     32 import popen2
     33 import re
     34 import sys
     35 import time
     36 
     37 if 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 *
     43 else:
     44    from bind2nsd.Config import *
     45    from bind2nsd.Utils import *
     46    from bind2nsd.NamedConf import *
     47    from bind2nsd.NsdConf import *
     48 
     49 if os.path.exists('../pexpect-2.1'):
     50    sys.path.append('../pexpect-2.1')
     51 import pexpect
     52 import pxssh
     53 
     54 #-- globals
     55 conf = Config()
     56 
     57 #-- useful functions
     58 def 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 
     72 def 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 
     82 def 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 
    121 def 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 
    141 def 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 
    193 def 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 
    209 def 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 
    245 def 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 ---------------------------------------------------------------
    267 def 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
    353 if __name__ == '__main__':
    354    main()
    355