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