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 NSD 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 NamedConf import *
     41    from NsdConf import *
     42    from Utils import *
     43 else:
     44    from bind2nsd.Config import *
     45    from bind2nsd.NamedConf import *
     46    from bind2nsd.NsdConf import *
     47    from bind2nsd.Utils 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 '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 
     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_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 
    118 def 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 
    138 def 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 
    185 def 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 
    201 def 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 
    237 def 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 ---------------------------------------------------------------
    259 def 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
    345 if __name__ == '__main__':
    346    main()
    347