wintest: use net rpc to put authenticated users into TelentClients if we need to
[samba.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
495         while retries > 0:
496             child = self.pexpect_spawn("nc -v -z -w 1 %s %u" % (hostname, port), crlf=False, timeout=1)
497             i = child.expect(['succeeded', 'failed', pexpect.EOF, pexpect.TIMEOUT])
498             if wait_for_fail:
499                 if i > 0:
500                     return
501             else:
502                 if i == 0:
503                     return
504
505             time.sleep(delay)
506             retries -= 1
507             self.info("retrying (retries=%u delay=%u)" % (retries, delay))
508
509         raise RuntimeError("gave up waiting for %s:%d" % (hostname, port))
510
511     def run_net_time(self, child):
512         '''run net time on windows'''
513         child.sendline("net time \\\\${HOSTNAME} /set")
514         child.expect("Do you want to set the local computer")
515         child.sendline("Y")
516         child.expect("The command completed successfully")
517
518     def run_date_time(self, child, time_tuple=None):
519         '''run date and time on windows'''
520         if time_tuple is None:
521             time_tuple = time.localtime()
522         child.sendline("date")
523         child.expect("Enter the new date:")
524         i = child.expect(["dd-mm-yy", "mm-dd-yy"])
525         if i == 0:
526             child.sendline(time.strftime("%d-%m-%y", time_tuple))
527         else:
528             child.sendline(time.strftime("%m-%d-%y", time_tuple))
529         child.expect("C:")
530         child.sendline("time")
531         child.expect("Enter the new time:")
532         child.sendline(time.strftime("%H:%M:%S", time_tuple))
533         child.expect("C:")
534
535     def get_ipconfig(self, child):
536         '''get the IP configuration of the child'''
537         child.sendline("ipconfig /all")
538         child.expect('Ethernet adapter ')
539         child.expect("[\w\s]+")
540         self.setvar("WIN_NIC", child.after)
541         child.expect(['IPv4 Address', 'IP Address'])
542         child.expect('\d+.\d+.\d+.\d+')
543         self.setvar('WIN_IPV4_ADDRESS', child.after)
544         child.expect('Subnet Mask')
545         child.expect('\d+.\d+.\d+.\d+')
546         self.setvar('WIN_SUBNET_MASK', child.after)
547         child.expect('Default Gateway')
548         i = child.expect(['\d+.\d+.\d+.\d+', "C:"])
549         if i == 0:
550             self.setvar('WIN_DEFAULT_GATEWAY', child.after)
551             child.expect("C:")
552
553     def get_is_dc(self, child):
554         '''check if a windows machine is a domain controller'''
555         child.sendline("dcdiag")
556         i = child.expect(["is not a Directory Server",
557                           "is not recognized as an internal or external command",
558                           "Home Server = ",
559                           "passed test Replications"])
560         if i == 0:
561             return False
562         if i == 1 or i == 3:
563             child.expect("C:")
564             child.sendline("net config Workstation")
565             child.expect("Workstation domain")
566             child.expect('[\S]+')
567             domain = child.after
568             i = child.expect(["Workstation Domain DNS Name", "Logon domain"])
569             '''If we get the Logon domain first, we are not in an AD domain'''
570             if i == 1:
571                 return False
572             if domain.upper() == self.getvar("WIN_DOMAIN").upper():
573                 return True
574
575         child.expect('[\S]+')
576         hostname = child.after
577         if hostname.upper() == self.getvar("WIN_HOSTNAME").upper():
578             return True
579
580     def set_noexpire(self, child, username):
581         """Ensure this user's password does not expire"""
582         child.sendline('wmic useraccount where name="%s" set PasswordExpires=FALSE' % username)
583         child.expect("update successful")
584         child.expect("C:")
585
586     def run_tlntadmn(self, child):
587         '''remove the annoying telnet restrictions'''
588         child.sendline('tlntadmn config maxconn=1024')
589         child.expect(["The settings were successfully updated", "Access is denied"])
590         child.expect("C:")
591
592     def disable_firewall(self, child):
593         '''remove the annoying firewall'''
594         child.sendline('netsh advfirewall set allprofiles state off')
595         i = child.expect(["Ok", "The following command was not found: advfirewall set allprofiles state off", "The requested operation requires elevation", "Access is denied"])
596         child.expect("C:")
597         if i == 1:
598             child.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
599             i = child.expect(["Ok", "The following command was not found", "Access is denied"])
600             if i != 0:
601                 self.info("Firewall disable failed - ignoring")
602             child.expect("C:")
603
604     def set_dns(self, child):
605         child.sendline('netsh interface ip set dns "${WIN_NIC}" static ${INTERFACE_IP} primary')
606         i = child.expect(['C:', pexpect.EOF, pexpect.TIMEOUT], timeout=5)
607         if i > 0:
608             return True
609         else:
610             return False
611
612     def set_ip(self, child):
613         """fix the IP address to the same value it had when we
614         connected, but don't use DHCP, and force the DNS server to our
615         DNS server.  This allows DNS updates to run"""
616         self.get_ipconfig(child)
617         if self.getvar("WIN_IPV4_ADDRESS") != self.getvar("WIN_IP"):
618             raise RuntimeError("ipconfig address %s != nmblookup address %s" % (self.getvar("WIN_IPV4_ADDRESS"),
619                                                                                 self.getvar("WIN_IP")))
620         child.sendline('netsh')
621         child.expect('netsh>')
622         child.sendline('offline')
623         child.expect('netsh>')
624         child.sendline('routing ip add persistentroute dest=0.0.0.0 mask=0.0.0.0 name="${WIN_NIC}" nhop=${WIN_DEFAULT_GATEWAY}')
625         child.expect('netsh>')
626         child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1 store=persistent')
627         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)
628         if i == 0:
629             child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1')
630             child.expect('netsh>')
631         child.sendline('commit')
632         child.sendline('online')
633         child.sendline('exit')
634
635         child.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=5)
636         return True
637
638
639     def resolve_ip(self, hostname, retries=60, delay=5):
640         '''resolve an IP given a hostname, assuming NBT'''
641         while retries > 0:
642             child = self.pexpect_spawn("bin/nmblookup %s" % hostname)
643             i = 0
644             while i == 0:
645                 i = child.expect(["querying", '\d+.\d+.\d+.\d+', hostname, "Lookup failed"])
646                 if i == 0:
647                     child.expect("\r")
648             if i == 1:
649                 return child.after
650             retries -= 1
651             time.sleep(delay)
652             self.info("retrying (retries=%u delay=%u)" % (retries, delay))
653         raise RuntimeError("Failed to resolve IP of %s" % hostname)
654
655
656     def open_telnet(self, hostname, username, password, retries=60, delay=5, set_time=False, set_ip=False,
657                     disable_firewall=True, run_tlntadmn=True, set_noexpire=False):
658         '''open a telnet connection to a windows server, return the pexpect child'''
659         set_route = False
660         set_dns = False
661         set_telnetclients = True
662         if self.getvar('WIN_IP'):
663             ip = self.getvar('WIN_IP')
664         else:
665             ip = self.resolve_ip(hostname)
666             self.setvar('WIN_IP', ip)
667         while retries > 0:
668             child = self.pexpect_spawn("telnet " + ip + " -l '" + username + "'")
669             i = child.expect(["Welcome to Microsoft Telnet Service",
670                               "Denying new connections due to the limit on number of connections",
671                               "No more connections are allowed to telnet server",
672                               "Unable to connect to remote host",
673                               "No route to host",
674                               "Connection refused",
675                               pexpect.EOF])
676             if i != 0:
677                 child.close()
678                 time.sleep(delay)
679                 retries -= 1
680                 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
681                 continue
682             child.expect("password:")
683             child.sendline(password)
684             i = child.expect(["C:",
685                               "TelnetClients",
686                               "Denying new connections due to the limit on number of connections",
687                               "No more connections are allowed to telnet server",
688                               "Unable to connect to remote host",
689                               "No route to host",
690                               "Connection refused",
691                               pexpect.EOF])
692             if i == 1:
693                 if set_telnetclients:
694                     self.run_cmd('bin/net rpc group addmem TelnetClients "authenticated users" -S $WIN_IP -U$WIN_USER%$WIN_PASS')
695                     child.close()
696                     retries -= 1
697                     set_telnetclients = False
698                     self.info("retrying (retries=%u delay=%u)" % (retries, delay))
699                     continue
700                 else:
701                     raise RuntimeError("Failed to connect with telnet due to missing TelnetClients membership")
702
703             if i != 0:
704                 child.close()
705                 time.sleep(delay)
706                 retries -= 1
707                 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
708                 continue
709             if set_dns:
710                 set_dns = False
711                 if self.set_dns(child):
712                     continue;
713             if set_route:
714                 child.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
715                 child.expect("C:")
716                 set_route = False
717             if set_time:
718                 self.run_date_time(child, None)
719                 set_time = False
720             if run_tlntadmn:
721                 self.run_tlntadmn(child)
722                 run_tlntadmn = False
723             if set_noexpire:
724                 self.set_noexpire(child, username)
725                 set_noexpire = False
726             if disable_firewall:
727                 self.disable_firewall(child)
728                 disable_firewall = False
729             if set_ip:
730                 set_ip = False
731                 if self.set_ip(child):
732                     set_route = True
733                     set_dns = True
734                 continue
735             return child
736         raise RuntimeError("Failed to connect with telnet")
737
738     def kinit(self, username, password):
739         '''use kinit to setup a credentials cache'''
740         self.run_cmd("kdestroy")
741         self.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
742         username = self.substitute(username)
743         s = username.split('@')
744         if len(s) > 0:
745             s[1] = s[1].upper()
746         username = '@'.join(s)
747         child = self.pexpect_spawn('kinit ' + username)
748         child.expect("Password")
749         child.sendline(password)
750         child.expect(pexpect.EOF)
751         child.close()
752         if child.exitstatus != 0:
753             raise RuntimeError("kinit failed with status %d" % child.exitstatus)
754
755     def get_domains(self):
756         '''return a dictionary of DNS domains and IPs for named.conf'''
757         ret = {}
758         for v in self.vars:
759             if v[-6:] == "_REALM":
760                 base = v[:-6]
761                 if base + '_IP' in self.vars:
762                     ret[self.vars[base + '_REALM']] = self.vars[base + '_IP']
763         return ret
764
765     def wait_reboot(self, retries=3):
766         '''wait for a VM to reboot'''
767
768         # first wait for it to shutdown
769         self.port_wait("${WIN_IP}", 139, wait_for_fail=True, delay=6)
770
771         # now wait for it to come back. If it fails to come back
772         # then try resetting it
773         while retries > 0:
774             try:
775                 self.port_wait("${WIN_IP}", 139)
776                 return
777             except:
778                 retries -= 1
779                 self.vm_reset("${WIN_VM}")
780                 self.info("retrying reboot (retries=%u)" % retries)
781         raise RuntimeError(self.substitute("VM ${WIN_VM} failed to reboot"))
782
783     def get_vms(self):
784         '''return a dictionary of all the configured VM names'''
785         ret = []
786         for v in self.vars:
787             if v[-3:] == "_VM":
788                 ret.append(self.vars[v])
789         return ret
790
791
792     def run_dcpromo_as_first_dc(self, vm, func_level=None):
793         self.setwinvars(vm)
794         self.info("Configuring a windows VM ${WIN_VM} at the first DC in the domain using dcpromo")
795         child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_time=True)
796         if self.get_is_dc(child):
797             return
798
799         if func_level == '2008r2':
800             self.setvar("FUNCTION_LEVEL_INT", str(4))
801         elif func_level == '2003':
802             self.setvar("FUNCTION_LEVEL_INT", str(1))
803         else:
804             self.setvar("FUNCTION_LEVEL_INT", str(0))
805
806         child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_ip=True, set_noexpire=True)
807
808         """This server must therefore not yet be a directory server, so we must promote it"""
809         child.sendline("copy /Y con answers.txt")
810         child.sendline('''
811 [DCInstall]
812 ; New forest promotion
813 ReplicaOrNewDomain=Domain
814 NewDomain=Forest
815 NewDomainDNSName=${WIN_REALM}
816 ForestLevel=${FUNCTION_LEVEL_INT}
817 DomainNetbiosName=${WIN_DOMAIN}
818 DomainLevel=${FUNCTION_LEVEL_INT}
819 InstallDNS=Yes
820 ConfirmGc=Yes
821 CreateDNSDelegation=No
822 DatabasePath="C:\Windows\NTDS"
823 LogPath="C:\Windows\NTDS"
824 SYSVOLPath="C:\Windows\SYSVOL"
825 ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
826 SafeModeAdminPassword=${WIN_PASS}
827 ; Run-time flags (optional)
828 RebootOnCompletion=No
829 \1a
830 ''')
831         child.expect("copied.")
832         child.expect("C:")
833         child.expect("C:")
834         child.sendline("dcpromo /answer:answers.txt")
835         i = child.expect(["You must restart this computer", "failed", "Active Directory Domain Services was not installed", "C:"], timeout=240)
836         if i == 1 or i == 2:
837             raise Exception("dcpromo failed")
838         child.sendline("shutdown -r -t 0")
839         self.port_wait("${WIN_IP}", 139, wait_for_fail=True)
840         self.port_wait("${WIN_IP}", 139)
841         self.retry_cmd("host -t SRV _ldap._tcp.${WIN_REALM} ${WIN_IP}", ['has SRV record'] )
842
843
844     def start_winvm(self, vm):
845         '''start a Windows VM'''
846         self.setwinvars(vm)
847         
848         self.info("Joining a windows box to the domain")
849         self.vm_poweroff("${WIN_VM}", checkfail=False)
850         self.vm_restore("${WIN_VM}", "${WIN_SNAPSHOT}")
851
852     def run_winjoin(self, vm, domain, username="administrator", password="${PASSWORD1}"):
853         '''join a windows box to a domain'''
854         child = self.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time=True, set_ip=True, set_noexpire=True)
855         retries = 5
856         while retries > 0:
857             child.sendline("ipconfig /flushdns")
858             child.expect("C:")
859             child.sendline("netdom join ${WIN_HOSTNAME} /Domain:%s /UserD:%s /PasswordD:%s" % (domain, username, password))
860             i = child.expect(["The command completed successfully", 
861                              "The specified domain either does not exist or could not be contacted."])
862             if i == 0:
863                 break
864             time.sleep(10)
865             retries -= 1
866
867         child.expect("C:")
868         child.sendline("shutdown /r -t 0")
869         self.wait_reboot()
870         child = self.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time=True, set_ip=True)
871         child.sendline("ipconfig /registerdns")
872         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")
873         child.expect("C:")
874
875
876     def test_remote_smbclient(self, vm, username="${WIN_USER}", password="${WIN_PASS}", args=""):
877         '''test smbclient against remote server'''
878         self.setwinvars(vm)
879         self.info('Testing smbclient')
880         self.chdir('${PREFIX}')
881         self.cmd_contains("bin/smbclient --version", ["${SAMBA_VERSION}"])
882         self.retry_cmd('bin/smbclient -L ${WIN_HOSTNAME} -U%s%%%s %s' % (username, password, args), ["IPC"], retries=60, delay=5)
883
884     def test_net_use(self, vm, realm, domain, username, password):
885         self.setwinvars(vm)
886         self.info('Testing net use against Samba3 member')
887         child = self.open_telnet("${WIN_HOSTNAME}", "%s\\%s" % (domain, username), password)
888         child.sendline("net use t: \\\\${HOSTNAME}.%s\\test" % realm)
889         child.expect("The command completed successfully")
890
891
892     def setup(self, testname, subdir):
893         '''setup for main tests, parsing command line'''
894         self.parser.add_option("--conf", type='string', default='', help='config file')
895         self.parser.add_option("--skip", type='string', default='', help='list of steps to skip (comma separated)')
896         self.parser.add_option("--vms", type='string', default=None, help='list of VMs to use (comma separated)')
897         self.parser.add_option("--list", action='store_true', default=False, help='list the available steps')
898         self.parser.add_option("--rebase", action='store_true', default=False, help='do a git pull --rebase')
899         self.parser.add_option("--clean", action='store_true', default=False, help='clean the tree')
900         self.parser.add_option("--prefix", type='string', default=None, help='override install prefix')
901         self.parser.add_option("--sourcetree", type='string', default=None, help='override sourcetree location')
902         self.parser.add_option("--nocleanup", action='store_true', default=False, help='disable cleanup code')
903
904         self.opts, self.args = self.parser.parse_args()
905
906         if not self.opts.conf:
907             print("Please specify a config file with --conf")
908             sys.exit(1)
909
910         # we don't need fsync safety in these tests
911         self.putenv('TDB_NO_FSYNC', '1')
912
913         self.load_config(self.opts.conf)
914
915         self.set_skip(self.opts.skip)
916         self.set_vms(self.opts.vms)
917
918         if self.opts.list:
919             self.list_steps_mode()
920
921         if self.opts.prefix:
922             self.setvar('PREFIX', self.opts.prefix)
923
924         if self.opts.sourcetree:
925             self.setvar('SOURCETREE', self.opts.sourcetree)
926
927         if self.opts.rebase:
928             self.info('rebasing')
929             self.chdir('${SOURCETREE}')
930             self.run_cmd('git pull --rebase')
931
932         if self.opts.clean:
933             self.info('cleaning')
934             self.chdir('${SOURCETREE}/' + subdir)
935             self.run_cmd('make clean')