s4/dsdb/tests: PY3 port samba4.user_account_control test
[Samba.git] / source4 / dsdb / tests / python / user_account_control.py
bloba64fc629c0101a4c967ba50a79063f3a5a68b566
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # This tests the restrictions on userAccountControl that apply even if write access is permitted
5 # Copyright Samuel Cabrero 2014 <samuelcabrero@kernevil.me>
6 # Copyright Andrew Bartlett 2014 <abartlet@samba.org>
8 # Licenced under the GPLv3
11 from __future__ import print_function
12 import optparse
13 import sys
14 import unittest
15 import samba
16 import samba.getopt as options
17 import samba.tests
18 import ldb
19 import base64
21 sys.path.insert(0, "bin/python")
22 from samba.tests.subunitrun import TestProgram, SubunitOptions
24 from samba.subunit.run import SubunitTestRunner
25 from samba.auth import system_session
26 from samba.samdb import SamDB
27 from samba.dcerpc import samr, security, lsa
28 from samba.credentials import Credentials
29 from samba.ndr import ndr_unpack, ndr_pack
30 from samba.tests import delete_force
31 from samba import gensec, sd_utils
32 from samba.credentials import DONT_USE_KERBEROS
33 from ldb import SCOPE_SUBTREE, SCOPE_BASE, LdbError
34 from ldb import Message, MessageElement, Dn
35 from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE
36 from samba.dsdb import UF_SCRIPT, UF_ACCOUNTDISABLE, UF_00000004, UF_HOMEDIR_REQUIRED, \
37 UF_LOCKOUT, UF_PASSWD_NOTREQD, UF_PASSWD_CANT_CHANGE, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,\
38 UF_TEMP_DUPLICATE_ACCOUNT, UF_NORMAL_ACCOUNT, UF_00000400, UF_INTERDOMAIN_TRUST_ACCOUNT, \
39 UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, UF_00004000, \
40 UF_00008000, UF_DONT_EXPIRE_PASSWD, UF_MNS_LOGON_ACCOUNT, UF_SMARTCARD_REQUIRED, \
41 UF_TRUSTED_FOR_DELEGATION, UF_NOT_DELEGATED, UF_USE_DES_KEY_ONLY, UF_DONT_REQUIRE_PREAUTH, \
42 UF_PASSWORD_EXPIRED, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, UF_NO_AUTH_DATA_REQUIRED, \
43 UF_PARTIAL_SECRETS_ACCOUNT, UF_USE_AES_KEYS
46 parser = optparse.OptionParser("user_account_control.py [options] <host>")
47 sambaopts = options.SambaOptions(parser)
48 parser.add_option_group(sambaopts)
49 parser.add_option_group(options.VersionOptions(parser))
51 # use command line creds if available
52 credopts = options.CredentialsOptions(parser)
53 parser.add_option_group(credopts)
54 opts, args = parser.parse_args()
56 if len(args) < 1:
57 parser.print_usage()
58 sys.exit(1)
59 host = args[0]
61 if "://" not in host:
62 ldaphost = "ldap://%s" % host
63 else:
64 ldaphost = host
65 start = host.rindex("://")
66 host = host.lstrip(start + 3)
68 lp = sambaopts.get_loadparm()
69 creds = credopts.get_credentials(lp)
70 creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
72 bits = [UF_SCRIPT, UF_ACCOUNTDISABLE, UF_00000004, UF_HOMEDIR_REQUIRED,
73 UF_LOCKOUT, UF_PASSWD_NOTREQD, UF_PASSWD_CANT_CHANGE,
74 UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,
75 UF_TEMP_DUPLICATE_ACCOUNT, UF_NORMAL_ACCOUNT, UF_00000400,
76 UF_INTERDOMAIN_TRUST_ACCOUNT,
77 UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, UF_00004000,
78 UF_00008000, UF_DONT_EXPIRE_PASSWD, UF_MNS_LOGON_ACCOUNT, UF_SMARTCARD_REQUIRED,
79 UF_TRUSTED_FOR_DELEGATION, UF_NOT_DELEGATED, UF_USE_DES_KEY_ONLY,
80 UF_DONT_REQUIRE_PREAUTH,
81 UF_PASSWORD_EXPIRED, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION,
82 UF_NO_AUTH_DATA_REQUIRED,
83 UF_PARTIAL_SECRETS_ACCOUNT, UF_USE_AES_KEYS,
84 int("0x10000000", 16), int("0x20000000", 16), int("0x40000000", 16), int("0x80000000", 16)]
86 account_types = set([UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT])
89 class UserAccountControlTests(samba.tests.TestCase):
90 def add_computer_ldap(self, computername, others=None, samdb=None):
91 if samdb is None:
92 samdb = self.samdb
93 dn = "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn)
94 domainname = ldb.Dn(self.samdb, self.samdb.domain_dn()).canonical_str().replace("/", "")
95 samaccountname = "%s$" % computername
96 dnshostname = "%s.%s" % (computername, domainname)
97 msg_dict = {
98 "dn": dn,
99 "objectclass": "computer"}
100 if others is not None:
101 msg_dict = dict(list(msg_dict.items()) + list(others.items()))
103 msg = ldb.Message.from_dict(self.samdb, msg_dict)
104 msg["sAMAccountName"] = samaccountname
106 print("Adding computer account %s" % computername)
107 samdb.add(msg)
109 def get_creds(self, target_username, target_password):
110 creds_tmp = Credentials()
111 creds_tmp.set_username(target_username)
112 creds_tmp.set_password(target_password)
113 creds_tmp.set_domain(creds.get_domain())
114 creds_tmp.set_realm(creds.get_realm())
115 creds_tmp.set_workstation(creds.get_workstation())
116 creds_tmp.set_gensec_features(creds_tmp.get_gensec_features()
117 | gensec.FEATURE_SEAL)
118 creds_tmp.set_kerberos_state(DONT_USE_KERBEROS) # kinit is too expensive to use in a tight loop
119 return creds_tmp
121 def setUp(self):
122 super(UserAccountControlTests, self).setUp()
123 self.admin_creds = creds
124 self.admin_samdb = SamDB(url=ldaphost,
125 session_info=system_session(),
126 credentials=self.admin_creds, lp=lp)
127 self.domain_sid = security.dom_sid(self.admin_samdb.get_domain_sid())
128 self.base_dn = self.admin_samdb.domain_dn()
130 self.unpriv_user = "testuser1"
131 self.unpriv_user_pw = "samba123@"
132 self.unpriv_creds = self.get_creds(self.unpriv_user, self.unpriv_user_pw)
134 delete_force(self.admin_samdb, "CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn))
135 delete_force(self.admin_samdb, "OU=test_computer_ou1,%s" % (self.base_dn))
136 delete_force(self.admin_samdb, "CN=%s,CN=Users,%s" % (self.unpriv_user, self.base_dn))
138 self.admin_samdb.newuser(self.unpriv_user, self.unpriv_user_pw)
139 res = self.admin_samdb.search("CN=%s,CN=Users,%s" % (self.unpriv_user, self.admin_samdb.domain_dn()),
140 scope=SCOPE_BASE,
141 attrs=["objectSid"])
142 self.assertEqual(1, len(res))
144 self.unpriv_user_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
145 self.unpriv_user_dn = res[0].dn
147 self.samdb = SamDB(url=ldaphost, credentials=self.unpriv_creds, lp=lp)
149 self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % host, lp, self.unpriv_creds)
150 self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
151 self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
153 self.sd_utils = sd_utils.SDUtils(self.admin_samdb)
155 self.admin_samdb.create_ou("OU=test_computer_ou1," + self.base_dn)
156 self.unpriv_user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
157 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.unpriv_user_sid)
159 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
161 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
163 self.add_computer_ldap("testcomputer-t")
165 self.sd_utils.modify_sd_on_dn("OU=test_computer_ou1," + self.base_dn, old_sd)
167 self.computernames = ["testcomputer-0"]
169 # Get the SD of the template account, then force it to match
170 # what we expect for SeMachineAccountPrivilege accounts, so we
171 # can confirm we created the accounts correctly
172 self.sd_reference_cc = self.sd_utils.read_sd_on_dn("CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn))
174 self.sd_reference_modify = self.sd_utils.read_sd_on_dn("CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn))
175 for ace in self.sd_reference_modify.dacl.aces:
176 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED and ace.trustee == self.unpriv_user_sid:
177 ace.access_mask = ace.access_mask | security.SEC_ADS_SELF_WRITE | security.SEC_ADS_WRITE_PROP
179 # Now reconnect without domain admin rights
180 self.samdb = SamDB(url=ldaphost, credentials=self.unpriv_creds, lp=lp)
182 def tearDown(self):
183 super(UserAccountControlTests, self).tearDown()
184 for computername in self.computernames:
185 delete_force(self.admin_samdb, "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn))
186 delete_force(self.admin_samdb, "CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn))
187 delete_force(self.admin_samdb, "OU=test_computer_ou1,%s" % (self.base_dn))
188 delete_force(self.admin_samdb, "CN=%s,CN=Users,%s" % (self.unpriv_user, self.base_dn))
190 def test_add_computer_sd_cc(self):
191 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
192 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
194 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
196 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
198 computername = self.computernames[0]
199 sd = ldb.MessageElement((ndr_pack(self.sd_reference_modify)),
200 ldb.FLAG_MOD_ADD,
201 "nTSecurityDescriptor")
202 self.add_computer_ldap(computername,
203 others={"nTSecurityDescriptor": sd})
205 res = self.admin_samdb.search("%s" % self.base_dn,
206 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
207 scope=SCOPE_SUBTREE,
208 attrs=["ntSecurityDescriptor"])
210 desc = res[0]["nTSecurityDescriptor"][0]
211 desc = ndr_unpack(security.descriptor, desc, allow_remaining=True)
213 sddl = desc.as_sddl(self.domain_sid)
214 self.assertEqual(self.sd_reference_modify.as_sddl(self.domain_sid), sddl)
216 m = ldb.Message()
217 m.dn = res[0].dn
218 m["description"] = ldb.MessageElement(
219 ("A description"), ldb.FLAG_MOD_REPLACE,
220 "description")
221 self.samdb.modify(m)
223 m = ldb.Message()
224 m.dn = res[0].dn
225 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_SERVER_TRUST_ACCOUNT),
226 ldb.FLAG_MOD_REPLACE, "userAccountControl")
227 try:
228 self.samdb.modify(m)
229 self.fail("Unexpectedly able to set userAccountControl to be a DC on %s" % m.dn)
230 except LdbError as e5:
231 (enum, estr) = e5.args
232 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
234 m = ldb.Message()
235 m.dn = res[0].dn
236 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
237 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT),
238 ldb.FLAG_MOD_REPLACE, "userAccountControl")
239 try:
240 self.samdb.modify(m)
241 self.fail("Unexpectedly able to set userAccountControl to be an RODC on %s" % m.dn)
242 except LdbError as e6:
243 (enum, estr) = e6.args
244 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
246 m = ldb.Message()
247 m.dn = res[0].dn
248 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT),
249 ldb.FLAG_MOD_REPLACE, "userAccountControl")
250 try:
251 self.samdb.modify(m)
252 self.fail("Unexpectedly able to set userAccountControl to be an Workstation on %s" % m.dn)
253 except LdbError as e7:
254 (enum, estr) = e7.args
255 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
257 m = ldb.Message()
258 m.dn = res[0].dn
259 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT),
260 ldb.FLAG_MOD_REPLACE, "userAccountControl")
261 self.samdb.modify(m)
263 m = ldb.Message()
264 m.dn = res[0].dn
265 m["primaryGroupID"] = ldb.MessageElement(str(security.DOMAIN_RID_ADMINS),
266 ldb.FLAG_MOD_REPLACE, "primaryGroupID")
267 try:
268 self.samdb.modify(m)
269 except LdbError as e8:
270 (enum, estr) = e8.args
271 self.assertEqual(ldb.ERR_UNWILLING_TO_PERFORM, enum)
272 return
273 self.fail()
275 def test_mod_computer_cc(self):
276 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
277 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
279 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
281 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
283 computername = self.computernames[0]
284 self.add_computer_ldap(computername)
286 res = self.admin_samdb.search("%s" % self.base_dn,
287 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
288 scope=SCOPE_SUBTREE,
289 attrs=[])
291 m = ldb.Message()
292 m.dn = res[0].dn
293 m["description"] = ldb.MessageElement(
294 ("A description"), ldb.FLAG_MOD_REPLACE,
295 "description")
296 self.samdb.modify(m)
298 m = ldb.Message()
299 m.dn = res[0].dn
300 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |
301 samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT),
302 ldb.FLAG_MOD_REPLACE, "userAccountControl")
303 try:
304 self.samdb.modify(m)
305 self.fail("Unexpectedly able to set userAccountControl on %s" % m.dn)
306 except LdbError as e9:
307 (enum, estr) = e9.args
308 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
310 m = ldb.Message()
311 m.dn = res[0].dn
312 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_SERVER_TRUST_ACCOUNT),
313 ldb.FLAG_MOD_REPLACE, "userAccountControl")
314 try:
315 self.samdb.modify(m)
316 self.fail()
317 except LdbError as e10:
318 (enum, estr) = e10.args
319 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
321 m = ldb.Message()
322 m.dn = res[0].dn
323 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT),
324 ldb.FLAG_MOD_REPLACE, "userAccountControl")
325 self.samdb.modify(m)
327 m = ldb.Message()
328 m.dn = res[0].dn
329 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT),
330 ldb.FLAG_MOD_REPLACE, "userAccountControl")
331 try:
332 self.samdb.modify(m)
333 self.fail("Unexpectedly able to set userAccountControl to be an Workstation on %s" % m.dn)
334 except LdbError as e11:
335 (enum, estr) = e11.args
336 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
338 def test_admin_mod_uac(self):
339 computername = self.computernames[0]
340 self.add_computer_ldap(computername, samdb=self.admin_samdb)
342 res = self.admin_samdb.search("%s" % self.base_dn,
343 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
344 scope=SCOPE_SUBTREE,
345 attrs=["userAccountControl"])
347 self.assertEqual(int(res[0]["userAccountControl"][0]), (UF_NORMAL_ACCOUNT |
348 UF_ACCOUNTDISABLE |
349 UF_PASSWD_NOTREQD))
351 m = ldb.Message()
352 m.dn = res[0].dn
353 m["userAccountControl"] = ldb.MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT |
354 UF_PARTIAL_SECRETS_ACCOUNT |
355 UF_TRUSTED_FOR_DELEGATION),
356 ldb.FLAG_MOD_REPLACE, "userAccountControl")
357 try:
358 self.admin_samdb.modify(m)
359 self.fail("Unexpectedly able to set userAccountControl to UF_WORKSTATION_TRUST_ACCOUNT|UF_PARTIAL_SECRETS_ACCOUNT|UF_TRUSTED_FOR_DELEGATION on %s" % m.dn)
360 except LdbError as e12:
361 (enum, estr) = e12.args
362 self.assertEqual(ldb.ERR_OTHER, enum)
364 m = ldb.Message()
365 m.dn = res[0].dn
366 m["userAccountControl"] = ldb.MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT |
367 UF_PARTIAL_SECRETS_ACCOUNT),
368 ldb.FLAG_MOD_REPLACE, "userAccountControl")
369 self.admin_samdb.modify(m)
371 res = self.admin_samdb.search("%s" % self.base_dn,
372 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
373 scope=SCOPE_SUBTREE,
374 attrs=["userAccountControl"])
376 self.assertEqual(int(res[0]["userAccountControl"][0]), (UF_WORKSTATION_TRUST_ACCOUNT |
377 UF_PARTIAL_SECRETS_ACCOUNT))
378 m = ldb.Message()
379 m.dn = res[0].dn
380 m["userAccountControl"] = ldb.MessageElement(str(UF_ACCOUNTDISABLE),
381 ldb.FLAG_MOD_REPLACE, "userAccountControl")
382 self.admin_samdb.modify(m)
384 res = self.admin_samdb.search("%s" % self.base_dn,
385 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
386 scope=SCOPE_SUBTREE,
387 attrs=["userAccountControl"])
389 self.assertEqual(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE)
391 def test_uac_bits_set(self):
392 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
393 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
395 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
397 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
399 computername = self.computernames[0]
400 self.add_computer_ldap(computername)
402 res = self.admin_samdb.search("%s" % self.base_dn,
403 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
404 scope=SCOPE_SUBTREE,
405 attrs=[])
407 m = ldb.Message()
408 m.dn = res[0].dn
409 m["description"] = ldb.MessageElement(
410 ("A description"), ldb.FLAG_MOD_REPLACE,
411 "description")
412 self.samdb.modify(m)
414 # These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test
415 priv_to_auth_users_bits = set([UF_PASSWD_NOTREQD, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,
416 UF_DONT_EXPIRE_PASSWD])
418 # These bits really are privileged, or can't be changed from UF_NORMAL as a non-admin
419 priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT,
420 UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION,
421 UF_WORKSTATION_TRUST_ACCOUNT])
423 invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT])
425 for bit in bits:
426 m = ldb.Message()
427 m.dn = res[0].dn
428 m["userAccountControl"] = ldb.MessageElement(str(bit | UF_PASSWD_NOTREQD),
429 ldb.FLAG_MOD_REPLACE, "userAccountControl")
430 try:
431 self.samdb.modify(m)
432 if (bit in priv_bits):
433 self.fail("Unexpectedly able to set userAccountControl bit 0x%08X on %s" % (bit, m.dn))
434 except LdbError as e:
435 (enum, estr) = e.args
436 if bit in invalid_bits:
437 self.assertEqual(enum, ldb.ERR_OTHER, "was not able to set 0x%08X on %s" % (bit, m.dn))
438 # No point going on, try the next bit
439 continue
440 elif (bit in priv_bits):
441 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
442 else:
443 self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
445 def uac_bits_unrelated_modify_helper(self, account_type):
446 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
447 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
449 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
451 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
453 computername = self.computernames[0]
454 self.add_computer_ldap(computername, others={"userAccountControl": [str(account_type)]})
456 res = self.admin_samdb.search("%s" % self.base_dn,
457 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
458 scope=SCOPE_SUBTREE,
459 attrs=["userAccountControl"])
460 self.assertEqual(int(res[0]["userAccountControl"][0]), account_type)
462 m = ldb.Message()
463 m.dn = res[0].dn
464 m["description"] = ldb.MessageElement(
465 ("A description"), ldb.FLAG_MOD_REPLACE,
466 "description")
467 self.samdb.modify(m)
469 invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT])
471 # UF_LOCKOUT isn't actually ignored, it changes other
472 # attributes but does not stick here. See MS-SAMR 2.2.1.13
473 # UF_FLAG Codes clarification that UF_SCRIPT and
474 # UF_PASSWD_CANT_CHANGE are simply ignored by both clients and
475 # servers. Other bits are ignored as they are undefined, or
476 # are not set into the attribute (instead triggering other
477 # events).
478 ignored_bits = set([UF_SCRIPT, UF_00000004, UF_LOCKOUT, UF_PASSWD_CANT_CHANGE,
479 UF_00000400, UF_00004000, UF_00008000, UF_PASSWORD_EXPIRED,
480 int("0x10000000", 16), int("0x20000000", 16), int("0x40000000", 16), int("0x80000000", 16)])
481 super_priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT])
483 priv_to_remove_bits = set([UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, UF_WORKSTATION_TRUST_ACCOUNT])
485 for bit in bits:
486 # Reset this to the initial position, just to be sure
487 m = ldb.Message()
488 m.dn = res[0].dn
489 m["userAccountControl"] = ldb.MessageElement(str(account_type),
490 ldb.FLAG_MOD_REPLACE, "userAccountControl")
491 self.admin_samdb.modify(m)
493 res = self.admin_samdb.search("%s" % self.base_dn,
494 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
495 scope=SCOPE_SUBTREE,
496 attrs=["userAccountControl"])
498 self.assertEqual(int(res[0]["userAccountControl"][0]), account_type)
500 m = ldb.Message()
501 m.dn = res[0].dn
502 m["userAccountControl"] = ldb.MessageElement(str(bit | UF_PASSWD_NOTREQD),
503 ldb.FLAG_MOD_REPLACE, "userAccountControl")
504 try:
505 self.admin_samdb.modify(m)
506 if bit in invalid_bits:
507 self.fail("Should have been unable to set userAccountControl bit 0x%08X on %s" % (bit, m.dn))
509 except LdbError as e1:
510 (enum, estr) = e1.args
511 if bit in invalid_bits:
512 self.assertEqual(enum, ldb.ERR_OTHER)
513 # No point going on, try the next bit
514 continue
515 elif bit in super_priv_bits:
516 self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)
517 # No point going on, try the next bit
518 continue
519 else:
520 self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
522 res = self.admin_samdb.search("%s" % self.base_dn,
523 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
524 scope=SCOPE_SUBTREE,
525 attrs=["userAccountControl"])
527 if bit in ignored_bits:
528 self.assertEqual(int(res[0]["userAccountControl"][0]),
529 UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD,
530 "Bit 0x%08x shouldn't stick" % bit)
531 else:
532 if bit in account_types:
533 self.assertEqual(int(res[0]["userAccountControl"][0]),
534 bit | UF_PASSWD_NOTREQD,
535 "Bit 0x%08x didn't stick" % bit)
536 else:
537 self.assertEqual(int(res[0]["userAccountControl"][0]),
538 bit | UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD,
539 "Bit 0x%08x didn't stick" % bit)
541 try:
542 m = ldb.Message()
543 m.dn = res[0].dn
544 m["userAccountControl"] = ldb.MessageElement(str(bit | UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE),
545 ldb.FLAG_MOD_REPLACE, "userAccountControl")
546 self.samdb.modify(m)
548 except LdbError as e2:
549 (enum, estr) = e2.args
550 self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
552 res = self.admin_samdb.search("%s" % self.base_dn,
553 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
554 scope=SCOPE_SUBTREE,
555 attrs=["userAccountControl"])
557 if bit in account_types:
558 self.assertEqual(int(res[0]["userAccountControl"][0]),
559 bit | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD,
560 "bit 0X%08x should have been added (0X%08x vs 0X%08x)"
561 % (bit, int(res[0]["userAccountControl"][0]),
562 bit | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD))
563 elif bit in ignored_bits:
564 self.assertEqual(int(res[0]["userAccountControl"][0]),
565 UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD,
566 "bit 0X%08x should have been added (0X%08x vs 0X%08x)"
567 % (bit, int(res[0]["userAccountControl"][0]),
568 UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD))
570 else:
571 self.assertEqual(int(res[0]["userAccountControl"][0]),
572 bit | UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD,
573 "bit 0X%08x should have been added (0X%08x vs 0X%08x)"
574 % (bit, int(res[0]["userAccountControl"][0]),
575 bit | UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD))
577 try:
578 m = ldb.Message()
579 m.dn = res[0].dn
580 m["userAccountControl"] = ldb.MessageElement(str(UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE),
581 ldb.FLAG_MOD_REPLACE, "userAccountControl")
582 self.samdb.modify(m)
583 if bit in priv_to_remove_bits:
584 self.fail("Should have been unable to remove userAccountControl bit 0x%08X on %s" % (bit, m.dn))
586 except LdbError as e3:
587 (enum, estr) = e3.args
588 if bit in priv_to_remove_bits:
589 self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)
590 else:
591 self.fail("Unexpectedly unable to remove userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
593 res = self.admin_samdb.search("%s" % self.base_dn,
594 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
595 scope=SCOPE_SUBTREE,
596 attrs=["userAccountControl"])
598 if bit in priv_to_remove_bits:
599 if bit in account_types:
600 self.assertEqual(int(res[0]["userAccountControl"][0]),
601 bit | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD,
602 "bit 0X%08x should not have been removed" % bit)
603 else:
604 self.assertEqual(int(res[0]["userAccountControl"][0]),
605 bit | UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD,
606 "bit 0X%08x should not have been removed" % bit)
607 else:
608 self.assertEqual(int(res[0]["userAccountControl"][0]),
609 UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD,
610 "bit 0X%08x should have been removed" % bit)
612 def test_uac_bits_unrelated_modify_normal(self):
613 self.uac_bits_unrelated_modify_helper(UF_NORMAL_ACCOUNT)
615 def test_uac_bits_unrelated_modify_workstation(self):
616 self.uac_bits_unrelated_modify_helper(UF_WORKSTATION_TRUST_ACCOUNT)
618 def test_uac_bits_add(self):
619 computername = self.computernames[0]
621 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
622 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
624 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
626 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
628 invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT])
629 # These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test
630 priv_to_auth_users_bits = set([UF_PASSWD_NOTREQD, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,
631 UF_DONT_EXPIRE_PASSWD])
633 # These bits really are privileged
634 priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT,
635 UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION])
637 for bit in bits:
638 try:
639 self.add_computer_ldap(computername, others={"userAccountControl": [str(bit)]})
640 delete_force(self.admin_samdb, "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn))
641 if bit in priv_bits:
642 self.fail("Unexpectdly able to set userAccountControl bit 0x%08X on %s" % (bit, computername))
644 except LdbError as e4:
645 (enum, estr) = e4.args
646 if bit in invalid_bits:
647 self.assertEqual(enum, ldb.ERR_OTHER, "Invalid bit 0x%08X was able to be set on %s" % (bit, computername))
648 # No point going on, try the next bit
649 continue
650 elif bit in priv_bits:
651 self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)
652 continue
653 else:
654 self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, computername, estr))
656 def test_primarygroupID_cc_add(self):
657 computername = self.computernames[0]
659 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
660 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
662 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
664 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
665 try:
666 # When creating a new object, you can not ever set the primaryGroupID
667 self.add_computer_ldap(computername, others={"primaryGroupID": [str(security.DOMAIN_RID_ADMINS)]})
668 self.fail("Unexpectedly able to set primaryGruopID to be an admin on %s" % computername)
669 except LdbError as e13:
670 (enum, estr) = e13.args
671 self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM)
673 def test_primarygroupID_priv_DC_modify(self):
674 computername = self.computernames[0]
676 self.add_computer_ldap(computername,
677 others={"userAccountControl": [str(UF_SERVER_TRUST_ACCOUNT)]},
678 samdb=self.admin_samdb)
679 res = self.admin_samdb.search("%s" % self.base_dn,
680 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
681 scope=SCOPE_SUBTREE,
682 attrs=[""])
684 m = ldb.Message()
685 m.dn = ldb.Dn(self.admin_samdb, "<SID=%s-%d>" % (str(self.domain_sid),
686 security.DOMAIN_RID_USERS))
687 m["member"] = ldb.MessageElement(
688 [str(res[0].dn)], ldb.FLAG_MOD_ADD,
689 "member")
690 self.admin_samdb.modify(m)
692 m = ldb.Message()
693 m.dn = res[0].dn
694 m["primaryGroupID"] = ldb.MessageElement(
695 [str(security.DOMAIN_RID_USERS)], ldb.FLAG_MOD_REPLACE,
696 "primaryGroupID")
697 try:
698 self.admin_samdb.modify(m)
700 # When creating a new object, you can not ever set the primaryGroupID
701 self.fail("Unexpectedly able to set primaryGroupID to be other than DCS on %s" % computername)
702 except LdbError as e14:
703 (enum, estr) = e14.args
704 self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM)
706 def test_primarygroupID_priv_member_modify(self):
707 computername = self.computernames[0]
709 self.add_computer_ldap(computername,
710 others={"userAccountControl": [str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)]},
711 samdb=self.admin_samdb)
712 res = self.admin_samdb.search("%s" % self.base_dn,
713 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
714 scope=SCOPE_SUBTREE,
715 attrs=[""])
717 m = ldb.Message()
718 m.dn = ldb.Dn(self.admin_samdb, "<SID=%s-%d>" % (str(self.domain_sid),
719 security.DOMAIN_RID_USERS))
720 m["member"] = ldb.MessageElement(
721 [str(res[0].dn)], ldb.FLAG_MOD_ADD,
722 "member")
723 self.admin_samdb.modify(m)
725 m = ldb.Message()
726 m.dn = res[0].dn
727 m["primaryGroupID"] = ldb.MessageElement(
728 [str(security.DOMAIN_RID_USERS)], ldb.FLAG_MOD_REPLACE,
729 "primaryGroupID")
730 try:
731 self.admin_samdb.modify(m)
733 # When creating a new object, you can not ever set the primaryGroupID
734 self.fail("Unexpectedly able to set primaryGroupID to be other than DCS on %s" % computername)
735 except LdbError as e15:
736 (enum, estr) = e15.args
737 self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM)
739 def test_primarygroupID_priv_user_modify(self):
740 computername = self.computernames[0]
742 self.add_computer_ldap(computername,
743 others={"userAccountControl": [str(UF_WORKSTATION_TRUST_ACCOUNT)]},
744 samdb=self.admin_samdb)
745 res = self.admin_samdb.search("%s" % self.base_dn,
746 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
747 scope=SCOPE_SUBTREE,
748 attrs=[""])
750 m = ldb.Message()
751 m.dn = ldb.Dn(self.admin_samdb, "<SID=%s-%d>" % (str(self.domain_sid),
752 security.DOMAIN_RID_ADMINS))
753 m["member"] = ldb.MessageElement(
754 [str(res[0].dn)], ldb.FLAG_MOD_ADD,
755 "member")
756 self.admin_samdb.modify(m)
758 m = ldb.Message()
759 m.dn = res[0].dn
760 m["primaryGroupID"] = ldb.MessageElement(
761 [str(security.DOMAIN_RID_ADMINS)], ldb.FLAG_MOD_REPLACE,
762 "primaryGroupID")
763 self.admin_samdb.modify(m)
766 runner = SubunitTestRunner()
767 rc = 0
768 if not runner.run(unittest.makeSuite(UserAccountControlTests)).wasSuccessful():
769 rc = 1
770 sys.exit(rc)