s3-wintest Add test of 'net use' against the Samba3 member
[garming/samba-autobuild/.git] / wintest / wintest.py
1 #!/usr/bin/env python
2
3 '''automated testing library for testing Samba against windows'''
4
5 import pexpect, subprocess
6 import optparse
7 import sys, os, time, re
8
9 class wintest():
10     '''testing of Samba against windows VMs'''
11
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")
18
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')
28
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)
35
36     def setvar(self, varname, value):
37         '''set a substitution variable'''
38         self.vars[varname] = value
39
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]
45
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)
54
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")
62
63     def info(self, msg):
64         '''print some information'''
65         if not self.list_mode:
66             print(self.substitute(msg))
67
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)
81
82     def list_steps_mode(self):
83         '''put wintest in step listing mode'''
84         self.list_mode = True
85
86     def set_skip(self, skiplist):
87         '''set a list of tests to skip'''
88         self.skiplist = skiplist.split(',')
89
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)
97
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
104
105     def substitute(self, text):
106         """Substitute strings of the form ${NAME} in text, replacing
107         with substitutions from vars.
108         """
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
114
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
130
131     def have_var(self, varname):
132         '''see if a variable has been set'''
133         return varname in self.vars
134
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
142
143     def putenv(self, key, value):
144         '''putenv with substitution'''
145         os.environ[key] = self.substitute(value)
146
147     def chdir(self, dir):
148         '''chdir with substitution'''
149         os.chdir(self.substitute(dir))
150
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)
155
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()
161
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)
179
180
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
197
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)
202
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'''
206
207         if isinstance(contains, str):
208             contains = [contains]
209
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:]
238
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)
252
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)
258
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)
264
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)
275
276         ret.old_sendline = ret.sendline
277         ret.sendline = sendline_sub
278         ret.old_expect = ret.expect
279         ret.expect = expect_sub
280
281         return ret
282
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
292
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)
296
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
306
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
312
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';
324
325     def configure_bind(self, kerberos_support=False, include=None):
326         self.chdir('${PREFIX}')
327
328         nameserver = self.get_nameserver()
329         if nameserver == self.getvar('INTERFACE_IP'):
330             raise RuntimeError("old /etc/resolv.conf must not contain %s as a nameserver, this will create loops with the generated dns configuration" % nameserver)
331         self.setvar('DNSSERVER', nameserver)
332
333         if self.getvar('INTERFACE_IPV6'):
334             ipv6_listen = 'listen-on-v6 port 53 { ${INTERFACE_IPV6}; };'
335         else:
336             ipv6_listen = ''
337         self.setvar('BIND_LISTEN_IPV6', ipv6_listen)
338
339         if not kerberos_support:
340             self.setvar("NAMED_TKEY_OPTION", "")
341         else:
342             if self.named_supports_gssapi_keytab():
343                 self.setvar("NAMED_TKEY_OPTION",
344                          'tkey-gssapi-keytab "${PREFIX}/private/dns.keytab";')
345             else:
346                 self.info("LCREALM=${LCREALM}")
347                 self.setvar("NAMED_TKEY_OPTION",
348                          '''tkey-gssapi-credential "DNS/${LCREALM}";
349                             tkey-domain "${LCREALM}";
350                  ''')
351             self.putenv('KEYTAB_FILE', '${PREFIX}/private/dns.keytab')
352             self.putenv('KRB5_KTNAME', '${PREFIX}/private/dns.keytab')
353
354         if include:
355             self.setvar("NAMED_INCLUDE", 'include "%s";' % include)
356         else:
357             self.setvar("NAMED_INCLUDE", '')
358
359         self.run_cmd("mkdir -p ${PREFIX}/etc")
360
361         self.write_file("etc/named.conf", '''
362 options {
363         listen-on port 53 { ${INTERFACE_IP};  };
364         ${BIND_LISTEN_IPV6}
365         directory       "${PREFIX}/var/named";
366         dump-file       "${PREFIX}/var/named/data/cache_dump.db";
367         pid-file        "${PREFIX}/var/named/named.pid";
368         statistics-file "${PREFIX}/var/named/data/named_stats.txt";
369         memstatistics-file "${PREFIX}/var/named/data/named_mem_stats.txt";
370         allow-query     { any; };
371         recursion yes;
372         ${NAMED_TKEY_OPTION}
373         max-cache-ttl 10;
374         max-ncache-ttl 10;
375
376         forward only;
377         forwarders {
378                   ${DNSSERVER};
379         };
380
381 };
382
383 key "rndc-key" {
384         algorithm hmac-md5;
385         secret "lA/cTrno03mt5Ju17ybEYw==";
386 };
387
388 controls {
389         inet ${INTERFACE_IP} port 953
390         allow { any; } keys { "rndc-key"; };
391 };
392
393 ${NAMED_INCLUDE}
394 ''')
395
396         # add forwarding for the windows domains
397         domains = self.get_domains()
398         for d in domains:
399             self.write_file('etc/named.conf',
400                          '''
401 zone "%s" IN {
402       type forward;
403       forward only;
404       forwarders {
405          %s;
406       };
407 };
408 ''' % (d, domains[d]),
409                      mode='a')
410
411
412         self.write_file("etc/rndc.conf", '''
413 # Start of rndc.conf
414 key "rndc-key" {
415         algorithm hmac-md5;
416         secret "lA/cTrno03mt5Ju17ybEYw==";
417 };
418
419 options {
420         default-key "rndc-key";
421         default-server  ${INTERFACE_IP};
422         default-port 953;
423 };
424 ''')
425
426
427     def stop_bind(self):
428         '''Stop our private BIND from listening and operating'''
429         self.rndc_cmd("stop", checkfail=False)
430         self.port_wait("${INTERFACE_IP}", 53, wait_for_fail=True)
431
432         self.run_cmd("rm -rf var/named")
433
434
435     def start_bind(self):
436         '''restart the test environment version of bind'''
437         self.info("Restarting bind9")
438         self.chdir('${PREFIX}')
439
440         self.set_nameserver(self.getvar('INTERFACE_IP'))
441
442         self.run_cmd("mkdir -p var/named/data")
443         self.run_cmd("chown -R ${BIND_USER} var/named")
444
445         self.bind_child = self.run_child("${BIND9} -u ${BIND_USER} -n 1 -c ${PREFIX}/etc/named.conf -g")
446
447         self.port_wait("${INTERFACE_IP}", 53)
448         self.rndc_cmd("flush")
449
450     def restart_bind(self, kerberos_support=False, include=None):
451         self.configure_bind(kerberos_support=kerberos_support, include=include)
452         self.stop_bind()
453         self.start_bind()
454
455     def restore_resolv_conf(self):
456         '''restore the /etc/resolv.conf after testing is complete'''
457         if getattr(self, 'resolv_conf_backup', False):
458             self.info("restoring /etc/resolv.conf")
459             self.run_cmd("mv -f %s /etc/resolv.conf" % self.resolv_conf_backup)
460
461
462     def vm_poweroff(self, vmname, checkfail=True):
463         '''power off a VM'''
464         self.setvar('VMNAME', vmname)
465         self.run_cmd("${VM_POWEROFF}", checkfail=checkfail)
466
467     def vm_reset(self, vmname):
468         '''reset a VM'''
469         self.setvar('VMNAME', vmname)
470         self.run_cmd("${VM_RESET}")
471
472     def vm_restore(self, vmname, snapshot):
473         '''restore a VM'''
474         self.setvar('VMNAME', vmname)
475         self.setvar('SNAPSHOT', snapshot)
476         self.run_cmd("${VM_RESTORE}")
477
478     def ping_wait(self, hostname):
479         '''wait for a hostname to come up on the network'''
480         hostname = self.substitute(hostname)
481         loops=10
482         while loops > 0:
483             try:
484                 self.run_cmd("ping -c 1 -w 10 %s" % hostname)
485                 break
486             except:
487                 loops = loops - 1
488         if loops == 0:
489             raise RuntimeError("Failed to ping %s" % hostname)
490         self.info("Host %s is up" % hostname)
491
492     def port_wait(self, hostname, port, retries=200, delay=3, wait_for_fail=False):
493         '''wait for a host to come up on the network'''
494         self.retry_cmd("nc -v -z -w 1 %s %u" % (hostname, port), ['succeeded'],
495                        retries=retries, delay=delay, wait_for_fail=wait_for_fail)
496
497     def run_net_time(self, child):
498         '''run net time on windows'''
499         child.sendline("net time \\\\${HOSTNAME} /set")
500         child.expect("Do you want to set the local computer")
501         child.sendline("Y")
502         child.expect("The command completed successfully")
503
504     def run_date_time(self, child, time_tuple=None):
505         '''run date and time on windows'''
506         if time_tuple is None:
507             time_tuple = time.localtime()
508         child.sendline("date")
509         child.expect("Enter the new date:")
510         i = child.expect(["dd-mm-yy", "mm-dd-yy"])
511         if i == 0:
512             child.sendline(time.strftime("%d-%m-%y", time_tuple))
513         else:
514             child.sendline(time.strftime("%m-%d-%y", time_tuple))
515         child.expect("C:")
516         child.sendline("time")
517         child.expect("Enter the new time:")
518         child.sendline(time.strftime("%H:%M:%S", time_tuple))
519         child.expect("C:")
520
521     def get_ipconfig(self, child):
522         '''get the IP configuration of the child'''
523         child.sendline("ipconfig /all")
524         child.expect('Ethernet adapter ')
525         child.expect("[\w\s]+")
526         self.setvar("WIN_NIC", child.after)
527         child.expect(['IPv4 Address', 'IP Address'])
528         child.expect('\d+.\d+.\d+.\d+')
529         self.setvar('WIN_IPV4_ADDRESS', child.after)
530         child.expect('Subnet Mask')
531         child.expect('\d+.\d+.\d+.\d+')
532         self.setvar('WIN_SUBNET_MASK', child.after)
533         child.expect('Default Gateway')
534         child.expect('\d+.\d+.\d+.\d+')
535         self.setvar('WIN_DEFAULT_GATEWAY', child.after)
536         child.expect("C:")
537
538     def get_is_dc(self, child):
539         '''check if a windows machine is a domain controller'''
540         child.sendline("dcdiag")
541         i = child.expect(["is not a Directory Server",
542                           "is not recognized as an internal or external command",
543                           "Home Server = ",
544                           "passed test Replications"])
545         if i == 0:
546             return False
547         if i == 1 or i == 3:
548             child.expect("C:")
549             child.sendline("net config Workstation")
550             child.expect("Workstation domain")
551             child.expect('[\S]+')
552             domain = child.after
553             i = child.expect(["Workstation Domain DNS Name", "Logon domain"])
554             '''If we get the Logon domain first, we are not in an AD domain'''
555             if i == 1:
556                 return False
557             if domain.upper() == self.getvar("WIN_DOMAIN").upper():
558                 return True
559
560         child.expect('[\S]+')
561         hostname = child.after
562         if hostname.upper() == self.getvar("WIN_HOSTNAME").upper():
563             return True
564
565     def set_noexpire(self, child, username):
566         """Ensure this user's password does not expire"""
567         child.sendline('wmic useraccount where name="%s" set PasswordExpires=FALSE' % username)
568         child.expect("update successful")
569         child.expect("C:")
570
571     def run_tlntadmn(self, child):
572         '''remove the annoying telnet restrictions'''
573         child.sendline('tlntadmn config maxconn=1024')
574         child.expect(["The settings were successfully updated", "Access is denied"])
575         child.expect("C:")
576
577     def disable_firewall(self, child):
578         '''remove the annoying firewall'''
579         child.sendline('netsh advfirewall set allprofiles state off')
580         i = child.expect(["Ok", "The following command was not found: advfirewall set allprofiles state off"])
581         child.expect("C:")
582         if i == 1:
583             child.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
584             i = child.expect(["Ok", "The following command was not found"])
585             if i != 0:
586                 self.info("Firewall disable failed - ignoring")
587             child.expect("C:")
588
589     def set_dns(self, child):
590         child.sendline('netsh interface ip set dns "${WIN_NIC}" static ${INTERFACE_IP} primary')
591         i = child.expect(['C:', pexpect.EOF, pexpect.TIMEOUT], timeout=5)
592         if i > 0:
593             return True
594         else:
595             return False
596
597     def set_ip(self, child):
598         """fix the IP address to the same value it had when we
599         connected, but don't use DHCP, and force the DNS server to our
600         DNS server.  This allows DNS updates to run"""
601         self.get_ipconfig(child)
602         if self.getvar("WIN_IPV4_ADDRESS") != self.getvar("WIN_IP"):
603             raise RuntimeError("ipconfig address %s != nmblookup address %s" % (self.getvar("WIN_IPV4_ADDRESS"),
604                                                                                 self.getvar("WIN_IP")))
605         child.sendline('netsh')
606         child.expect('netsh>')
607         child.sendline('offline')
608         child.expect('netsh>')
609         child.sendline('routing ip add persistentroute dest=0.0.0.0 mask=0.0.0.0 name="${WIN_NIC}" nhop=${WIN_DEFAULT_GATEWAY}')
610         child.expect('netsh>')
611         child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1 store=persistent')
612         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)
613         if i == 0:
614             child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1')
615             child.expect('netsh>')
616         child.sendline('commit')
617         child.sendline('online')
618         child.sendline('exit')
619
620         child.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=5)
621         return True
622
623
624     def resolve_ip(self, hostname, retries=60, delay=5):
625         '''resolve an IP given a hostname, assuming NBT'''
626         while retries > 0:
627             child = self.pexpect_spawn("bin/nmblookup %s" % hostname)
628             i = 0
629             while i == 0:
630                 i = child.expect(["querying", '\d+.\d+.\d+.\d+', hostname, "Lookup failed"])
631                 if i == 0:
632                     child.expect("\r")
633             if i == 1:
634                 return child.after
635             retries -= 1
636             time.sleep(delay)
637             self.info("retrying (retries=%u delay=%u)" % (retries, delay))
638         raise RuntimeError("Failed to resolve IP of %s" % hostname)
639
640
641     def open_telnet(self, hostname, username, password, retries=60, delay=5, set_time=False, set_ip=False,
642                     disable_firewall=True, run_tlntadmn=True, set_noexpire=False):
643         '''open a telnet connection to a windows server, return the pexpect child'''
644         set_route = False
645         set_dns = False
646         if self.getvar('WIN_IP'):
647             ip = self.getvar('WIN_IP')
648         else:
649             ip = self.resolve_ip(hostname)
650             self.setvar('WIN_IP', ip)
651         while retries > 0:
652             child = self.pexpect_spawn("telnet " + ip + " -l '" + username + "'")
653             i = child.expect(["Welcome to Microsoft Telnet Service",
654                               "Denying new connections due to the limit on number of connections",
655                               "No more connections are allowed to telnet server",
656                               "Unable to connect to remote host",
657                               "No route to host",
658                               "Connection refused",
659                               pexpect.EOF])
660             if i != 0:
661                 child.close()
662                 time.sleep(delay)
663                 retries -= 1
664                 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
665                 continue
666             child.expect("password:")
667             child.sendline(password)
668             i = child.expect(["C:",
669                               "Denying new connections due to the limit on number of connections",
670                               "No more connections are allowed to telnet server",
671                               "Unable to connect to remote host",
672                               "No route to host",
673                               "Connection refused",
674                               pexpect.EOF])
675             if i != 0:
676                 child.close()
677                 time.sleep(delay)
678                 retries -= 1
679                 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
680                 continue
681             if set_dns:
682                 set_dns = False
683                 if self.set_dns(child):
684                     continue;
685             if set_route:
686                 child.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
687                 child.expect("C:")
688                 set_route = False
689             if set_time:
690                 self.run_date_time(child, None)
691                 set_time = False
692             if run_tlntadmn:
693                 self.run_tlntadmn(child)
694                 run_tlntadmn = False
695             if set_noexpire:
696                 self.set_noexpire(child, username)
697                 set_noexpire = False
698             if disable_firewall:
699                 self.disable_firewall(child)
700                 disable_firewall = False
701             if set_ip:
702                 set_ip = False
703                 if self.set_ip(child):
704                     set_route = True
705                     set_dns = True
706                 continue
707             return child
708         raise RuntimeError("Failed to connect with telnet")
709
710     def kinit(self, username, password):
711         '''use kinit to setup a credentials cache'''
712         self.run_cmd("kdestroy")
713         self.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
714         username = self.substitute(username)
715         s = username.split('@')
716         if len(s) > 0:
717             s[1] = s[1].upper()
718         username = '@'.join(s)
719         child = self.pexpect_spawn('kinit ' + username)
720         child.expect("Password")
721         child.sendline(password)
722         child.expect(pexpect.EOF)
723         child.close()
724         if child.exitstatus != 0:
725             raise RuntimeError("kinit failed with status %d" % child.exitstatus)
726
727     def get_domains(self):
728         '''return a dictionary of DNS domains and IPs for named.conf'''
729         ret = {}
730         for v in self.vars:
731             if v[-6:] == "_REALM":
732                 base = v[:-6]
733                 if base + '_IP' in self.vars:
734                     ret[self.vars[base + '_REALM']] = self.vars[base + '_IP']
735         return ret
736
737     def wait_reboot(self, retries=3):
738         '''wait for a VM to reboot'''
739
740         # first wait for it to shutdown
741         self.port_wait("${WIN_IP}", 139, wait_for_fail=True, delay=6)
742
743         # now wait for it to come back. If it fails to come back
744         # then try resetting it
745         while retries > 0:
746             try:
747                 self.port_wait("${WIN_IP}", 139)
748                 return
749             except:
750                 retries -= 1
751                 self.vm_reset("${WIN_VM}")
752                 self.info("retrying reboot (retries=%u)" % retries)
753         raise RuntimeError(self.substitute("VM ${WIN_VM} failed to reboot"))
754
755     def get_vms(self):
756         '''return a dictionary of all the configured VM names'''
757         ret = []
758         for v in self.vars:
759             if v[-3:] == "_VM":
760                 ret.append(self.vars[v])
761         return ret
762
763
764     def run_dcpromo_as_first_dc(self, vm, func_level=None):
765         self.setwinvars(vm)
766         self.info("Configuring a windows VM ${WIN_VM} at the first DC in the domain using dcpromo")
767         child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_time=True)
768         if self.get_is_dc(child):
769             return
770
771         if func_level == '2008r2':
772             self.setvar("FUNCTION_LEVEL_INT", str(4))
773         elif func_level == '2003':
774             self.setvar("FUNCTION_LEVEL_INT", str(1))
775         else:
776             self.setvar("FUNCTION_LEVEL_INT", str(0))
777
778         child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_ip=True, set_noexpire=True)
779
780         """This server must therefore not yet be a directory server, so we must promote it"""
781         child.sendline("copy /Y con answers.txt")
782         child.sendline('''
783 [DCInstall]
784 ; New forest promotion
785 ReplicaOrNewDomain=Domain
786 NewDomain=Forest
787 NewDomainDNSName=${WIN_REALM}
788 ForestLevel=${FUNCTION_LEVEL_INT}
789 DomainNetbiosName=${WIN_DOMAIN}
790 DomainLevel=${FUNCTION_LEVEL_INT}
791 InstallDNS=Yes
792 ConfirmGc=Yes
793 CreateDNSDelegation=No
794 DatabasePath="C:\Windows\NTDS"
795 LogPath="C:\Windows\NTDS"
796 SYSVOLPath="C:\Windows\SYSVOL"
797 ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
798 SafeModeAdminPassword=${WIN_PASS}
799 ; Run-time flags (optional)
800 RebootOnCompletion=No
801 \1a
802 ''')
803         child.expect("copied.")
804         child.expect("C:")
805         child.expect("C:")
806         child.sendline("dcpromo /answer:answers.txt")
807         i = child.expect(["You must restart this computer", "failed", "Active Directory Domain Services was not installed", "C:"], timeout=240)
808         if i == 1 or i == 2:
809             raise Exception("dcpromo failed")
810         child.sendline("shutdown -r -t 0")
811         self.port_wait("${WIN_IP}", 139, wait_for_fail=True)
812         self.port_wait("${WIN_IP}", 139)
813         self.retry_cmd("host -t SRV _ldap._tcp.${WIN_REALM} ${WIN_IP}", ['has SRV record'] )
814
815
816     def start_winvm(self, vm):
817         '''start a Windows VM'''
818         self.setwinvars(vm)
819         
820         self.info("Joining a windows box to the domain")
821         self.vm_poweroff("${WIN_VM}", checkfail=False)
822         self.vm_restore("${WIN_VM}", "${WIN_SNAPSHOT}")
823
824     def run_winjoin(self, vm, domain, username="administrator", password="${PASSWORD1}"):
825         '''join a windows box to a domain'''
826         child = self.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time=True, set_ip=True, set_noexpire=True)
827         child.sendline("ipconfig /flushdns")
828         child.expect("C:")
829         child.sendline("netdom join ${WIN_HOSTNAME} /Domain:%s /UserD:%s /PasswordD:%s" % (domain, username, password))
830         child.expect("The command completed successfully")
831         child.expect("C:")
832         child.sendline("shutdown /r -t 0")
833         self.wait_reboot()
834         child = self.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time=True, set_ip=True)
835         child.sendline("ipconfig /registerdns")
836         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")
837         child.expect("C:")
838
839
840     def test_remote_smbclient(self, vm, username="${WIN_USER}", password="${WIN_PASS}", args=""):
841         '''test smbclient against remote server'''
842         self.setwinvars(vm)
843         self.info('Testing smbclient')
844         self.chdir('${PREFIX}')
845         self.cmd_contains("bin/smbclient --version", ["${SAMBA_VERSION}"])
846         self.retry_cmd('bin/smbclient -L ${WIN_HOSTNAME} -U%s%%%s %s' % (username, password, args), ["IPC"])
847
848     def test_net_use(self, vm, domain, username, password):
849         self.setwinvars(vm)
850         self.info('Testing net use against Samba3 member')
851         child = self.open_telnet("${WIN_HOSTNAME}", "%s\\%s" % (domain, username), password)
852         child.sendline("net use t: \\\\${HOSTNAME}.${LCREALM}\\test")
853         child.expect("The command completed successfully")
854
855
856     def setup(self, testname, subdir):
857         '''setup for main tests, parsing command line'''
858         self.parser.add_option("--conf", type='string', default='', help='config file')
859         self.parser.add_option("--skip", type='string', default='', help='list of steps to skip (comma separated)')
860         self.parser.add_option("--vms", type='string', default=None, help='list of VMs to use (comma separated)')
861         self.parser.add_option("--list", action='store_true', default=False, help='list the available steps')
862         self.parser.add_option("--rebase", action='store_true', default=False, help='do a git pull --rebase')
863         self.parser.add_option("--clean", action='store_true', default=False, help='clean the tree')
864         self.parser.add_option("--prefix", type='string', default=None, help='override install prefix')
865         self.parser.add_option("--sourcetree", type='string', default=None, help='override sourcetree location')
866         self.parser.add_option("--nocleanup", action='store_true', default=False, help='disable cleanup code')
867
868         self.opts, self.args = self.parser.parse_args()
869
870         if not self.opts.conf:
871             print("Please specify a config file with --conf")
872             sys.exit(1)
873
874         # we don't need fsync safety in these tests
875         self.putenv('TDB_NO_FSYNC', '1')
876
877         self.load_config(self.opts.conf)
878
879         self.set_skip(self.opts.skip)
880         self.set_vms(self.opts.vms)
881
882         if self.opts.list:
883             self.list_steps_mode()
884
885         if self.opts.prefix:
886             self.setvar('PREFIX', self.opts.prefix)
887
888         if self.opts.sourcetree:
889             self.setvar('SOURCETREE', self.opts.sourcetree)
890
891         if self.opts.rebase:
892             self.info('rebasing')
893             self.chdir('${SOURCETREE}')
894             self.run_cmd('git pull --rebase')
895
896         if self.opts.clean:
897             self.info('cleaning')
898             self.chdir('${SOURCETREE}/' + subdir)
899             self.run_cmd('make clean')