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
16 import samba
.getopt
as options
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()
62 ldaphost
= "ldap://%s" % 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):
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
)
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
)
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
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()),
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
)
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
)),
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
,
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
)
218 m
["description"] = ldb
.MessageElement(
219 ("A description"), ldb
.FLAG_MOD_REPLACE
,
225 m
["userAccountControl"] = ldb
.MessageElement(str(samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT
),
226 ldb
.FLAG_MOD_REPLACE
, "userAccountControl")
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
)
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")
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
)
248 m
["userAccountControl"] = ldb
.MessageElement(str(samba
.dsdb
.UF_WORKSTATION_TRUST_ACCOUNT
),
249 ldb
.FLAG_MOD_REPLACE
, "userAccountControl")
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
)
259 m
["userAccountControl"] = ldb
.MessageElement(str(samba
.dsdb
.UF_NORMAL_ACCOUNT
),
260 ldb
.FLAG_MOD_REPLACE
, "userAccountControl")
265 m
["primaryGroupID"] = ldb
.MessageElement(str(security
.DOMAIN_RID_ADMINS
),
266 ldb
.FLAG_MOD_REPLACE
, "primaryGroupID")
269 except LdbError
as e8
:
270 (enum
, estr
) = e8
.args
271 self
.assertEqual(ldb
.ERR_UNWILLING_TO_PERFORM
, enum
)
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
,
293 m
["description"] = ldb
.MessageElement(
294 ("A description"), ldb
.FLAG_MOD_REPLACE
,
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")
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
)
312 m
["userAccountControl"] = ldb
.MessageElement(str(samba
.dsdb
.UF_SERVER_TRUST_ACCOUNT
),
313 ldb
.FLAG_MOD_REPLACE
, "userAccountControl")
317 except LdbError
as e10
:
318 (enum
, estr
) = e10
.args
319 self
.assertEqual(ldb
.ERR_INSUFFICIENT_ACCESS_RIGHTS
, enum
)
323 m
["userAccountControl"] = ldb
.MessageElement(str(samba
.dsdb
.UF_NORMAL_ACCOUNT
),
324 ldb
.FLAG_MOD_REPLACE
, "userAccountControl")
329 m
["userAccountControl"] = ldb
.MessageElement(str(samba
.dsdb
.UF_WORKSTATION_TRUST_ACCOUNT
),
330 ldb
.FLAG_MOD_REPLACE
, "userAccountControl")
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
,
345 attrs
=["userAccountControl"])
347 self
.assertEqual(int(res
[0]["userAccountControl"][0]), (UF_NORMAL_ACCOUNT |
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")
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
)
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
,
374 attrs
=["userAccountControl"])
376 self
.assertEqual(int(res
[0]["userAccountControl"][0]), (UF_WORKSTATION_TRUST_ACCOUNT |
377 UF_PARTIAL_SECRETS_ACCOUNT
))
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
,
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
,
409 m
["description"] = ldb
.MessageElement(
410 ("A description"), ldb
.FLAG_MOD_REPLACE
,
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
])
428 m
["userAccountControl"] = ldb
.MessageElement(str(bit | UF_PASSWD_NOTREQD
),
429 ldb
.FLAG_MOD_REPLACE
, "userAccountControl")
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
440 elif (bit
in priv_bits
):
441 self
.assertEqual(ldb
.ERR_INSUFFICIENT_ACCESS_RIGHTS
, enum
)
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
,
459 attrs
=["userAccountControl"])
460 self
.assertEqual(int(res
[0]["userAccountControl"][0]), account_type
)
464 m
["description"] = ldb
.MessageElement(
465 ("A description"), ldb
.FLAG_MOD_REPLACE
,
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
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
])
486 # Reset this to the initial position, just to be sure
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
,
496 attrs
=["userAccountControl"])
498 self
.assertEqual(int(res
[0]["userAccountControl"][0]), account_type
)
502 m
["userAccountControl"] = ldb
.MessageElement(str(bit | UF_PASSWD_NOTREQD
),
503 ldb
.FLAG_MOD_REPLACE
, "userAccountControl")
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
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
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
,
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
)
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
)
537 self
.assertEqual(int(res
[0]["userAccountControl"][0]),
538 bit | UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD
,
539 "Bit 0x%08x didn't stick" % bit
)
544 m
["userAccountControl"] = ldb
.MessageElement(str(bit | UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE
),
545 ldb
.FLAG_MOD_REPLACE
, "userAccountControl")
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
,
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
))
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
))
580 m
["userAccountControl"] = ldb
.MessageElement(str(UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE
),
581 ldb
.FLAG_MOD_REPLACE
, "userAccountControl")
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
)
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
,
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
)
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
)
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
])
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
))
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
650 elif bit
in priv_bits
:
651 self
.assertEqual(enum
, ldb
.ERR_INSUFFICIENT_ACCESS_RIGHTS
)
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
)
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
,
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
,
690 self
.admin_samdb
.modify(m
)
694 m
["primaryGroupID"] = ldb
.MessageElement(
695 [str(security
.DOMAIN_RID_USERS
)], ldb
.FLAG_MOD_REPLACE
,
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
,
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
,
723 self
.admin_samdb
.modify(m
)
727 m
["primaryGroupID"] = ldb
.MessageElement(
728 [str(security
.DOMAIN_RID_USERS
)], ldb
.FLAG_MOD_REPLACE
,
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
,
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
,
756 self
.admin_samdb
.modify(m
)
760 m
["primaryGroupID"] = ldb
.MessageElement(
761 [str(security
.DOMAIN_RID_ADMINS
)], ldb
.FLAG_MOD_REPLACE
,
763 self
.admin_samdb
.modify(m
)
766 runner
= SubunitTestRunner()
768 if not runner
.run(unittest
.makeSuite(UserAccountControlTests
)).wasSuccessful():