wintest: enable dns forwarding for internal dns
[Samba/gebeck_regimport.git] / wintest / wintest.py
blob6257fb45ddc63a2d6cbd9343c8186e071004a226
1 #!/usr/bin/env python
3 '''automated testing library for testing Samba against windows'''
5 import pexpect, subprocess
6 import optparse
7 import sys, os, time, re
9 class wintest():
10 '''testing of Samba against windows VMs'''
12 def __init__(self):
13 self.vars = {}
14 self.list_mode = False
15 self.vms = None
16 os.environ['PYTHONUNBUFFERED'] = '1'
17 self.parser = optparse.OptionParser("wintest")
19 def check_prerequesites(self):
20 self.info("Checking prerequesites")
21 self.setvar('HOSTNAME', self.cmd_output("hostname -s").strip())
22 if os.getuid() != 0:
23 raise Exception("You must run this script as root")
24 self.run_cmd('ifconfig ${INTERFACE} ${INTERFACE_NET} up')
25 if self.getvar('INTERFACE_IPV6'):
26 self.run_cmd('ifconfig ${INTERFACE} inet6 del ${INTERFACE_IPV6}/64', checkfail=False)
27 self.run_cmd('ifconfig ${INTERFACE} inet6 add ${INTERFACE_IPV6}/64 up')
29 def stop_vms(self):
30 '''Shut down any existing alive VMs, so they do not collide with what we are doing'''
31 self.info('Shutting down any of our VMs already running')
32 vms = self.get_vms()
33 for v in vms:
34 self.vm_poweroff(v, checkfail=False)
36 def setvar(self, varname, value):
37 '''set a substitution variable'''
38 self.vars[varname] = value
40 def getvar(self, varname):
41 '''return a substitution variable'''
42 if not varname in self.vars:
43 return None
44 return self.vars[varname]
46 def setwinvars(self, vm, prefix='WIN'):
47 '''setup WIN_XX vars based on a vm name'''
48 for v in ['VM', 'HOSTNAME', 'USER', 'PASS', 'SNAPSHOT', 'REALM', 'DOMAIN', 'IP']:
49 vname = '%s_%s' % (vm, v)
50 if vname in self.vars:
51 self.setvar("%s_%s" % (prefix,v), self.substitute("${%s}" % vname))
52 else:
53 self.vars.pop("%s_%s" % (prefix,v), None)
55 if self.getvar("WIN_REALM"):
56 self.setvar("WIN_REALM", self.getvar("WIN_REALM").upper())
57 self.setvar("WIN_LCREALM", self.getvar("WIN_REALM").lower())
58 dnsdomain = self.getvar("WIN_REALM")
59 self.setvar("WIN_BASEDN", "DC=" + dnsdomain.replace(".", ",DC="))
60 if self.getvar("WIN_USER") is None:
61 self.setvar("WIN_USER", "administrator")
63 def info(self, msg):
64 '''print some information'''
65 if not self.list_mode:
66 print(self.substitute(msg))
68 def load_config(self, fname):
69 '''load the config file'''
70 f = open(fname)
71 for line in f:
72 line = line.strip()
73 if len(line) == 0 or line[0] == '#':
74 continue
75 colon = line.find(':')
76 if colon == -1:
77 raise RuntimeError("Invalid config line '%s'" % line)
78 varname = line[0:colon].strip()
79 value = line[colon+1:].strip()
80 self.setvar(varname, value)
82 def list_steps_mode(self):
83 '''put wintest in step listing mode'''
84 self.list_mode = True
86 def set_skip(self, skiplist):
87 '''set a list of tests to skip'''
88 self.skiplist = skiplist.split(',')
90 def set_vms(self, vms):
91 '''set a list of VMs to test'''
92 if vms is not None:
93 self.vms = []
94 for vm in vms.split(','):
95 vm = vm.upper()
96 self.vms.append(vm)
98 def skip(self, step):
99 '''return True if we should skip a step'''
100 if self.list_mode:
101 print("\t%s" % step)
102 return True
103 return step in self.skiplist
105 def substitute(self, text):
106 """Substitute strings of the form ${NAME} in text, replacing
107 with substitutions from vars.
109 if isinstance(text, list):
110 ret = text[:]
111 for i in range(len(ret)):
112 ret[i] = self.substitute(ret[i])
113 return ret
115 """We may have objects such as pexpect.EOF that are not strings"""
116 if not isinstance(text, str):
117 return text
118 while True:
119 var_start = text.find("${")
120 if var_start == -1:
121 return text
122 var_end = text.find("}", var_start)
123 if var_end == -1:
124 return text
125 var_name = text[var_start+2:var_end]
126 if not var_name in self.vars:
127 raise RuntimeError("Unknown substitution variable ${%s}" % var_name)
128 text = text.replace("${%s}" % var_name, self.vars[var_name])
129 return text
131 def have_var(self, varname):
132 '''see if a variable has been set'''
133 return varname in self.vars
135 def have_vm(self, vmname):
136 '''see if a VM should be used'''
137 if not self.have_var(vmname + '_VM'):
138 return False
139 if self.vms is None:
140 return True
141 return vmname in self.vms
143 def putenv(self, key, value):
144 '''putenv with substitution'''
145 os.environ[key] = self.substitute(value)
147 def chdir(self, dir):
148 '''chdir with substitution'''
149 os.chdir(self.substitute(dir))
151 def del_files(self, dirs):
152 '''delete all files in the given directory'''
153 for d in dirs:
154 self.run_cmd("find %s -type f | xargs rm -f" % d)
156 def write_file(self, filename, text, mode='w'):
157 '''write to a file'''
158 f = open(self.substitute(filename), mode=mode)
159 f.write(self.substitute(text))
160 f.close()
162 def run_cmd(self, cmd, dir=".", show=None, output=False, checkfail=True):
163 '''run a command'''
164 cmd = self.substitute(cmd)
165 if isinstance(cmd, list):
166 self.info('$ ' + " ".join(cmd))
167 else:
168 self.info('$ ' + cmd)
169 if output:
170 return subprocess.Popen([cmd], shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=dir).communicate()[0]
171 if isinstance(cmd, list):
172 shell=False
173 else:
174 shell=True
175 if checkfail:
176 return subprocess.check_call(cmd, shell=shell, cwd=dir)
177 else:
178 return subprocess.call(cmd, shell=shell, cwd=dir)
181 def run_child(self, cmd, dir="."):
182 '''create a child and return the Popen handle to it'''
183 cwd = os.getcwd()
184 cmd = self.substitute(cmd)
185 if isinstance(cmd, list):
186 self.info('$ ' + " ".join(cmd))
187 else:
188 self.info('$ ' + cmd)
189 if isinstance(cmd, list):
190 shell=False
191 else:
192 shell=True
193 os.chdir(dir)
194 ret = subprocess.Popen(cmd, shell=shell, stderr=subprocess.STDOUT)
195 os.chdir(cwd)
196 return ret
198 def cmd_output(self, cmd):
199 '''return output from and command'''
200 cmd = self.substitute(cmd)
201 return self.run_cmd(cmd, output=True)
203 def cmd_contains(self, cmd, contains, nomatch=False, ordered=False, regex=False,
204 casefold=True):
205 '''check that command output contains the listed strings'''
207 if isinstance(contains, str):
208 contains = [contains]
210 out = self.cmd_output(cmd)
211 self.info(out)
212 for c in self.substitute(contains):
213 if regex:
214 if casefold:
215 c = c.upper()
216 out = out.upper()
217 m = re.search(c, out)
218 if m is None:
219 start = -1
220 end = -1
221 else:
222 start = m.start()
223 end = m.end()
224 elif casefold:
225 start = out.upper().find(c.upper())
226 end = start + len(c)
227 else:
228 start = out.find(c)
229 end = start + len(c)
230 if nomatch:
231 if start != -1:
232 raise RuntimeError("Expected to not see %s in %s" % (c, cmd))
233 else:
234 if start == -1:
235 raise RuntimeError("Expected to see %s in %s" % (c, cmd))
236 if ordered and start != -1:
237 out = out[end:]
239 def retry_cmd(self, cmd, contains, retries=30, delay=2, wait_for_fail=False,
240 ordered=False, regex=False, casefold=True):
241 '''retry a command a number of times'''
242 while retries > 0:
243 try:
244 self.cmd_contains(cmd, contains, nomatch=wait_for_fail,
245 ordered=ordered, regex=regex, casefold=casefold)
246 return
247 except:
248 time.sleep(delay)
249 retries -= 1
250 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
251 raise RuntimeError("Failed to find %s" % contains)
253 def pexpect_spawn(self, cmd, timeout=60, crlf=True, casefold=True):
254 '''wrapper around pexpect spawn'''
255 cmd = self.substitute(cmd)
256 self.info("$ " + cmd)
257 ret = pexpect.spawn(cmd, logfile=sys.stdout, timeout=timeout)
259 def sendline_sub(line):
260 line = self.substitute(line)
261 if crlf:
262 line = line.replace('\n', '\r\n') + '\r'
263 return ret.old_sendline(line)
265 def expect_sub(line, timeout=ret.timeout, casefold=casefold):
266 line = self.substitute(line)
267 if casefold:
268 if isinstance(line, list):
269 for i in range(len(line)):
270 if isinstance(line[i], str):
271 line[i] = '(?i)' + line[i]
272 elif isinstance(line, str):
273 line = '(?i)' + line
274 return ret.old_expect(line, timeout=timeout)
276 ret.old_sendline = ret.sendline
277 ret.sendline = sendline_sub
278 ret.old_expect = ret.expect
279 ret.expect = expect_sub
281 return ret
283 def get_nameserver(self):
284 '''Get the current nameserver from /etc/resolv.conf'''
285 child = self.pexpect_spawn('cat /etc/resolv.conf', crlf=False)
286 i = child.expect(['Generated by wintest', 'nameserver'])
287 if i == 0:
288 child.expect('your original resolv.conf')
289 child.expect('nameserver')
290 child.expect('\d+.\d+.\d+.\d+')
291 return child.after
293 def rndc_cmd(self, cmd, checkfail=True):
294 '''run a rndc command'''
295 self.run_cmd("${RNDC} -c ${PREFIX}/etc/rndc.conf %s" % cmd, checkfail=checkfail)
297 def named_supports_gssapi_keytab(self):
298 '''see if named supports tkey-gssapi-keytab'''
299 self.write_file("${PREFIX}/named.conf.test",
300 'options { tkey-gssapi-keytab "test"; };')
301 try:
302 self.run_cmd("${NAMED_CHECKCONF} ${PREFIX}/named.conf.test")
303 except subprocess.CalledProcessError:
304 return False
305 return True
307 def set_nameserver(self, nameserver):
308 '''set the nameserver in resolv.conf'''
309 self.write_file("/etc/resolv.conf.wintest", '''
310 # Generated by wintest, the Samba v Windows automated testing system
311 nameserver %s
313 # your original resolv.conf appears below:
314 ''' % self.substitute(nameserver))
315 child = self.pexpect_spawn("cat /etc/resolv.conf", crlf=False)
316 i = child.expect(['your original resolv.conf appears below:', pexpect.EOF])
317 if i == 0:
318 child.expect(pexpect.EOF)
319 contents = child.before.lstrip().replace('\r', '')
320 self.write_file('/etc/resolv.conf.wintest', contents, mode='a')
321 self.write_file('/etc/resolv.conf.wintest-bak', contents)
322 self.run_cmd("mv -f /etc/resolv.conf.wintest /etc/resolv.conf")
323 self.resolv_conf_backup = '/etc/resolv.conf.wintest-bak';
325 def configure_bind(self, kerberos_support=False, include=None):
326 self.chdir('${PREFIX}')
328 if self.getvar('INTERFACE_IPV6'):
329 ipv6_listen = 'listen-on-v6 port 53 { ${INTERFACE_IPV6}; };'
330 else:
331 ipv6_listen = ''
332 self.setvar('BIND_LISTEN_IPV6', ipv6_listen)
334 if not kerberos_support:
335 self.setvar("NAMED_TKEY_OPTION", "")
336 else:
337 if self.named_supports_gssapi_keytab():
338 self.setvar("NAMED_TKEY_OPTION",
339 'tkey-gssapi-keytab "${PREFIX}/private/dns.keytab";')
340 else:
341 self.info("LCREALM=${LCREALM}")
342 self.setvar("NAMED_TKEY_OPTION",
343 '''tkey-gssapi-credential "DNS/${LCREALM}";
344 tkey-domain "${LCREALM}";
345 ''')
346 self.putenv('KEYTAB_FILE', '${PREFIX}/private/dns.keytab')
347 self.putenv('KRB5_KTNAME', '${PREFIX}/private/dns.keytab')
349 if include:
350 self.setvar("NAMED_INCLUDE", 'include "%s";' % include)
351 else:
352 self.setvar("NAMED_INCLUDE", '')
354 self.run_cmd("mkdir -p ${PREFIX}/etc")
356 self.write_file("etc/named.conf", '''
357 options {
358 listen-on port 53 { ${INTERFACE_IP}; };
359 ${BIND_LISTEN_IPV6}
360 directory "${PREFIX}/var/named";
361 dump-file "${PREFIX}/var/named/data/cache_dump.db";
362 pid-file "${PREFIX}/var/named/named.pid";
363 statistics-file "${PREFIX}/var/named/data/named_stats.txt";
364 memstatistics-file "${PREFIX}/var/named/data/named_mem_stats.txt";
365 allow-query { any; };
366 recursion yes;
367 ${NAMED_TKEY_OPTION}
368 max-cache-ttl 10;
369 max-ncache-ttl 10;
371 forward only;
372 forwarders {
373 ${DNSSERVER};
378 key "rndc-key" {
379 algorithm hmac-md5;
380 secret "lA/cTrno03mt5Ju17ybEYw==";
383 controls {
384 inet ${INTERFACE_IP} port 953
385 allow { any; } keys { "rndc-key"; };
388 ${NAMED_INCLUDE}
389 ''')
391 # add forwarding for the windows domains
392 domains = self.get_domains()
393 for d in domains:
394 self.write_file('etc/named.conf',
396 zone "%s" IN {
397 type forward;
398 forward only;
399 forwarders {
403 ''' % (d, domains[d]),
404 mode='a')
407 self.write_file("etc/rndc.conf", '''
408 # Start of rndc.conf
409 key "rndc-key" {
410 algorithm hmac-md5;
411 secret "lA/cTrno03mt5Ju17ybEYw==";
414 options {
415 default-key "rndc-key";
416 default-server ${INTERFACE_IP};
417 default-port 953;
419 ''')
422 def stop_bind(self):
423 '''Stop our private BIND from listening and operating'''
424 self.rndc_cmd("stop", checkfail=False)
425 self.port_wait("${INTERFACE_IP}", 53, wait_for_fail=True)
427 self.run_cmd("rm -rf var/named")
430 def start_bind(self):
431 '''restart the test environment version of bind'''
432 self.info("Restarting bind9")
433 self.chdir('${PREFIX}')
435 self.run_cmd("mkdir -p var/named/data")
436 self.run_cmd("chown -R ${BIND_USER} var/named")
438 self.bind_child = self.run_child("${BIND9} -u ${BIND_USER} -n 1 -c ${PREFIX}/etc/named.conf -g")
440 self.port_wait("${INTERFACE_IP}", 53)
441 self.rndc_cmd("flush")
443 def restart_bind(self, kerberos_support=False, include=None):
444 self.configure_bind(kerberos_support=kerberos_support, include=include)
445 self.stop_bind()
446 self.start_bind()
448 def restore_resolv_conf(self):
449 '''restore the /etc/resolv.conf after testing is complete'''
450 if getattr(self, 'resolv_conf_backup', False):
451 self.info("restoring /etc/resolv.conf")
452 self.run_cmd("mv -f %s /etc/resolv.conf" % self.resolv_conf_backup)
455 def vm_poweroff(self, vmname, checkfail=True):
456 '''power off a VM'''
457 self.setvar('VMNAME', vmname)
458 self.run_cmd("${VM_POWEROFF}", checkfail=checkfail)
460 def vm_reset(self, vmname):
461 '''reset a VM'''
462 self.setvar('VMNAME', vmname)
463 self.run_cmd("${VM_RESET}")
465 def vm_restore(self, vmname, snapshot):
466 '''restore a VM'''
467 self.setvar('VMNAME', vmname)
468 self.setvar('SNAPSHOT', snapshot)
469 self.run_cmd("${VM_RESTORE}")
471 def ping_wait(self, hostname):
472 '''wait for a hostname to come up on the network'''
473 hostname = self.substitute(hostname)
474 loops=10
475 while loops > 0:
476 try:
477 self.run_cmd("ping -c 1 -w 10 %s" % hostname)
478 break
479 except:
480 loops = loops - 1
481 if loops == 0:
482 raise RuntimeError("Failed to ping %s" % hostname)
483 self.info("Host %s is up" % hostname)
485 def port_wait(self, hostname, port, retries=200, delay=3, wait_for_fail=False):
486 '''wait for a host to come up on the network'''
488 while retries > 0:
489 child = self.pexpect_spawn("nc -v -z -w 1 %s %u" % (hostname, port), crlf=False, timeout=1)
490 child.expect([pexpect.EOF, pexpect.TIMEOUT])
491 child.close()
492 i = child.exitstatus
493 if wait_for_fail:
494 #wait for timeout or fail
495 if i == None or i > 0:
496 return
497 else:
498 if i == 0:
499 return
501 time.sleep(delay)
502 retries -= 1
503 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
505 raise RuntimeError("gave up waiting for %s:%d" % (hostname, port))
507 def run_net_time(self, child):
508 '''run net time on windows'''
509 child.sendline("net time \\\\${HOSTNAME} /set")
510 child.expect("Do you want to set the local computer")
511 child.sendline("Y")
512 child.expect("The command completed successfully")
514 def run_date_time(self, child, time_tuple=None):
515 '''run date and time on windows'''
516 if time_tuple is None:
517 time_tuple = time.localtime()
518 child.sendline("date")
519 child.expect("Enter the new date:")
520 i = child.expect(["dd-mm-yy", "mm-dd-yy"])
521 if i == 0:
522 child.sendline(time.strftime("%d-%m-%y", time_tuple))
523 else:
524 child.sendline(time.strftime("%m-%d-%y", time_tuple))
525 child.expect("C:")
526 child.sendline("time")
527 child.expect("Enter the new time:")
528 child.sendline(time.strftime("%H:%M:%S", time_tuple))
529 child.expect("C:")
531 def get_ipconfig(self, child):
532 '''get the IP configuration of the child'''
533 child.sendline("ipconfig /all")
534 child.expect('Ethernet adapter ')
535 child.expect("[\w\s]+")
536 self.setvar("WIN_NIC", child.after)
537 child.expect(['IPv4 Address', 'IP Address'])
538 child.expect('\d+.\d+.\d+.\d+')
539 self.setvar('WIN_IPV4_ADDRESS', child.after)
540 child.expect('Subnet Mask')
541 child.expect('\d+.\d+.\d+.\d+')
542 self.setvar('WIN_SUBNET_MASK', child.after)
543 child.expect('Default Gateway')
544 i = child.expect(['\d+.\d+.\d+.\d+', "C:"])
545 if i == 0:
546 self.setvar('WIN_DEFAULT_GATEWAY', child.after)
547 child.expect("C:")
549 def get_is_dc(self, child):
550 '''check if a windows machine is a domain controller'''
551 child.sendline("dcdiag")
552 i = child.expect(["is not a [Directory Server|DC]",
553 "is not recognized as an internal or external command",
554 "Home Server = ",
555 "passed test Replications"])
556 if i == 0:
557 return False
558 if i == 1 or i == 3:
559 child.expect("C:")
560 child.sendline("net config Workstation")
561 child.expect("Workstation domain")
562 child.expect('[\S]+')
563 domain = child.after
564 i = child.expect(["Workstation Domain DNS Name", "Logon domain"])
565 '''If we get the Logon domain first, we are not in an AD domain'''
566 if i == 1:
567 return False
568 if domain.upper() == self.getvar("WIN_DOMAIN").upper():
569 return True
571 child.expect('[\S]+')
572 hostname = child.after
573 if hostname.upper() == self.getvar("WIN_HOSTNAME").upper():
574 return True
576 def set_noexpire(self, child, username):
577 """Ensure this user's password does not expire"""
578 child.sendline('wmic useraccount where name="%s" set PasswordExpires=FALSE' % username)
579 child.expect("update successful")
580 child.expect("C:")
582 def run_tlntadmn(self, child):
583 '''remove the annoying telnet restrictions'''
584 child.sendline('tlntadmn config maxconn=1024')
585 child.expect(["The settings were successfully updated", "Access is denied"])
586 child.expect("C:")
588 def disable_firewall(self, child):
589 '''remove the annoying firewall'''
590 child.sendline('netsh advfirewall set allprofiles state off')
591 i = child.expect(["Ok", "The following command was not found: advfirewall set allprofiles state off", "The requested operation requires elevation", "Access is denied"])
592 child.expect("C:")
593 if i == 1:
594 child.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
595 i = child.expect(["Ok", "The following command was not found", "Access is denied"])
596 if i != 0:
597 self.info("Firewall disable failed - ignoring")
598 child.expect("C:")
600 def set_dns(self, child):
601 child.sendline('netsh interface ip set dns "${WIN_NIC}" static ${INTERFACE_IP} primary')
602 i = child.expect(['C:', pexpect.EOF, pexpect.TIMEOUT], timeout=5)
603 if i > 0:
604 return True
605 else:
606 return False
608 def set_ip(self, child):
609 """fix the IP address to the same value it had when we
610 connected, but don't use DHCP, and force the DNS server to our
611 DNS server. This allows DNS updates to run"""
612 self.get_ipconfig(child)
613 if self.getvar("WIN_IPV4_ADDRESS") != self.getvar("WIN_IP"):
614 raise RuntimeError("ipconfig address %s != nmblookup address %s" % (self.getvar("WIN_IPV4_ADDRESS"),
615 self.getvar("WIN_IP")))
616 child.sendline('netsh')
617 child.expect('netsh>')
618 child.sendline('offline')
619 child.expect('netsh>')
620 child.sendline('routing ip add persistentroute dest=0.0.0.0 mask=0.0.0.0 name="${WIN_NIC}" nhop=${WIN_DEFAULT_GATEWAY}')
621 child.expect('netsh>')
622 child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1 store=persistent')
623 i = child.expect(['The syntax supplied for this command is not valid. Check help for the correct syntax', 'netsh>', pexpect.EOF, pexpect.TIMEOUT], timeout=5)
624 if i == 0:
625 child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1')
626 child.expect('netsh>')
627 child.sendline('commit')
628 child.sendline('online')
629 child.sendline('exit')
631 child.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=5)
632 return True
635 def resolve_ip(self, hostname, retries=60, delay=5):
636 '''resolve an IP given a hostname, assuming NBT'''
637 while retries > 0:
638 child = self.pexpect_spawn("bin/nmblookup %s" % hostname)
639 i = 0
640 while i == 0:
641 i = child.expect(["querying", '\d+.\d+.\d+.\d+', hostname, "Lookup failed"])
642 if i == 0:
643 child.expect("\r")
644 if i == 1:
645 return child.after
646 retries -= 1
647 time.sleep(delay)
648 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
649 raise RuntimeError("Failed to resolve IP of %s" % hostname)
652 def open_telnet(self, hostname, username, password, retries=60, delay=5, set_time=False, set_ip=False,
653 disable_firewall=True, run_tlntadmn=True, set_noexpire=False):
654 '''open a telnet connection to a windows server, return the pexpect child'''
655 set_route = False
656 set_dns = False
657 set_telnetclients = True
658 if self.getvar('WIN_IP'):
659 ip = self.getvar('WIN_IP')
660 else:
661 ip = self.resolve_ip(hostname)
662 self.setvar('WIN_IP', ip)
663 while retries > 0:
664 child = self.pexpect_spawn("telnet " + ip + " -l '" + username + "'")
665 i = child.expect(["Welcome to Microsoft Telnet Service",
666 "Denying new connections due to the limit on number of connections",
667 "No more connections are allowed to telnet server",
668 "Unable to connect to remote host",
669 "No route to host",
670 "Connection refused",
671 pexpect.EOF])
672 if i != 0:
673 child.close()
674 time.sleep(delay)
675 retries -= 1
676 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
677 continue
678 child.expect("password:")
679 child.sendline(password)
680 i = child.expect(["C:",
681 "TelnetClients",
682 "Denying new connections due to the limit on number of connections",
683 "No more connections are allowed to telnet server",
684 "Unable to connect to remote host",
685 "No route to host",
686 "Connection refused",
687 pexpect.EOF])
688 if i == 1:
689 if set_telnetclients:
690 self.run_cmd('bin/net rpc group addmem TelnetClients "authenticated users" -S $WIN_IP -U$WIN_USER%$WIN_PASS')
691 child.close()
692 retries -= 1
693 set_telnetclients = False
694 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
695 continue
696 else:
697 raise RuntimeError("Failed to connect with telnet due to missing TelnetClients membership")
699 if i != 0:
700 child.close()
701 time.sleep(delay)
702 retries -= 1
703 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
704 continue
705 if set_dns:
706 set_dns = False
707 if self.set_dns(child):
708 continue;
709 if set_route:
710 child.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
711 child.expect("C:")
712 set_route = False
713 if set_time:
714 self.run_date_time(child, None)
715 set_time = False
716 if run_tlntadmn:
717 self.run_tlntadmn(child)
718 run_tlntadmn = False
719 if set_noexpire:
720 self.set_noexpire(child, username)
721 set_noexpire = False
722 if disable_firewall:
723 self.disable_firewall(child)
724 disable_firewall = False
725 if set_ip:
726 set_ip = False
727 if self.set_ip(child):
728 set_route = True
729 set_dns = True
730 continue
731 return child
732 raise RuntimeError("Failed to connect with telnet")
734 def kinit(self, username, password):
735 '''use kinit to setup a credentials cache'''
736 self.run_cmd("kdestroy")
737 self.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
738 username = self.substitute(username)
739 s = username.split('@')
740 if len(s) > 0:
741 s[1] = s[1].upper()
742 username = '@'.join(s)
743 child = self.pexpect_spawn('kinit ' + username)
744 child.expect("Password")
745 child.sendline(password)
746 child.expect(pexpect.EOF)
747 child.close()
748 if child.exitstatus != 0:
749 raise RuntimeError("kinit failed with status %d" % child.exitstatus)
751 def get_domains(self):
752 '''return a dictionary of DNS domains and IPs for named.conf'''
753 ret = {}
754 for v in self.vars:
755 if v[-6:] == "_REALM":
756 base = v[:-6]
757 if base + '_IP' in self.vars:
758 ret[self.vars[base + '_REALM']] = self.vars[base + '_IP']
759 return ret
761 def wait_reboot(self, retries=3):
762 '''wait for a VM to reboot'''
764 # first wait for it to shutdown
765 self.port_wait("${WIN_IP}", 139, wait_for_fail=True, delay=6)
767 # now wait for it to come back. If it fails to come back
768 # then try resetting it
769 while retries > 0:
770 try:
771 self.port_wait("${WIN_IP}", 139)
772 return
773 except:
774 retries -= 1
775 self.vm_reset("${WIN_VM}")
776 self.info("retrying reboot (retries=%u)" % retries)
777 raise RuntimeError(self.substitute("VM ${WIN_VM} failed to reboot"))
779 def get_vms(self):
780 '''return a dictionary of all the configured VM names'''
781 ret = []
782 for v in self.vars:
783 if v[-3:] == "_VM":
784 ret.append(self.vars[v])
785 return ret
788 def run_dcpromo_as_first_dc(self, vm, func_level=None):
789 self.setwinvars(vm)
790 self.info("Configuring a windows VM ${WIN_VM} at the first DC in the domain using dcpromo")
791 child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_time=True)
792 if self.get_is_dc(child):
793 return
795 if func_level == '2008r2':
796 self.setvar("FUNCTION_LEVEL_INT", str(4))
797 elif func_level == '2003':
798 self.setvar("FUNCTION_LEVEL_INT", str(1))
799 else:
800 self.setvar("FUNCTION_LEVEL_INT", str(0))
802 child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_ip=True, set_noexpire=True)
804 """This server must therefore not yet be a directory server, so we must promote it"""
805 child.sendline("copy /Y con answers.txt")
806 child.sendline('''
807 [DCInstall]
808 ; New forest promotion
809 ReplicaOrNewDomain=Domain
810 NewDomain=Forest
811 NewDomainDNSName=${WIN_REALM}
812 ForestLevel=${FUNCTION_LEVEL_INT}
813 DomainNetbiosName=${WIN_DOMAIN}
814 DomainLevel=${FUNCTION_LEVEL_INT}
815 InstallDNS=Yes
816 ConfirmGc=Yes
817 CreateDNSDelegation=No
818 DatabasePath="C:\Windows\NTDS"
819 LogPath="C:\Windows\NTDS"
820 SYSVOLPath="C:\Windows\SYSVOL"
821 ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
822 SafeModeAdminPassword=${WIN_PASS}
823 ; Run-time flags (optional)
824 RebootOnCompletion=No
825 \x1a
826 ''')
827 child.expect("copied.")
828 child.expect("C:")
829 child.expect("C:")
830 child.sendline("dcpromo /answer:answers.txt")
831 i = child.expect(["You must restart this computer", "failed", "Active Directory Domain Services was not installed", "C:"], timeout=240)
832 if i == 1 or i == 2:
833 raise Exception("dcpromo failed")
834 child.sendline("shutdown -r -t 0")
835 self.port_wait("${WIN_IP}", 139, wait_for_fail=True)
836 self.port_wait("${WIN_IP}", 139)
837 self.retry_cmd("host -t SRV _ldap._tcp.${WIN_REALM} ${WIN_IP}", ['has SRV record'], retries=60, delay=5 )
840 def start_winvm(self, vm):
841 '''start a Windows VM'''
842 self.setwinvars(vm)
844 self.info("Joining a windows box to the domain")
845 self.vm_poweroff("${WIN_VM}", checkfail=False)
846 self.vm_restore("${WIN_VM}", "${WIN_SNAPSHOT}")
848 def run_winjoin(self, vm, domain, username="administrator", password="${PASSWORD1}"):
849 '''join a windows box to a domain'''
850 child = self.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time=True, set_ip=True, set_noexpire=True)
851 retries = 5
852 while retries > 0:
853 child.sendline("ipconfig /flushdns")
854 child.expect("C:")
855 child.sendline("netdom join ${WIN_HOSTNAME} /Domain:%s /UserD:%s /PasswordD:%s" % (domain, username, password))
856 i = child.expect(["The command completed successfully",
857 "The specified domain either does not exist or could not be contacted."])
858 if i == 0:
859 break
860 time.sleep(10)
861 retries -= 1
863 child.expect("C:")
864 child.sendline("shutdown /r -t 0")
865 self.wait_reboot()
866 child = self.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time=True, set_ip=True)
867 child.sendline("ipconfig /registerdns")
868 child.expect("Registration of the DNS resource records for all adapters of this computer has been initiated. Any errors will be reported in the Event Viewer")
869 child.expect("C:")
872 def test_remote_smbclient(self, vm, username="${WIN_USER}", password="${WIN_PASS}", args=""):
873 '''test smbclient against remote server'''
874 self.setwinvars(vm)
875 self.info('Testing smbclient')
876 self.chdir('${PREFIX}')
877 smbclient = self.getvar("smbclient")
878 self.cmd_contains("%s --version" % (smbclient), ["${SAMBA_VERSION}"])
879 self.retry_cmd('%s -L ${WIN_HOSTNAME} -U%s%%%s %s' % (smbclient, username, password, args), ["IPC"], retries=60, delay=5)
881 def test_net_use(self, vm, realm, domain, username, password):
882 self.setwinvars(vm)
883 self.info('Testing net use against Samba3 member')
884 child = self.open_telnet("${WIN_HOSTNAME}", "%s\\%s" % (domain, username), password)
885 child.sendline("net use t: \\\\${HOSTNAME}.%s\\test" % realm)
886 child.expect("The command completed successfully")
889 def setup(self, testname, subdir):
890 '''setup for main tests, parsing command line'''
891 self.parser.add_option("--conf", type='string', default='', help='config file')
892 self.parser.add_option("--skip", type='string', default='', help='list of steps to skip (comma separated)')
893 self.parser.add_option("--vms", type='string', default=None, help='list of VMs to use (comma separated)')
894 self.parser.add_option("--list", action='store_true', default=False, help='list the available steps')
895 self.parser.add_option("--rebase", action='store_true', default=False, help='do a git pull --rebase')
896 self.parser.add_option("--clean", action='store_true', default=False, help='clean the tree')
897 self.parser.add_option("--prefix", type='string', default=None, help='override install prefix')
898 self.parser.add_option("--sourcetree", type='string', default=None, help='override sourcetree location')
899 self.parser.add_option("--nocleanup", action='store_true', default=False, help='disable cleanup code')
900 self.parser.add_option("--use-ntvfs", action='store_true', default=False, help='use NTVFS for the fileserver')
901 self.parser.add_option("--dns-backend", type="choice",
902 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
903 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server, " \
904 "BIND9_FLATFILE uses bind9 text database to store zone information, " \
905 "BIND9_DLZ uses samba4 AD to store zone information (default), " \
906 "NONE skips the DNS setup entirely (not recommended)",
907 default="BIND9_DLZ")
909 self.opts, self.args = self.parser.parse_args()
911 if not self.opts.conf:
912 print("Please specify a config file with --conf")
913 sys.exit(1)
915 # we don't need fsync safety in these tests
916 self.putenv('TDB_NO_FSYNC', '1')
918 self.load_config(self.opts.conf)
920 nameserver = self.get_nameserver()
921 if nameserver == self.getvar('INTERFACE_IP'):
922 raise RuntimeError("old /etc/resolv.conf must not contain %s as a nameserver, this will create loops with the generated dns configuration" % nameserver)
923 self.setvar('DNSSERVER', nameserver)
925 self.set_skip(self.opts.skip)
926 self.set_vms(self.opts.vms)
928 if self.opts.list:
929 self.list_steps_mode()
931 if self.opts.prefix:
932 self.setvar('PREFIX', self.opts.prefix)
934 if self.opts.sourcetree:
935 self.setvar('SOURCETREE', self.opts.sourcetree)
937 if self.opts.rebase:
938 self.info('rebasing')
939 self.chdir('${SOURCETREE}')
940 self.run_cmd('git pull --rebase')
942 if self.opts.clean:
943 self.info('cleaning')
944 self.chdir('${SOURCETREE}/' + subdir)
945 self.run_cmd('make clean')
947 if self.opts.use_ntvfs:
948 self.setvar('USE_NTVFS', "--use-ntvfs")
949 else:
950 self.setvar('USE_NTVFS', "")
952 self.setvar('NAMESERVER_BACKEND', self.opts.dns_backend)
954 if self.opts.dns_backend == 'SAMBA_INTERNAL':
955 self.setvar('ALLOW_DNS_UPDATES', '--option=allow dns updates = True')
956 # we need recursive queries, since host expects answers with RA-bit
957 self.setvar('DNS_RECURSIVE_QUERIES', '--option=dns recursive queries = Yes')
958 self.setvar('DNS_FORWARDER', "--option=dns forwarder = %s" % nameserver)
959 else:
960 self.setvar('ALLOW_DNS_UPDATES', '')
961 self.setvar('DNS_RECURSIVE_QUERIES', '')
962 self.setvar('DNS_FORWARDER', '')