s64-sync revision 1.1 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