xref: /netbsd-src/external/bsd/nsd/dist/contrib/bind2nsd/scripts/s64-sync (revision d83a80ee7fb31190352cf1f781441e06ca6a86db)
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
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 Utils import *
41   from NamedConf import *
42   from NsdConf import *
43else:
44   from bind2nsd.Config import *
45   from bind2nsd.Utils import *
46   from bind2nsd.NamedConf import *
47   from bind2nsd.NsdConf 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 '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
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_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
121def 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
141def 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
193def 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
209def 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
245def 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 ---------------------------------------------------------------
267def 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
353if __name__ == '__main__':
354   main()
355