s4:dsdb/tests: use ncacn_ip_tcp:server[seal] for samr connections
[Samba.git] / source4 / dsdb / tests / python / user_account_control.py
blobc583883ae28e1004cafc28369dce33a088eaaa49
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 import optparse
12 import sys
13 import unittest
14 import samba
15 import samba.getopt as options
16 import samba.tests
17 import ldb
18 import base64
20 sys.path.insert(0, "bin/python")
21 from samba.tests.subunitrun import TestProgram, SubunitOptions
23 from samba.subunit.run import SubunitTestRunner
24 from samba.auth import system_session
25 from samba.samdb import SamDB
26 from samba.dcerpc import samr, security, lsa
27 from samba.credentials import Credentials
28 from samba.ndr import ndr_unpack, ndr_pack
29 from samba.tests import delete_force
30 from samba import gensec, sd_utils
31 from samba.credentials import DONT_USE_KERBEROS
32 from ldb import SCOPE_SUBTREE, SCOPE_BASE, LdbError
33 from ldb import Message, MessageElement, Dn
34 from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE
35 from samba.dsdb import UF_SCRIPT, UF_ACCOUNTDISABLE, UF_00000004, UF_HOMEDIR_REQUIRED, \
36 UF_LOCKOUT,UF_PASSWD_NOTREQD, UF_PASSWD_CANT_CHANGE, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,\
37 UF_TEMP_DUPLICATE_ACCOUNT, UF_NORMAL_ACCOUNT, UF_00000400, UF_INTERDOMAIN_TRUST_ACCOUNT, \
38 UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, UF_00004000, \
39 UF_00008000, UF_DONT_EXPIRE_PASSWD, UF_MNS_LOGON_ACCOUNT, UF_SMARTCARD_REQUIRED, \
40 UF_TRUSTED_FOR_DELEGATION, UF_NOT_DELEGATED, UF_USE_DES_KEY_ONLY, UF_DONT_REQUIRE_PREAUTH, \
41 UF_PASSWORD_EXPIRED, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, UF_NO_AUTH_DATA_REQUIRED, \
42 UF_PARTIAL_SECRETS_ACCOUNT, UF_USE_AES_KEYS
45 parser = optparse.OptionParser("user_account_control.py [options] <host>")
46 sambaopts = options.SambaOptions(parser)
47 parser.add_option_group(sambaopts)
48 parser.add_option_group(options.VersionOptions(parser))
50 # use command line creds if available
51 credopts = options.CredentialsOptions(parser)
52 parser.add_option_group(credopts)
53 opts, args = parser.parse_args()
55 if len(args) < 1:
56 parser.print_usage()
57 sys.exit(1)
58 host = args[0]
60 if not "://" in host:
61 ldaphost = "ldap://%s" % host
62 else:
63 ldaphost = host
64 start = host.rindex("://")
65 host = host.lstrip(start+3)
67 lp = sambaopts.get_loadparm()
68 creds = credopts.get_credentials(lp)
69 creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
71 bits = [UF_SCRIPT, UF_ACCOUNTDISABLE, UF_00000004, UF_HOMEDIR_REQUIRED,
72 UF_LOCKOUT,UF_PASSWD_NOTREQD, UF_PASSWD_CANT_CHANGE,
73 UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,
74 UF_TEMP_DUPLICATE_ACCOUNT, UF_NORMAL_ACCOUNT, UF_00000400,
75 UF_INTERDOMAIN_TRUST_ACCOUNT,
76 UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, UF_00004000,
77 UF_00008000, UF_DONT_EXPIRE_PASSWD, UF_MNS_LOGON_ACCOUNT, UF_SMARTCARD_REQUIRED,
78 UF_TRUSTED_FOR_DELEGATION, UF_NOT_DELEGATED, UF_USE_DES_KEY_ONLY,
79 UF_DONT_REQUIRE_PREAUTH,
80 UF_PASSWORD_EXPIRED, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION,
81 UF_NO_AUTH_DATA_REQUIRED,
82 UF_PARTIAL_SECRETS_ACCOUNT, UF_USE_AES_KEYS,
83 int("0x10000000", 16), int("0x20000000", 16), int("0x40000000", 16), int("0x80000000", 16)]
85 account_types = set([UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT])
88 class UserAccountControlTests(samba.tests.TestCase):
89 def add_computer_ldap(self, computername, others=None, samdb=None):
90 if samdb is None:
91 samdb = self.samdb
92 dn = "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn)
93 domainname = ldb.Dn(self.samdb, self.samdb.domain_dn()).canonical_str().replace("/", "")
94 samaccountname = "%s$" % computername
95 dnshostname = "%s.%s" % (computername, domainname)
96 msg_dict = {
97 "dn": dn,
98 "objectclass": "computer"}
99 if others is not None:
100 msg_dict = dict(msg_dict.items() + others.items())
102 msg = ldb.Message.from_dict(self.samdb, msg_dict )
103 msg["sAMAccountName"] = samaccountname
105 print "Adding computer account %s" % computername
106 samdb.add(msg)
108 def get_creds(self, target_username, target_password):
109 creds_tmp = Credentials()
110 creds_tmp.set_username(target_username)
111 creds_tmp.set_password(target_password)
112 creds_tmp.set_domain(creds.get_domain())
113 creds_tmp.set_realm(creds.get_realm())
114 creds_tmp.set_workstation(creds.get_workstation())
115 creds_tmp.set_gensec_features(creds_tmp.get_gensec_features()
116 | gensec.FEATURE_SEAL)
117 creds_tmp.set_kerberos_state(DONT_USE_KERBEROS) # kinit is too expensive to use in a tight loop
118 return creds_tmp
120 def setUp(self):
121 super(UserAccountControlTests, self).setUp()
122 self.admin_creds = creds
123 self.admin_samdb = SamDB(url=ldaphost,
124 session_info=system_session(),
125 credentials=self.admin_creds, lp=lp)
127 self.unpriv_user = "testuser1"
128 self.unpriv_user_pw = "samba123@"
129 self.unpriv_creds = self.get_creds(self.unpriv_user, self.unpriv_user_pw)
131 self.admin_samdb.newuser(self.unpriv_user, self.unpriv_user_pw)
132 res = self.admin_samdb.search("CN=%s,CN=Users,%s" % (self.unpriv_user, self.admin_samdb.domain_dn()),
133 scope=SCOPE_BASE,
134 attrs=["objectSid"])
135 self.assertEqual(1, len(res))
137 self.unpriv_user_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
138 self.unpriv_user_dn = res[0].dn
140 self.samdb = SamDB(url=ldaphost, credentials=self.unpriv_creds, lp=lp)
141 self.domain_sid = security.dom_sid(self.samdb.get_domain_sid())
142 self.base_dn = self.samdb.domain_dn()
144 self.samr = samr.samr("ncacn_ip_tcp:%s[seal]" % host, lp, self.unpriv_creds)
145 self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
146 self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
148 self.sd_utils = sd_utils.SDUtils(self.admin_samdb)
150 self.admin_samdb.create_ou("OU=test_computer_ou1," + self.base_dn)
151 self.unpriv_user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
152 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.unpriv_user_sid)
154 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
156 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
158 self.add_computer_ldap("testcomputer-t")
160 self.sd_utils.modify_sd_on_dn("OU=test_computer_ou1," + self.base_dn, old_sd)
162 self.computernames = ["testcomputer-0"]
164 # Get the SD of the template account, then force it to match
165 # what we expect for SeMachineAccountPrivilege accounts, so we
166 # can confirm we created the accounts correctly
167 self.sd_reference_cc = self.sd_utils.read_sd_on_dn("CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn))
169 self.sd_reference_modify = self.sd_utils.read_sd_on_dn("CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn))
170 for ace in self.sd_reference_modify.dacl.aces:
171 if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED and ace.trustee == self.unpriv_user_sid:
172 ace.access_mask = ace.access_mask | security.SEC_ADS_SELF_WRITE | security.SEC_ADS_WRITE_PROP
174 # Now reconnect without domain admin rights
175 self.samdb = SamDB(url=ldaphost, credentials=self.unpriv_creds, lp=lp)
178 def tearDown(self):
179 super(UserAccountControlTests, self).tearDown()
180 for computername in self.computernames:
181 delete_force(self.admin_samdb, "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn))
182 delete_force(self.admin_samdb, "CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn))
183 delete_force(self.admin_samdb, "OU=test_computer_ou1,%s" % (self.base_dn))
184 delete_force(self.admin_samdb, "CN=%s,CN=Users,%s" % (self.unpriv_user, self.base_dn))
186 def test_add_computer_sd_cc(self):
187 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
188 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
190 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
192 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
194 computername=self.computernames[0]
195 sd = ldb.MessageElement((ndr_pack(self.sd_reference_modify)),
196 ldb.FLAG_MOD_ADD,
197 "nTSecurityDescriptor")
198 self.add_computer_ldap(computername,
199 others={"nTSecurityDescriptor": sd})
201 res = self.admin_samdb.search("%s" % self.base_dn,
202 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
203 scope=SCOPE_SUBTREE,
204 attrs=["ntSecurityDescriptor"])
206 desc = res[0]["nTSecurityDescriptor"][0]
207 desc = ndr_unpack(security.descriptor, desc, allow_remaining=True)
209 sddl = desc.as_sddl(self.domain_sid)
210 self.assertEqual(self.sd_reference_modify.as_sddl(self.domain_sid), sddl)
212 m = ldb.Message()
213 m.dn = res[0].dn
214 m["description"]= ldb.MessageElement(
215 ("A description"), ldb.FLAG_MOD_REPLACE,
216 "description")
217 self.samdb.modify(m)
219 m = ldb.Message()
220 m.dn = res[0].dn
221 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_SERVER_TRUST_ACCOUNT),
222 ldb.FLAG_MOD_REPLACE, "userAccountControl")
223 try:
224 self.samdb.modify(m)
225 self.fail("Unexpectedly able to set userAccountControl to be a DC on %s" % m.dn)
226 except LdbError, (enum, estr):
227 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
229 m = ldb.Message()
230 m.dn = res[0].dn
231 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT|samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT),
232 ldb.FLAG_MOD_REPLACE, "userAccountControl")
233 try:
234 self.samdb.modify(m)
235 self.fail("Unexpectedly able to set userAccountControl to be an RODC on %s" % m.dn)
236 except LdbError, (enum, estr):
237 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
239 m = ldb.Message()
240 m.dn = res[0].dn
241 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT),
242 ldb.FLAG_MOD_REPLACE, "userAccountControl")
243 try:
244 self.samdb.modify(m)
245 self.fail("Unexpectedly able to set userAccountControl to be an Workstation on %s" % m.dn)
246 except LdbError, (enum, estr):
247 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
249 m = ldb.Message()
250 m.dn = res[0].dn
251 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT),
252 ldb.FLAG_MOD_REPLACE, "userAccountControl")
253 self.samdb.modify(m)
255 m = ldb.Message()
256 m.dn = res[0].dn
257 m["primaryGroupID"] = ldb.MessageElement(str(security.DOMAIN_RID_ADMINS),
258 ldb.FLAG_MOD_REPLACE, "primaryGroupID")
259 try:
260 self.samdb.modify(m)
261 except LdbError, (enum, estr):
262 self.assertEqual(ldb.ERR_UNWILLING_TO_PERFORM, enum)
263 return
264 self.fail()
266 def test_mod_computer_cc(self):
267 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
268 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
270 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
272 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
274 computername=self.computernames[0]
275 self.add_computer_ldap(computername)
277 res = self.admin_samdb.search("%s" % self.base_dn,
278 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
279 scope=SCOPE_SUBTREE,
280 attrs=[])
282 m = ldb.Message()
283 m.dn = res[0].dn
284 m["description"]= ldb.MessageElement(
285 ("A description"), ldb.FLAG_MOD_REPLACE,
286 "description")
287 self.samdb.modify(m)
289 m = ldb.Message()
290 m.dn = res[0].dn
291 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT|samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT),
292 ldb.FLAG_MOD_REPLACE, "userAccountControl")
293 try:
294 self.samdb.modify(m)
295 self.fail("Unexpectedly able to set userAccountControl on %s" % m.dn)
296 except LdbError, (enum, estr):
297 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
299 m = ldb.Message()
300 m.dn = res[0].dn
301 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_SERVER_TRUST_ACCOUNT),
302 ldb.FLAG_MOD_REPLACE, "userAccountControl")
303 try:
304 self.samdb.modify(m)
305 self.fail()
306 except LdbError, (enum, estr):
307 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
309 m = ldb.Message()
310 m.dn = res[0].dn
311 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT),
312 ldb.FLAG_MOD_REPLACE, "userAccountControl")
313 self.samdb.modify(m)
315 m = ldb.Message()
316 m.dn = res[0].dn
317 m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT),
318 ldb.FLAG_MOD_REPLACE, "userAccountControl")
319 try:
320 self.samdb.modify(m)
321 self.fail("Unexpectedly able to set userAccountControl to be an Workstation on %s" % m.dn)
322 except LdbError, (enum, estr):
323 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
326 def test_admin_mod_uac(self):
327 computername=self.computernames[0]
328 self.add_computer_ldap(computername, samdb=self.admin_samdb)
330 res = self.admin_samdb.search("%s" % self.base_dn,
331 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
332 scope=SCOPE_SUBTREE,
333 attrs=["userAccountControl"])
335 self.assertEqual(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT|UF_ACCOUNTDISABLE|UF_PASSWD_NOTREQD)
337 m = ldb.Message()
338 m.dn = res[0].dn
339 m["userAccountControl"] = ldb.MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT|UF_PARTIAL_SECRETS_ACCOUNT|UF_TRUSTED_FOR_DELEGATION),
340 ldb.FLAG_MOD_REPLACE, "userAccountControl")
341 try:
342 self.admin_samdb.modify(m)
343 self.fail("Unexpectedly able to set userAccountControl to UF_WORKSTATION_TRUST_ACCOUNT|UF_PARTIAL_SECRETS_ACCOUNT|UF_TRUSTED_FOR_DELEGATION on %s" % m.dn)
344 except LdbError, (enum, estr):
345 self.assertEqual(ldb.ERR_OTHER, enum)
347 m = ldb.Message()
348 m.dn = res[0].dn
349 m["userAccountControl"] = ldb.MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT|UF_PARTIAL_SECRETS_ACCOUNT),
350 ldb.FLAG_MOD_REPLACE, "userAccountControl")
351 self.admin_samdb.modify(m)
353 res = self.admin_samdb.search("%s" % self.base_dn,
354 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
355 scope=SCOPE_SUBTREE,
356 attrs=["userAccountControl"])
358 self.assertEqual(int(res[0]["userAccountControl"][0]), UF_WORKSTATION_TRUST_ACCOUNT|UF_PARTIAL_SECRETS_ACCOUNT)
359 m = ldb.Message()
360 m.dn = res[0].dn
361 m["userAccountControl"] = ldb.MessageElement(str(UF_ACCOUNTDISABLE),
362 ldb.FLAG_MOD_REPLACE, "userAccountControl")
363 self.admin_samdb.modify(m)
365 res = self.admin_samdb.search("%s" % self.base_dn,
366 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
367 scope=SCOPE_SUBTREE,
368 attrs=["userAccountControl"])
370 self.assertEqual(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT| UF_ACCOUNTDISABLE)
373 def test_uac_bits_set(self):
374 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
375 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
377 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
379 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
381 computername=self.computernames[0]
382 self.add_computer_ldap(computername)
384 res = self.admin_samdb.search("%s" % self.base_dn,
385 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
386 scope=SCOPE_SUBTREE,
387 attrs=[])
389 m = ldb.Message()
390 m.dn = res[0].dn
391 m["description"]= ldb.MessageElement(
392 ("A description"), ldb.FLAG_MOD_REPLACE,
393 "description")
394 self.samdb.modify(m)
396 # These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test
397 priv_to_auth_users_bits = set([UF_PASSWD_NOTREQD, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,
398 UF_DONT_EXPIRE_PASSWD])
400 # These bits really are privileged, or can't be changed from UF_NORMAL as a non-admin
401 priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT,
402 UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION,
403 UF_WORKSTATION_TRUST_ACCOUNT])
405 invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT])
407 for bit in bits:
408 m = ldb.Message()
409 m.dn = res[0].dn
410 m["userAccountControl"] = ldb.MessageElement(str(bit|UF_PASSWD_NOTREQD),
411 ldb.FLAG_MOD_REPLACE, "userAccountControl")
412 try:
413 self.samdb.modify(m)
414 if (bit in priv_bits):
415 self.fail("Unexpectedly able to set userAccountControl bit 0x%08X on %s" % (bit, m.dn))
416 except LdbError, (enum, estr):
417 if bit in invalid_bits:
418 self.assertEqual(enum, ldb.ERR_OTHER, "was not able to set 0x%08X on %s" % (bit, m.dn))
419 # No point going on, try the next bit
420 continue
421 elif (bit in priv_bits):
422 self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
423 else:
424 self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
427 def uac_bits_unrelated_modify_helper(self, account_type):
428 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
429 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
431 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
433 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
435 computername=self.computernames[0]
436 self.add_computer_ldap(computername, others={"userAccountControl": [str(account_type)]})
438 res = self.admin_samdb.search("%s" % self.base_dn,
439 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
440 scope=SCOPE_SUBTREE,
441 attrs=["userAccountControl"])
442 self.assertEqual(int(res[0]["userAccountControl"][0]), account_type)
444 m = ldb.Message()
445 m.dn = res[0].dn
446 m["description"]= ldb.MessageElement(
447 ("A description"), ldb.FLAG_MOD_REPLACE,
448 "description")
449 self.samdb.modify(m)
451 invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT])
453 # UF_LOCKOUT isn't actually ignored, it changes other
454 # attributes but does not stick here. See MS-SAMR 2.2.1.13
455 # UF_FLAG Codes clarification that UF_SCRIPT and
456 # UF_PASSWD_CANT_CHANGE are simply ignored by both clients and
457 # servers. Other bits are ignored as they are undefined, or
458 # are not set into the attribute (instead triggering other
459 # events).
460 ignored_bits = set([UF_SCRIPT, UF_00000004, UF_LOCKOUT, UF_PASSWD_CANT_CHANGE,
461 UF_00000400, UF_00004000, UF_00008000, UF_PASSWORD_EXPIRED,
462 int("0x10000000", 16), int("0x20000000", 16), int("0x40000000", 16), int("0x80000000", 16)])
463 super_priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT])
465 priv_to_remove_bits = set([UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, UF_WORKSTATION_TRUST_ACCOUNT])
467 for bit in bits:
468 # Reset this to the initial position, just to be sure
469 m = ldb.Message()
470 m.dn = res[0].dn
471 m["userAccountControl"] = ldb.MessageElement(str(account_type),
472 ldb.FLAG_MOD_REPLACE, "userAccountControl")
473 self.admin_samdb.modify(m)
475 res = self.admin_samdb.search("%s" % self.base_dn,
476 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
477 scope=SCOPE_SUBTREE,
478 attrs=["userAccountControl"])
480 self.assertEqual(int(res[0]["userAccountControl"][0]), account_type)
482 m = ldb.Message()
483 m.dn = res[0].dn
484 m["userAccountControl"] = ldb.MessageElement(str(bit|UF_PASSWD_NOTREQD),
485 ldb.FLAG_MOD_REPLACE, "userAccountControl")
486 try:
487 self.admin_samdb.modify(m)
488 if bit in invalid_bits:
489 self.fail("Should have been unable to set userAccountControl bit 0x%08X on %s" % (bit, m.dn))
491 except LdbError, (enum, estr):
492 if bit in invalid_bits:
493 self.assertEqual(enum, ldb.ERR_OTHER)
494 # No point going on, try the next bit
495 continue
496 elif bit in super_priv_bits:
497 self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)
498 # No point going on, try the next bit
499 continue
500 else:
501 self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
503 res = self.admin_samdb.search("%s" % self.base_dn,
504 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
505 scope=SCOPE_SUBTREE,
506 attrs=["userAccountControl"])
508 if bit in ignored_bits:
509 self.assertEqual(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD, "Bit 0x%08x shouldn't stick" % bit)
510 else:
511 if bit in account_types:
512 self.assertEqual(int(res[0]["userAccountControl"][0]), bit|UF_PASSWD_NOTREQD, "Bit 0x%08x didn't stick" % bit)
513 else:
514 self.assertEqual(int(res[0]["userAccountControl"][0]), bit|UF_NORMAL_ACCOUNT|UF_PASSWD_NOTREQD, "Bit 0x%08x didn't stick" % bit)
516 try:
517 m = ldb.Message()
518 m.dn = res[0].dn
519 m["userAccountControl"] = ldb.MessageElement(str(bit|UF_PASSWD_NOTREQD|UF_ACCOUNTDISABLE),
520 ldb.FLAG_MOD_REPLACE, "userAccountControl")
521 self.samdb.modify(m)
523 except LdbError, (enum, estr):
524 self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
526 res = self.admin_samdb.search("%s" % self.base_dn,
527 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
528 scope=SCOPE_SUBTREE,
529 attrs=["userAccountControl"])
531 if bit in account_types:
532 self.assertEqual(int(res[0]["userAccountControl"][0]),
533 bit|UF_ACCOUNTDISABLE|UF_PASSWD_NOTREQD,
534 "bit 0X%08x should have been added (0X%08x vs 0X%08x)"
535 % (bit, int(res[0]["userAccountControl"][0]),
536 bit|UF_ACCOUNTDISABLE|UF_PASSWD_NOTREQD))
537 elif bit in ignored_bits:
538 self.assertEqual(int(res[0]["userAccountControl"][0]),
539 UF_NORMAL_ACCOUNT|UF_ACCOUNTDISABLE|UF_PASSWD_NOTREQD,
540 "bit 0X%08x should have been added (0X%08x vs 0X%08x)"
541 % (bit, int(res[0]["userAccountControl"][0]),
542 UF_NORMAL_ACCOUNT|UF_ACCOUNTDISABLE|UF_PASSWD_NOTREQD))
544 else:
545 self.assertEqual(int(res[0]["userAccountControl"][0]),
546 bit|UF_NORMAL_ACCOUNT|UF_ACCOUNTDISABLE|UF_PASSWD_NOTREQD,
547 "bit 0X%08x should have been added (0X%08x vs 0X%08x)"
548 % (bit, int(res[0]["userAccountControl"][0]),
549 bit|UF_NORMAL_ACCOUNT|UF_ACCOUNTDISABLE|UF_PASSWD_NOTREQD))
551 try:
552 m = ldb.Message()
553 m.dn = res[0].dn
554 m["userAccountControl"] = ldb.MessageElement(str(UF_PASSWD_NOTREQD|UF_ACCOUNTDISABLE),
555 ldb.FLAG_MOD_REPLACE, "userAccountControl")
556 self.samdb.modify(m)
557 if bit in priv_to_remove_bits:
558 self.fail("Should have been unable to remove userAccountControl bit 0x%08X on %s" % (bit, m.dn))
560 except LdbError, (enum, estr):
561 if bit in priv_to_remove_bits:
562 self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)
563 else:
564 self.fail("Unexpectedly unable to remove userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
566 res = self.admin_samdb.search("%s" % self.base_dn,
567 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
568 scope=SCOPE_SUBTREE,
569 attrs=["userAccountControl"])
571 if bit in priv_to_remove_bits:
572 if bit in account_types:
573 self.assertEqual(int(res[0]["userAccountControl"][0]),
574 bit|UF_ACCOUNTDISABLE|UF_PASSWD_NOTREQD,
575 "bit 0X%08x should not have been removed" % bit)
576 else:
577 self.assertEqual(int(res[0]["userAccountControl"][0]),
578 bit|UF_NORMAL_ACCOUNT|UF_ACCOUNTDISABLE|UF_PASSWD_NOTREQD,
579 "bit 0X%08x should not have been removed" % bit)
580 else:
581 self.assertEqual(int(res[0]["userAccountControl"][0]),
582 UF_NORMAL_ACCOUNT|UF_ACCOUNTDISABLE|UF_PASSWD_NOTREQD,
583 "bit 0X%08x should have been removed" % bit)
585 def test_uac_bits_unrelated_modify_normal(self):
586 self.uac_bits_unrelated_modify_helper(UF_NORMAL_ACCOUNT)
588 def test_uac_bits_unrelated_modify_workstation(self):
589 self.uac_bits_unrelated_modify_helper(UF_WORKSTATION_TRUST_ACCOUNT)
591 def test_uac_bits_add(self):
592 computername=self.computernames[0]
594 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
595 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
597 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
599 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
601 invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT])
602 # These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test
603 priv_to_auth_users_bits = set([UF_PASSWD_NOTREQD, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,
604 UF_DONT_EXPIRE_PASSWD])
606 # These bits really are privileged
607 priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT,
608 UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION])
610 for bit in bits:
611 try:
612 self.add_computer_ldap(computername, others={"userAccountControl": [str(bit)]})
613 delete_force(self.admin_samdb, "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn))
614 if bit in priv_bits:
615 self.fail("Unexpectdly able to set userAccountControl bit 0x%08X on %s" % (bit, computername))
617 except LdbError, (enum, estr):
618 if bit in invalid_bits:
619 self.assertEqual(enum, ldb.ERR_OTHER, "Invalid bit 0x%08X was able to be set on %s" % (bit, computername))
620 # No point going on, try the next bit
621 continue
622 elif bit in priv_bits:
623 self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)
624 continue
625 else:
626 self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, computername, estr))
628 def test_primarygroupID_cc_add(self):
629 computername=self.computernames[0]
631 user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
632 mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
634 old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
636 self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
637 try:
638 # When creating a new object, you can not ever set the primaryGroupID
639 self.add_computer_ldap(computername, others={"primaryGroupID": [str(security.DOMAIN_RID_ADMINS)]})
640 self.fail("Unexpectedly able to set primaryGruopID to be an admin on %s" % computername)
641 except LdbError, (enum, estr):
642 self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM)
645 def test_primarygroupID_priv_DC_modify(self):
646 computername=self.computernames[0]
648 self.add_computer_ldap(computername,
649 others={"userAccountControl": [str(UF_SERVER_TRUST_ACCOUNT)]},
650 samdb=self.admin_samdb)
651 res = self.admin_samdb.search("%s" % self.base_dn,
652 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
653 scope=SCOPE_SUBTREE,
654 attrs=[""])
657 m = ldb.Message()
658 m.dn = ldb.Dn(self.admin_samdb, "<SID=%s-%d>" % (str(self.domain_sid),
659 security.DOMAIN_RID_USERS))
660 m["member"]= ldb.MessageElement(
661 [str(res[0].dn)], ldb.FLAG_MOD_ADD,
662 "member")
663 self.admin_samdb.modify(m)
665 m = ldb.Message()
666 m.dn = res[0].dn
667 m["primaryGroupID"]= ldb.MessageElement(
668 [str(security.DOMAIN_RID_USERS)], ldb.FLAG_MOD_REPLACE,
669 "primaryGroupID")
670 try:
671 self.admin_samdb.modify(m)
673 # When creating a new object, you can not ever set the primaryGroupID
674 self.fail("Unexpectedly able to set primaryGroupID to be other than DCS on %s" % computername)
675 except LdbError, (enum, estr):
676 self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM)
678 def test_primarygroupID_priv_member_modify(self):
679 computername=self.computernames[0]
681 self.add_computer_ldap(computername,
682 others={"userAccountControl": [str(UF_WORKSTATION_TRUST_ACCOUNT|UF_PARTIAL_SECRETS_ACCOUNT)]},
683 samdb=self.admin_samdb)
684 res = self.admin_samdb.search("%s" % self.base_dn,
685 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
686 scope=SCOPE_SUBTREE,
687 attrs=[""])
690 m = ldb.Message()
691 m.dn = ldb.Dn(self.admin_samdb, "<SID=%s-%d>" % (str(self.domain_sid),
692 security.DOMAIN_RID_USERS))
693 m["member"]= ldb.MessageElement(
694 [str(res[0].dn)], ldb.FLAG_MOD_ADD,
695 "member")
696 self.admin_samdb.modify(m)
698 m = ldb.Message()
699 m.dn = res[0].dn
700 m["primaryGroupID"]= ldb.MessageElement(
701 [str(security.DOMAIN_RID_USERS)], ldb.FLAG_MOD_REPLACE,
702 "primaryGroupID")
703 try:
704 self.admin_samdb.modify(m)
706 # When creating a new object, you can not ever set the primaryGroupID
707 self.fail("Unexpectedly able to set primaryGroupID to be other than DCS on %s" % computername)
708 except LdbError, (enum, estr):
709 self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM)
712 def test_primarygroupID_priv_user_modify(self):
713 computername=self.computernames[0]
715 self.add_computer_ldap(computername,
716 others={"userAccountControl": [str(UF_WORKSTATION_TRUST_ACCOUNT)]},
717 samdb=self.admin_samdb)
718 res = self.admin_samdb.search("%s" % self.base_dn,
719 expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
720 scope=SCOPE_SUBTREE,
721 attrs=[""])
724 m = ldb.Message()
725 m.dn = ldb.Dn(self.admin_samdb, "<SID=%s-%d>" % (str(self.domain_sid),
726 security.DOMAIN_RID_ADMINS))
727 m["member"]= ldb.MessageElement(
728 [str(res[0].dn)], ldb.FLAG_MOD_ADD,
729 "member")
730 self.admin_samdb.modify(m)
732 m = ldb.Message()
733 m.dn = res[0].dn
734 m["primaryGroupID"]= ldb.MessageElement(
735 [str(security.DOMAIN_RID_ADMINS)], ldb.FLAG_MOD_REPLACE,
736 "primaryGroupID")
737 self.admin_samdb.modify(m)
740 runner = SubunitTestRunner()
741 rc = 0
742 if not runner.run(unittest.makeSuite(UserAccountControlTests)).wasSuccessful():
743 rc = 1
744 sys.exit(rc)