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