Home | History | Annotate | Line # | Download | only in scripts
      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