e1a716e480720dc7d9105260cccd60be1f083766
[metze/samba/wip.git] / source4 / dsdb / tests / python / password_lockout.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # This tests the password lockout behavior for AD implementations
4 #
5 # Copyright Matthias Dieter Wallnoefer 2010
6 # Copyright Andrew Bartlett 2013
7 # Copyright Stefan Metzmacher 2014
8 #
9
10 import optparse
11 import sys
12 import base64
13 import time
14
15 sys.path.insert(0, "bin/python")
16 import samba
17
18 from samba.tests.subunitrun import TestProgram, SubunitOptions
19
20 import samba.getopt as options
21
22 from samba.auth import system_session
23 from samba.credentials import Credentials, DONT_USE_KERBEROS, MUST_USE_KERBEROS
24 from ldb import SCOPE_BASE, LdbError
25 from ldb import ERR_CONSTRAINT_VIOLATION
26 from ldb import ERR_INVALID_CREDENTIALS
27 from ldb import Message, MessageElement, Dn
28 from ldb import FLAG_MOD_REPLACE
29 from samba import gensec, dsdb
30 from samba.samdb import SamDB
31 import samba.tests
32 from samba.tests import delete_force
33 from samba.dcerpc import security, samr
34 from samba.ndr import ndr_unpack
35
36 parser = optparse.OptionParser("password_lockout.py [options] <host>")
37 sambaopts = options.SambaOptions(parser)
38 parser.add_option_group(sambaopts)
39 parser.add_option_group(options.VersionOptions(parser))
40 # use command line creds if available
41 credopts = options.CredentialsOptions(parser)
42 parser.add_option_group(credopts)
43 subunitopts = SubunitOptions(parser)
44 parser.add_option_group(subunitopts)
45 opts, args = parser.parse_args()
46
47 if len(args) < 1:
48     parser.print_usage()
49     sys.exit(1)
50
51 host = args[0]
52
53 lp = sambaopts.get_loadparm()
54 global_creds = credopts.get_credentials(lp)
55
56 import password_lockout_base
57
58 #
59 # Tests start here
60 #
61
62 class PasswordTests(password_lockout_base.BasePasswordTestCase):
63     def setUp(self):
64         self.host = host
65         self.host_url = host_url
66         self.lp = lp
67         self.global_creds = global_creds
68         self.ldb = SamDB(url=self.host_url, session_info=system_session(self.lp),
69                          credentials=self.global_creds, lp=self.lp)
70         super(PasswordTests, self).setUp()
71
72         self.lockout2krb5_creds = self.insta_creds(self.template_creds,
73                                                    username="lockout2krb5",
74                                                    userpass="thatsAcomplPASS0",
75                                                    kerberos_state=MUST_USE_KERBEROS)
76         self.lockout2krb5_ldb = self._readd_user(self.lockout2krb5_creds,
77                                                  lockOutObservationWindow=self.lockout_observation_window)
78
79         self.lockout2ntlm_creds = self.insta_creds(self.template_creds,
80                                                    username="lockout2ntlm",
81                                                    userpass="thatsAcomplPASS0",
82                                                    kerberos_state=DONT_USE_KERBEROS)
83         self.lockout2ntlm_ldb = self._readd_user(self.lockout2ntlm_creds,
84                                                  lockOutObservationWindow=self.lockout_observation_window)
85
86     def _reset_ldap_lockoutTime(self, res):
87         self.ldb.modify_ldif("""
88 dn: """ + str(res[0].dn) + """
89 changetype: modify
90 replace: lockoutTime
91 lockoutTime: 0
92 """)
93
94     def _reset_ldap_userAccountControl(self, res):
95         self.assertTrue("userAccountControl" in res[0])
96         self.assertTrue("msDS-User-Account-Control-Computed" in res[0])
97
98         uac = int(res[0]["userAccountControl"][0])
99         uacc = int(res[0]["msDS-User-Account-Control-Computed"][0])
100
101         uac |= uacc
102         uac = uac & ~dsdb.UF_LOCKOUT
103
104         self.ldb.modify_ldif("""
105 dn: """ + str(res[0].dn) + """
106 changetype: modify
107 replace: userAccountControl
108 userAccountControl: %d
109 """ % uac)
110
111     def _reset_by_method(self, res, method):
112         if method is "ldap_userAccountControl":
113             self._reset_ldap_userAccountControl(res)
114         elif method is "ldap_lockoutTime":
115             self._reset_ldap_lockoutTime(res)
116         elif method is "samr":
117             self._reset_samr(res)
118         else:
119             self.assertTrue(False, msg="Invalid reset method[%s]" % method)
120
121     def _test_userPassword_lockout_with_clear_change(self, creds, other_ldb, method,
122                                                      initial_lastlogon_relation=None):
123         # Notice: This works only against Windows if "dSHeuristics" has been set
124         # properly
125         username = creds.get_username()
126         userpass = creds.get_password()
127         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
128
129         use_kerberos = creds.get_kerberos_state()
130         if use_kerberos == MUST_USE_KERBEROS:
131             logoncount_relation = 'greater'
132             lastlogon_relation = 'greater'
133             print "Performs a password cleartext change operation on 'userPassword' using Kerberos"
134         else:
135             logoncount_relation = 'equal'
136             lastlogon_relation = 'equal'
137             print "Performs a password cleartext change operation on 'userPassword' using NTLMSSP"
138
139         if initial_lastlogon_relation is not None:
140             lastlogon_relation = initial_lastlogon_relation
141
142         res = self._check_account(userdn,
143                                   badPwdCount=0,
144                                   badPasswordTime=("greater", 0),
145                                   logonCount=(logoncount_relation, 0),
146                                   lastLogon=(lastlogon_relation, 0),
147                                   lastLogonTimestamp=('greater', 0),
148                                   userAccountControl=
149                                     dsdb.UF_NORMAL_ACCOUNT,
150                                   msDSUserAccountControlComputed=0)
151         badPasswordTime = int(res[0]["badPasswordTime"][0])
152         logonCount = int(res[0]["logonCount"][0])
153         lastLogon = int(res[0]["lastLogon"][0])
154         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
155         if lastlogon_relation == 'greater':
156             self.assertGreater(lastLogon, badPasswordTime)
157             self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
158
159         # Change password on a connection as another user
160
161         # Wrong old password
162         try:
163             other_ldb.modify_ldif("""
164 dn: """ + userdn + """
165 changetype: modify
166 delete: userPassword
167 userPassword: thatsAcomplPASS1x
168 add: userPassword
169 userPassword: thatsAcomplPASS2
170 """)
171             self.fail()
172         except LdbError, (num, msg):
173             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
174             self.assertTrue('00000056' in msg, msg)
175
176         res = self._check_account(userdn,
177                                   badPwdCount=1,
178                                   badPasswordTime=("greater", badPasswordTime),
179                                   logonCount=logonCount,
180                                   lastLogon=lastLogon,
181                                   lastLogonTimestamp=lastLogonTimestamp,
182                                   userAccountControl=
183                                     dsdb.UF_NORMAL_ACCOUNT,
184                                   msDSUserAccountControlComputed=0)
185         badPasswordTime = int(res[0]["badPasswordTime"][0])
186
187         # Correct old password
188         other_ldb.modify_ldif("""
189 dn: """ + userdn + """
190 changetype: modify
191 delete: userPassword
192 userPassword: """ + userpass + """
193 add: userPassword
194 userPassword: thatsAcomplPASS2
195 """)
196
197         res = self._check_account(userdn,
198                                   badPwdCount=1,
199                                   badPasswordTime=badPasswordTime,
200                                   logonCount=logonCount,
201                                   lastLogon=lastLogon,
202                                   lastLogonTimestamp=lastLogonTimestamp,
203                                   userAccountControl=
204                                     dsdb.UF_NORMAL_ACCOUNT,
205                                   msDSUserAccountControlComputed=0)
206
207         # Wrong old password
208         try:
209             other_ldb.modify_ldif("""
210 dn: """ + userdn + """
211 changetype: modify
212 delete: userPassword
213 userPassword: thatsAcomplPASS1x
214 add: userPassword
215 userPassword: thatsAcomplPASS2
216 """)
217             self.fail()
218         except LdbError, (num, msg):
219             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
220             self.assertTrue('00000056' in msg, msg)
221
222         res = self._check_account(userdn,
223                                   badPwdCount=2,
224                                   badPasswordTime=("greater", badPasswordTime),
225                                   logonCount=logonCount,
226                                   lastLogon=lastLogon,
227                                   lastLogonTimestamp=lastLogonTimestamp,
228                                   userAccountControl=
229                                     dsdb.UF_NORMAL_ACCOUNT,
230                                   msDSUserAccountControlComputed=0)
231         badPasswordTime = int(res[0]["badPasswordTime"][0])
232
233         print "two failed password change"
234
235         # Wrong old password
236         try:
237             other_ldb.modify_ldif("""
238 dn: """ + userdn + """
239 changetype: modify
240 delete: userPassword
241 userPassword: thatsAcomplPASS1x
242 add: userPassword
243 userPassword: thatsAcomplPASS2
244 """)
245             self.fail()
246         except LdbError, (num, msg):
247             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
248             self.assertTrue('00000056' in msg, msg)
249
250         res = self._check_account(userdn,
251                                   badPwdCount=3,
252                                   badPasswordTime=("greater", badPasswordTime),
253                                   logonCount=logonCount,
254                                   lastLogon=lastLogon,
255                                   lastLogonTimestamp=lastLogonTimestamp,
256                                   lockoutTime=("greater", badPasswordTime),
257                                   userAccountControl=
258                                     dsdb.UF_NORMAL_ACCOUNT,
259                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
260         badPasswordTime = int(res[0]["badPasswordTime"][0])
261         lockoutTime = int(res[0]["lockoutTime"][0])
262
263         # Wrong old password
264         try:
265             other_ldb.modify_ldif("""
266 dn: """ + userdn + """
267 changetype: modify
268 delete: userPassword
269 userPassword: thatsAcomplPASS1x
270 add: userPassword
271 userPassword: thatsAcomplPASS2
272 """)
273             self.fail()
274         except LdbError, (num, msg):
275             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
276             self.assertTrue('00000775' in msg, msg)
277
278         res = self._check_account(userdn,
279                                   badPwdCount=3,
280                                   badPasswordTime=badPasswordTime,
281                                   logonCount=logonCount,
282                                   lastLogon=lastLogon,
283                                   lastLogonTimestamp=lastLogonTimestamp,
284                                   lockoutTime=lockoutTime,
285                                   userAccountControl=
286                                     dsdb.UF_NORMAL_ACCOUNT,
287                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
288
289         # Wrong old password
290         try:
291             other_ldb.modify_ldif("""
292 dn: """ + userdn + """
293 changetype: modify
294 delete: userPassword
295 userPassword: thatsAcomplPASS1x
296 add: userPassword
297 userPassword: thatsAcomplPASS2
298 """)
299             self.fail()
300         except LdbError, (num, msg):
301             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
302             self.assertTrue('00000775' in msg, msg)
303
304         res = self._check_account(userdn,
305                                   badPwdCount=3,
306                                   badPasswordTime=badPasswordTime,
307                                   logonCount=logonCount,
308                                   lockoutTime=lockoutTime,
309                                   lastLogon=lastLogon,
310                                   lastLogonTimestamp=lastLogonTimestamp,
311                                   userAccountControl=
312                                     dsdb.UF_NORMAL_ACCOUNT,
313                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
314
315         try:
316             # Correct old password
317             other_ldb.modify_ldif("""
318 dn: """ + userdn + """
319 changetype: modify
320 delete: userPassword
321 userPassword: thatsAcomplPASS2
322 add: userPassword
323 userPassword: thatsAcomplPASS2x
324 """)
325             self.fail()
326         except LdbError, (num, msg):
327             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
328             self.assertTrue('00000775' in msg, msg)
329
330         res = self._check_account(userdn,
331                                   badPwdCount=3,
332                                   badPasswordTime=badPasswordTime,
333                                   logonCount=logonCount,
334                                   lastLogon=lastLogon,
335                                   lastLogonTimestamp=lastLogonTimestamp,
336                                   lockoutTime=lockoutTime,
337                                   userAccountControl=
338                                     dsdb.UF_NORMAL_ACCOUNT,
339                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
340
341         # Now reset the password, which does NOT change the lockout!
342         self.ldb.modify_ldif("""
343 dn: """ + userdn + """
344 changetype: modify
345 replace: userPassword
346 userPassword: thatsAcomplPASS2
347 """)
348
349         res = self._check_account(userdn,
350                                   badPwdCount=3,
351                                   badPasswordTime=badPasswordTime,
352                                   logonCount=logonCount,
353                                   lastLogon=lastLogon,
354                                   lastLogonTimestamp=lastLogonTimestamp,
355                                   lockoutTime=lockoutTime,
356                                   userAccountControl=
357                                     dsdb.UF_NORMAL_ACCOUNT,
358                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
359
360         try:
361             # Correct old password
362             other_ldb.modify_ldif("""
363 dn: """ + userdn + """
364 changetype: modify
365 delete: userPassword
366 userPassword: thatsAcomplPASS2
367 add: userPassword
368 userPassword: thatsAcomplPASS2x
369 """)
370             self.fail()
371         except LdbError, (num, msg):
372             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
373             self.assertTrue('00000775' in msg, msg)
374
375         res = self._check_account(userdn,
376                                   badPwdCount=3,
377                                   badPasswordTime=badPasswordTime,
378                                   logonCount=logonCount,
379                                   lastLogon=lastLogon,
380                                   lastLogonTimestamp=lastLogonTimestamp,
381                                   lockoutTime=lockoutTime,
382                                   userAccountControl=
383                                     dsdb.UF_NORMAL_ACCOUNT,
384                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
385
386         m = Message()
387         m.dn = Dn(self.ldb, userdn)
388         m["userAccountControl"] = MessageElement(
389           str(dsdb.UF_LOCKOUT),
390           FLAG_MOD_REPLACE, "userAccountControl")
391
392         self.ldb.modify(m)
393
394         # This shows that setting the UF_LOCKOUT flag alone makes no difference
395         res = self._check_account(userdn,
396                                   badPwdCount=3,
397                                   badPasswordTime=badPasswordTime,
398                                   logonCount=logonCount,
399                                   lastLogon=lastLogon,
400                                   lastLogonTimestamp=lastLogonTimestamp,
401                                   lockoutTime=lockoutTime,
402                                   userAccountControl=
403                                     dsdb.UF_NORMAL_ACCOUNT,
404                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
405
406         # This shows that setting the UF_LOCKOUT flag makes no difference
407         try:
408             # Correct old password
409             other_ldb.modify_ldif("""
410 dn: """ + userdn + """
411 changetype: modify
412 delete: unicodePwd
413 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
414 add: unicodePwd
415 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
416 """)
417             self.fail()
418         except LdbError, (num, msg):
419             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
420             self.assertTrue('00000775' in msg, msg)
421
422         res = self._check_account(userdn,
423                                   badPwdCount=3,
424                                   badPasswordTime=badPasswordTime,
425                                   logonCount=logonCount,
426                                   lockoutTime=lockoutTime,
427                                   lastLogon=lastLogon,
428                                   lastLogonTimestamp=lastLogonTimestamp,
429                                   userAccountControl=
430                                     dsdb.UF_NORMAL_ACCOUNT,
431                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
432
433         self._reset_by_method(res, method)
434
435         # Here bad password counts are reset without logon success.
436         res = self._check_account(userdn,
437                                   badPwdCount=0,
438                                   badPasswordTime=badPasswordTime,
439                                   logonCount=logonCount,
440                                   lockoutTime=0,
441                                   lastLogon=lastLogon,
442                                   lastLogonTimestamp=lastLogonTimestamp,
443                                   userAccountControl=
444                                     dsdb.UF_NORMAL_ACCOUNT,
445                                   msDSUserAccountControlComputed=0)
446
447         # The correct password after doing the unlock
448
449         other_ldb.modify_ldif("""
450 dn: """ + userdn + """
451 changetype: modify
452 delete: unicodePwd
453 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
454 add: unicodePwd
455 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2x\"".encode('utf-16-le')) + """
456 """)
457         userpass = "thatsAcomplPASS2x"
458         creds.set_password(userpass)
459
460         res = self._check_account(userdn,
461                                   badPwdCount=0,
462                                   badPasswordTime=badPasswordTime,
463                                   logonCount=logonCount,
464                                   lockoutTime=0,
465                                   lastLogon=lastLogon,
466                                   lastLogonTimestamp=lastLogonTimestamp,
467                                   userAccountControl=
468                                     dsdb.UF_NORMAL_ACCOUNT,
469                                   msDSUserAccountControlComputed=0)
470
471         # Wrong old password
472         try:
473             other_ldb.modify_ldif("""
474 dn: """ + userdn + """
475 changetype: modify
476 delete: userPassword
477 userPassword: thatsAcomplPASS1xyz
478 add: userPassword
479 userPassword: thatsAcomplPASS2XYZ
480 """)
481             self.fail()
482         except LdbError, (num, msg):
483             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
484             self.assertTrue('00000056' in msg, msg)
485
486         res = self._check_account(userdn,
487                                   badPwdCount=1,
488                                   badPasswordTime=("greater", badPasswordTime),
489                                   logonCount=logonCount,
490                                   lockoutTime=0,
491                                   lastLogon=lastLogon,
492                                   lastLogonTimestamp=lastLogonTimestamp,
493                                   userAccountControl=
494                                     dsdb.UF_NORMAL_ACCOUNT,
495                                   msDSUserAccountControlComputed=0)
496         badPasswordTime = int(res[0]["badPasswordTime"][0])
497
498         # Wrong old password
499         try:
500             other_ldb.modify_ldif("""
501 dn: """ + userdn + """
502 changetype: modify
503 delete: userPassword
504 userPassword: thatsAcomplPASS1xyz
505 add: userPassword
506 userPassword: thatsAcomplPASS2XYZ
507 """)
508             self.fail()
509         except LdbError, (num, msg):
510             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
511             self.assertTrue('00000056' in msg, msg)
512
513         res = self._check_account(userdn,
514                                   badPwdCount=2,
515                                   badPasswordTime=("greater", badPasswordTime),
516                                   logonCount=logonCount,
517                                   lockoutTime=0,
518                                   lastLogon=lastLogon,
519                                   lastLogonTimestamp=lastLogonTimestamp,
520                                   userAccountControl=
521                                     dsdb.UF_NORMAL_ACCOUNT,
522                                   msDSUserAccountControlComputed=0)
523         badPasswordTime = int(res[0]["badPasswordTime"][0])
524
525         self._reset_ldap_lockoutTime(res)
526
527         res = self._check_account(userdn,
528                                   badPwdCount=0,
529                                   badPasswordTime=badPasswordTime,
530                                   logonCount=logonCount,
531                                   lastLogon=lastLogon,
532                                   lastLogonTimestamp=lastLogonTimestamp,
533                                   lockoutTime=0,
534                                   userAccountControl=
535                                     dsdb.UF_NORMAL_ACCOUNT,
536                                   msDSUserAccountControlComputed=0)
537
538     def test_userPassword_lockout_with_clear_change_krb5_ldap_userAccountControl(self):
539         self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
540                                                           self.lockout2krb5_ldb,
541                                                           "ldap_userAccountControl")
542
543     def test_userPassword_lockout_with_clear_change_krb5_ldap_lockoutTime(self):
544         self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
545                                                           self.lockout2krb5_ldb,
546                                                           "ldap_lockoutTime")
547
548     def test_userPassword_lockout_with_clear_change_krb5_samr(self):
549         self._test_userPassword_lockout_with_clear_change(self.lockout1krb5_creds,
550                                                           self.lockout2krb5_ldb,
551                                                           "samr")
552
553     def test_userPassword_lockout_with_clear_change_ntlm_ldap_userAccountControl(self):
554         self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
555                                                           self.lockout2ntlm_ldb,
556                                                           "ldap_userAccountControl",
557                                                           initial_lastlogon_relation='greater')
558
559     def test_userPassword_lockout_with_clear_change_ntlm_ldap_lockoutTime(self):
560         self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
561                                                           self.lockout2ntlm_ldb,
562                                                           "ldap_lockoutTime",
563                                                           initial_lastlogon_relation='greater')
564
565     def test_userPassword_lockout_with_clear_change_ntlm_samr(self):
566         self._test_userPassword_lockout_with_clear_change(self.lockout1ntlm_creds,
567                                                           self.lockout2ntlm_ldb,
568                                                           "samr",
569                                                           initial_lastlogon_relation='greater')
570
571     def _test_unicodePwd_lockout_with_clear_change(self, creds, other_ldb,
572                                                    initial_logoncount_relation=None):
573         print "Performs a password cleartext change operation on 'unicodePwd'"
574         username = creds.get_username()
575         userpass = creds.get_password()
576         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
577         if initial_logoncount_relation is not None:
578             logoncount_relation = initial_logoncount_relation
579         else:
580             logoncount_relation = "greater"
581
582         res = self._check_account(userdn,
583                                   badPwdCount=0,
584                                   badPasswordTime=("greater", 0),
585                                   logonCount=(logoncount_relation, 0),
586                                   lastLogon=("greater", 0),
587                                   lastLogonTimestamp=("greater", 0),
588                                   userAccountControl=
589                                     dsdb.UF_NORMAL_ACCOUNT,
590                                   msDSUserAccountControlComputed=0)
591         badPasswordTime = int(res[0]["badPasswordTime"][0])
592         logonCount = int(res[0]["logonCount"][0])
593         lastLogon = int(res[0]["lastLogon"][0])
594         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
595         self.assertGreater(lastLogonTimestamp, badPasswordTime)
596         self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
597
598         # Change password on a connection as another user
599
600         # Wrong old password
601         try:
602             other_ldb.modify_ldif("""
603 dn: """ + userdn + """
604 changetype: modify
605 delete: unicodePwd
606 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS1x\"".encode('utf-16-le')) + """
607 add: unicodePwd
608 unicodePwd:: """ + base64.b64encode("\"thatsAcomplPASS2\"".encode('utf-16-le')) + """
609 """)
610             self.fail()
611         except LdbError, (num, msg):
612             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
613             self.assertTrue('00000056' in msg, msg)
614
615         res = self._check_account(userdn,
616                                   badPwdCount=1,
617                                   badPasswordTime=("greater", badPasswordTime),
618                                   logonCount=logonCount,
619                                   lastLogon=lastLogon,
620                                   lastLogonTimestamp=lastLogonTimestamp,
621                                   userAccountControl=
622                                     dsdb.UF_NORMAL_ACCOUNT,
623                                   msDSUserAccountControlComputed=0)
624         badPasswordTime = int(res[0]["badPasswordTime"][0])
625
626         # Correct old password
627         old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
628         invalid_utf16 = "\"thatsAcomplPASSX\"".encode('utf-16-le')
629         userpass = "thatsAcomplPASS2"
630         creds.set_password(userpass)
631         new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
632
633         other_ldb.modify_ldif("""
634 dn: """ + userdn + """
635 changetype: modify
636 delete: unicodePwd
637 unicodePwd:: """ + base64.b64encode(old_utf16) + """
638 add: unicodePwd
639 unicodePwd:: """ + base64.b64encode(new_utf16) + """
640 """)
641
642         res = self._check_account(userdn,
643                                   badPwdCount=1,
644                                   badPasswordTime=badPasswordTime,
645                                   logonCount=logonCount,
646                                   lastLogon=lastLogon,
647                                   lastLogonTimestamp=lastLogonTimestamp,
648                                   userAccountControl=
649                                     dsdb.UF_NORMAL_ACCOUNT,
650                                   msDSUserAccountControlComputed=0)
651
652         # Wrong old password
653         try:
654             other_ldb.modify_ldif("""
655 dn: """ + userdn + """
656 changetype: modify
657 delete: unicodePwd
658 unicodePwd:: """ + base64.b64encode(old_utf16) + """
659 add: unicodePwd
660 unicodePwd:: """ + base64.b64encode(new_utf16) + """
661 """)
662             self.fail()
663         except LdbError, (num, msg):
664             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
665             self.assertTrue('00000056' in msg, msg)
666
667         res = self._check_account(userdn,
668                                   badPwdCount=2,
669                                   badPasswordTime=("greater", badPasswordTime),
670                                   logonCount=logonCount,
671                                   lastLogon=lastLogon,
672                                   lastLogonTimestamp=lastLogonTimestamp,
673                                   userAccountControl=
674                                     dsdb.UF_NORMAL_ACCOUNT,
675                                   msDSUserAccountControlComputed=0)
676         badPasswordTime = int(res[0]["badPasswordTime"][0])
677
678         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
679         # It doesn't create "lockoutTime" = 0 and doesn't
680         # reset "badPwdCount" = 0.
681         self._reset_samr(res)
682
683         res = self._check_account(userdn,
684                                   badPwdCount=2,
685                                   badPasswordTime=badPasswordTime,
686                                   logonCount=logonCount,
687                                   lastLogon=lastLogon,
688                                   lastLogonTimestamp=lastLogonTimestamp,
689                                   userAccountControl=
690                                     dsdb.UF_NORMAL_ACCOUNT,
691                                   msDSUserAccountControlComputed=0)
692
693         print "two failed password change"
694
695         # Wrong old password
696         try:
697             other_ldb.modify_ldif("""
698 dn: """ + userdn + """
699 changetype: modify
700 delete: unicodePwd
701 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
702 add: unicodePwd
703 unicodePwd:: """ + base64.b64encode(new_utf16) + """
704 """)
705             self.fail()
706         except LdbError, (num, msg):
707             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
708             self.assertTrue('00000056' in msg, msg)
709
710         # this is strange, why do we have lockoutTime=badPasswordTime here?
711         res = self._check_account(userdn,
712                                   badPwdCount=3,
713                                   badPasswordTime=("greater", badPasswordTime),
714                                   logonCount=logonCount,
715                                   lastLogon=lastLogon,
716                                   lastLogonTimestamp=lastLogonTimestamp,
717                                   lockoutTime=("greater", badPasswordTime),
718                                   userAccountControl=
719                                     dsdb.UF_NORMAL_ACCOUNT,
720                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
721         badPasswordTime = int(res[0]["badPasswordTime"][0])
722         lockoutTime = int(res[0]["lockoutTime"][0])
723
724         # Wrong old password
725         try:
726             other_ldb.modify_ldif("""
727 dn: """ + userdn + """
728 changetype: modify
729 delete: unicodePwd
730 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
731 add: unicodePwd
732 unicodePwd:: """ + base64.b64encode(new_utf16) + """
733 """)
734             self.fail()
735         except LdbError, (num, msg):
736             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
737             self.assertTrue('00000775' in msg, msg)
738
739         res = self._check_account(userdn,
740                                   badPwdCount=3,
741                                   badPasswordTime=badPasswordTime,
742                                   logonCount=logonCount,
743                                   lastLogon=lastLogon,
744                                   lastLogonTimestamp=lastLogonTimestamp,
745                                   lockoutTime=lockoutTime,
746                                   userAccountControl=
747                                     dsdb.UF_NORMAL_ACCOUNT,
748                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
749
750         # Wrong old password
751         try:
752             other_ldb.modify_ldif("""
753 dn: """ + userdn + """
754 changetype: modify
755 delete: unicodePwd
756 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
757 add: unicodePwd
758 unicodePwd:: """ + base64.b64encode(new_utf16) + """
759 """)
760             self.fail()
761         except LdbError, (num, msg):
762             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
763             self.assertTrue('00000775' in msg, msg)
764
765         res = self._check_account(userdn,
766                                   badPwdCount=3,
767                                   badPasswordTime=badPasswordTime,
768                                   logonCount=logonCount,
769                                   lastLogon=lastLogon,
770                                   lastLogonTimestamp=lastLogonTimestamp,
771                                   lockoutTime=lockoutTime,
772                                   userAccountControl=
773                                     dsdb.UF_NORMAL_ACCOUNT,
774                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
775
776         try:
777             # Correct old password
778             other_ldb.modify_ldif("""
779 dn: """ + userdn + """
780 changetype: modify
781 delete: unicodePwd
782 unicodePwd:: """ + base64.b64encode(new_utf16) + """
783 add: unicodePwd
784 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
785 """)
786             self.fail()
787         except LdbError, (num, msg):
788             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
789             self.assertTrue('00000775' in msg, msg)
790
791         res = self._check_account(userdn,
792                                   badPwdCount=3,
793                                   badPasswordTime=badPasswordTime,
794                                   logonCount=logonCount,
795                                   lastLogon=lastLogon,
796                                   lastLogonTimestamp=lastLogonTimestamp,
797                                   lockoutTime=lockoutTime,
798                                   userAccountControl=
799                                     dsdb.UF_NORMAL_ACCOUNT,
800                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
801
802         # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
803         self._reset_samr(res);
804
805         res = self._check_account(userdn,
806                                   badPwdCount=0,
807                                   badPasswordTime=badPasswordTime,
808                                   logonCount=logonCount,
809                                   lastLogon=lastLogon,
810                                   lastLogonTimestamp=lastLogonTimestamp,
811                                   lockoutTime=0,
812                                   userAccountControl=
813                                     dsdb.UF_NORMAL_ACCOUNT,
814                                   msDSUserAccountControlComputed=0)
815
816         # Correct old password
817         old_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
818         invalid_utf16 = "\"thatsAcomplPASSiX\"".encode('utf-16-le')
819         userpass = "thatsAcomplPASS2x"
820         creds.set_password(userpass)
821         new_utf16 = ("\"%s\"" % userpass).encode('utf-16-le')
822
823         other_ldb.modify_ldif("""
824 dn: """ + userdn + """
825 changetype: modify
826 delete: unicodePwd
827 unicodePwd:: """ + base64.b64encode(old_utf16) + """
828 add: unicodePwd
829 unicodePwd:: """ + base64.b64encode(new_utf16) + """
830 """)
831
832         res = self._check_account(userdn,
833                                   badPwdCount=0,
834                                   badPasswordTime=badPasswordTime,
835                                   logonCount=logonCount,
836                                   lastLogon=lastLogon,
837                                   lastLogonTimestamp=lastLogonTimestamp,
838                                   lockoutTime=0,
839                                   userAccountControl=
840                                     dsdb.UF_NORMAL_ACCOUNT,
841                                   msDSUserAccountControlComputed=0)
842
843         # Wrong old password
844         try:
845             other_ldb.modify_ldif("""
846 dn: """ + userdn + """
847 changetype: modify
848 delete: unicodePwd
849 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
850 add: unicodePwd
851 unicodePwd:: """ + base64.b64encode(new_utf16) + """
852 """)
853             self.fail()
854         except LdbError, (num, msg):
855             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
856             self.assertTrue('00000056' in msg, msg)
857
858         res = self._check_account(userdn,
859                                   badPwdCount=1,
860                                   badPasswordTime=("greater", badPasswordTime),
861                                   logonCount=logonCount,
862                                   lastLogon=lastLogon,
863                                   lastLogonTimestamp=lastLogonTimestamp,
864                                   lockoutTime=0,
865                                   userAccountControl=
866                                     dsdb.UF_NORMAL_ACCOUNT,
867                                   msDSUserAccountControlComputed=0)
868         badPasswordTime = int(res[0]["badPasswordTime"][0])
869
870         # Wrong old password
871         try:
872             other_ldb.modify_ldif("""
873 dn: """ + userdn + """
874 changetype: modify
875 delete: unicodePwd
876 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
877 add: unicodePwd
878 unicodePwd:: """ + base64.b64encode(new_utf16) + """
879 """)
880             self.fail()
881         except LdbError, (num, msg):
882             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
883             self.assertTrue('00000056' in msg, msg)
884
885         res = self._check_account(userdn,
886                                   badPwdCount=2,
887                                   badPasswordTime=("greater", badPasswordTime),
888                                   logonCount=logonCount,
889                                   lastLogon=lastLogon,
890                                   lastLogonTimestamp=lastLogonTimestamp,
891                                   lockoutTime=0,
892                                   userAccountControl=
893                                     dsdb.UF_NORMAL_ACCOUNT,
894                                   msDSUserAccountControlComputed=0)
895         badPasswordTime = int(res[0]["badPasswordTime"][0])
896
897         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
898         # It doesn't reset "badPwdCount" = 0.
899         self._reset_samr(res)
900
901         res = self._check_account(userdn,
902                                   badPwdCount=2,
903                                   badPasswordTime=badPasswordTime,
904                                   logonCount=logonCount,
905                                   lastLogon=lastLogon,
906                                   lastLogonTimestamp=lastLogonTimestamp,
907                                   lockoutTime=0,
908                                   userAccountControl=
909                                     dsdb.UF_NORMAL_ACCOUNT,
910                                   msDSUserAccountControlComputed=0)
911
912         # Wrong old password
913         try:
914             other_ldb.modify_ldif("""
915 dn: """ + userdn + """
916 changetype: modify
917 delete: unicodePwd
918 unicodePwd:: """ + base64.b64encode(invalid_utf16) + """
919 add: unicodePwd
920 unicodePwd:: """ + base64.b64encode(new_utf16) + """
921 """)
922             self.fail()
923         except LdbError, (num, msg):
924             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
925             self.assertTrue('00000056' in msg, msg)
926
927         res = self._check_account(userdn,
928                                   badPwdCount=3,
929                                   badPasswordTime=("greater", badPasswordTime),
930                                   logonCount=logonCount,
931                                   lastLogon=lastLogon,
932                                   lastLogonTimestamp=lastLogonTimestamp,
933                                   lockoutTime=("greater", badPasswordTime),
934                                   userAccountControl=
935                                     dsdb.UF_NORMAL_ACCOUNT,
936                                   msDSUserAccountControlComputed=dsdb.UF_LOCKOUT)
937         badPasswordTime = int(res[0]["badPasswordTime"][0])
938         lockoutTime = int(res[0]["lockoutTime"][0])
939
940         time.sleep(self.account_lockout_duration + 1)
941
942         res = self._check_account(userdn,
943                                   badPwdCount=3, effective_bad_password_count=0,
944                                   badPasswordTime=badPasswordTime,
945                                   logonCount=logonCount,
946                                   lastLogon=lastLogon,
947                                   lastLogonTimestamp=lastLogonTimestamp,
948                                   lockoutTime=lockoutTime,
949                                   userAccountControl=
950                                     dsdb.UF_NORMAL_ACCOUNT,
951                                   msDSUserAccountControlComputed=0)
952
953         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
954         # It doesn't reset "lockoutTime" = 0 and doesn't
955         # reset "badPwdCount" = 0.
956         self._reset_samr(res)
957
958         res = self._check_account(userdn,
959                                   badPwdCount=3, effective_bad_password_count=0,
960                                   badPasswordTime=badPasswordTime,
961                                   logonCount=logonCount,
962                                   lockoutTime=lockoutTime,
963                                   lastLogon=lastLogon,
964                                   lastLogonTimestamp=lastLogonTimestamp,
965                                   userAccountControl=
966                                     dsdb.UF_NORMAL_ACCOUNT,
967                                   msDSUserAccountControlComputed=0)
968
969     def test_unicodePwd_lockout_with_clear_change_krb5(self):
970         self._test_unicodePwd_lockout_with_clear_change(self.lockout1krb5_creds,
971                                                         self.lockout2krb5_ldb)
972
973     def test_unicodePwd_lockout_with_clear_change_ntlm(self):
974         self._test_unicodePwd_lockout_with_clear_change(self.lockout1ntlm_creds,
975                                                         self.lockout2ntlm_ldb,
976                                                         initial_logoncount_relation="equal")
977
978     def test_login_lockout_krb5(self):
979         self._test_login_lockout(self.lockout1krb5_creds)
980
981     def test_login_lockout_ntlm(self):
982         self._test_login_lockout(self.lockout1ntlm_creds)
983
984     def test_multiple_logon_krb5(self):
985         self._test_multiple_logon(self.lockout1krb5_creds)
986
987     def test_multiple_logon_ntlm(self):
988         self._test_multiple_logon(self.lockout1ntlm_creds)
989
990     def _testing_add_user(self, creds, lockOutObservationWindow=0):
991         username = creds.get_username()
992         userpass = creds.get_password()
993         userdn = "cn=%s,cn=users,%s" % (username, self.base_dn)
994
995         use_kerberos = creds.get_kerberos_state()
996         if use_kerberos == MUST_USE_KERBEROS:
997             logoncount_relation = 'greater'
998             lastlogon_relation = 'greater'
999         else:
1000             logoncount_relation = 'equal'
1001             if lockOutObservationWindow == 0:
1002                 lastlogon_relation = 'greater'
1003             else:
1004                 lastlogon_relation = 'equal'
1005
1006         delete_force(self.ldb, userdn)
1007         self.ldb.add({
1008              "dn": userdn,
1009              "objectclass": "user",
1010              "sAMAccountName": username})
1011
1012         self.addCleanup(delete_force, self.ldb, userdn)
1013
1014         res = self._check_account(userdn,
1015                                   badPwdCount=0,
1016                                   badPasswordTime=0,
1017                                   logonCount=0,
1018                                   lastLogon=0,
1019                                   lastLogonTimestamp=('absent', None),
1020                                   userAccountControl=
1021                                     dsdb.UF_NORMAL_ACCOUNT |
1022                                     dsdb.UF_ACCOUNTDISABLE |
1023                                     dsdb.UF_PASSWD_NOTREQD,
1024                                   msDSUserAccountControlComputed=
1025                                     dsdb.UF_PASSWORD_EXPIRED)
1026
1027         # SAMR doesn't have any impact if dsdb.UF_LOCKOUT isn't present.
1028         # It doesn't create "lockoutTime" = 0.
1029         self._reset_samr(res)
1030
1031         res = self._check_account(userdn,
1032                                   badPwdCount=0,
1033                                   badPasswordTime=0,
1034                                   logonCount=0,
1035                                   lastLogon=0,
1036                                   lastLogonTimestamp=('absent', None),
1037                                   userAccountControl=
1038                                     dsdb.UF_NORMAL_ACCOUNT |
1039                                     dsdb.UF_ACCOUNTDISABLE |
1040                                     dsdb.UF_PASSWD_NOTREQD,
1041                                   msDSUserAccountControlComputed=
1042                                     dsdb.UF_PASSWORD_EXPIRED)
1043
1044         # Tests a password change when we don't have any password yet with a
1045         # wrong old password
1046         try:
1047             self.ldb.modify_ldif("""
1048 dn: """ + userdn + """
1049 changetype: modify
1050 delete: userPassword
1051 userPassword: noPassword
1052 add: userPassword
1053 userPassword: thatsAcomplPASS2
1054 """)
1055             self.fail()
1056         except LdbError, (num, msg):
1057             self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
1058             # Windows (2008 at least) seems to have some small bug here: it
1059             # returns "0000056A" on longer (always wrong) previous passwords.
1060             self.assertTrue('00000056' in msg, msg)
1061
1062         res = self._check_account(userdn,
1063                                   badPwdCount=1,
1064                                   badPasswordTime=("greater", 0),
1065                                   logonCount=0,
1066                                   lastLogon=0,
1067                                   lastLogonTimestamp=('absent', None),
1068                                   userAccountControl=
1069                                     dsdb.UF_NORMAL_ACCOUNT |
1070                                     dsdb.UF_ACCOUNTDISABLE |
1071                                     dsdb.UF_PASSWD_NOTREQD,
1072                                   msDSUserAccountControlComputed=
1073                                     dsdb.UF_PASSWORD_EXPIRED)
1074         badPwdCount = int(res[0]["badPwdCount"][0])
1075         badPasswordTime = int(res[0]["badPasswordTime"][0])
1076
1077         # Sets the initial user password with a "special" password change
1078         # I think that this internally is a password set operation and it can
1079         # only be performed by someone which has password set privileges on the
1080         # account (at least in s4 we do handle it like that).
1081         self.ldb.modify_ldif("""
1082 dn: """ + userdn + """
1083 changetype: modify
1084 delete: userPassword
1085 add: userPassword
1086 userPassword: """ + userpass + """
1087 """)
1088
1089         res = self._check_account(userdn,
1090                                   badPwdCount=badPwdCount,
1091                                   badPasswordTime=badPasswordTime,
1092                                   logonCount=0,
1093                                   lastLogon=0,
1094                                   lastLogonTimestamp=('absent', None),
1095                                   userAccountControl=
1096                                     dsdb.UF_NORMAL_ACCOUNT |
1097                                     dsdb.UF_ACCOUNTDISABLE |
1098                                     dsdb.UF_PASSWD_NOTREQD,
1099                                   msDSUserAccountControlComputed=0)
1100
1101         # Enables the user account
1102         self.ldb.enable_account("(sAMAccountName=%s)" % username)
1103
1104         res = self._check_account(userdn,
1105                                   badPwdCount=badPwdCount,
1106                                   badPasswordTime=badPasswordTime,
1107                                   logonCount=0,
1108                                   lastLogon=0,
1109                                   lastLogonTimestamp=('absent', None),
1110                                   userAccountControl=
1111                                     dsdb.UF_NORMAL_ACCOUNT,
1112                                   msDSUserAccountControlComputed=0)
1113         if lockOutObservationWindow != 0:
1114             time.sleep(lockOutObservationWindow + 1)
1115             effective_bad_password_count = 0
1116         else:
1117             effective_bad_password_count = badPwdCount
1118
1119         res = self._check_account(userdn,
1120                                   badPwdCount=badPwdCount,
1121                                   effective_bad_password_count=effective_bad_password_count,
1122                                   badPasswordTime=badPasswordTime,
1123                                   logonCount=0,
1124                                   lastLogon=0,
1125                                   lastLogonTimestamp=('absent', None),
1126                                   userAccountControl=
1127                                     dsdb.UF_NORMAL_ACCOUNT,
1128                                   msDSUserAccountControlComputed=0)
1129
1130         ldb = SamDB(url=self.host_url, credentials=creds, lp=self.lp)
1131
1132         if lockOutObservationWindow == 0:
1133             badPwdCount = 0
1134             effective_bad_password_count = 0
1135         if use_kerberos == MUST_USE_KERBEROS:
1136             badPwdCount = 0
1137             effective_bad_password_count = 0
1138
1139         res = self._check_account(userdn,
1140                                   badPwdCount=badPwdCount,
1141                                   effective_bad_password_count=effective_bad_password_count,
1142                                   badPasswordTime=badPasswordTime,
1143                                   logonCount=(logoncount_relation, 0),
1144                                   lastLogon=(lastlogon_relation, 0),
1145                                   lastLogonTimestamp=('greater', badPasswordTime),
1146                                   userAccountControl=
1147                                     dsdb.UF_NORMAL_ACCOUNT,
1148                                   msDSUserAccountControlComputed=0)
1149
1150         logonCount = int(res[0]["logonCount"][0])
1151         lastLogon = int(res[0]["lastLogon"][0])
1152         lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0])
1153         if lastlogon_relation == 'greater':
1154             self.assertGreater(lastLogon, badPasswordTime)
1155             self.assertGreaterEqual(lastLogon, lastLogonTimestamp)
1156
1157         res = self._check_account(userdn,
1158                                   badPwdCount=badPwdCount,
1159                                   effective_bad_password_count=effective_bad_password_count,
1160                                   badPasswordTime=badPasswordTime,
1161                                   logonCount=logonCount,
1162                                   lastLogon=lastLogon,
1163                                   lastLogonTimestamp=lastLogonTimestamp,
1164                                   userAccountControl=
1165                                     dsdb.UF_NORMAL_ACCOUNT,
1166                                   msDSUserAccountControlComputed=0)
1167         return ldb
1168
1169     def _reset_samr(self, res):
1170
1171         # Now reset the lockout, by removing ACB_AUTOLOCK (which removes the lock, despite being a generated attribute)
1172         samr_user = self._open_samr_user(res)
1173         acb_info = self.samr.QueryUserInfo(samr_user, 16)
1174         acb_info.acct_flags &= ~samr.ACB_AUTOLOCK
1175         self.samr.SetUserInfo(samr_user, 16, acb_info)
1176         self.samr.Close(samr_user)
1177
1178     def test_lockout_observation_window(self):
1179         lockout3krb5_creds = self.insta_creds(self.template_creds,
1180                                               username="lockout3krb5",
1181                                               userpass="thatsAcomplPASS0",
1182                                               kerberos_state=MUST_USE_KERBEROS)
1183         self._testing_add_user(lockout3krb5_creds)
1184
1185         lockout4krb5_creds = self.insta_creds(self.template_creds,
1186                                               username="lockout4krb5",
1187                                               userpass="thatsAcomplPASS0",
1188                                               kerberos_state=MUST_USE_KERBEROS)
1189         self._testing_add_user(lockout4krb5_creds,
1190                                lockOutObservationWindow=self.lockout_observation_window)
1191
1192         lockout3ntlm_creds = self.insta_creds(self.template_creds,
1193                                               username="lockout3ntlm",
1194                                               userpass="thatsAcomplPASS0",
1195                                               kerberos_state=DONT_USE_KERBEROS)
1196         self._testing_add_user(lockout3ntlm_creds)
1197         lockout4ntlm_creds = self.insta_creds(self.template_creds,
1198                                               username="lockout4ntlm",
1199                                               userpass="thatsAcomplPASS0",
1200                                               kerberos_state=DONT_USE_KERBEROS)
1201         self._testing_add_user(lockout4ntlm_creds,
1202                                lockOutObservationWindow=self.lockout_observation_window)
1203
1204 host_url = "ldap://%s" % host
1205
1206 TestProgram(module=__name__, opts=subunitopts)