e9f9b96538e90da1ac27b9efdb1f6fb8fd464cf0
[garming/samba-autobuild/.git] / source4 / dsdb / tests / python / user_account_control.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 # This tests the restrictions on userAccountControl that apply even if write access is permitted
4 #
5 # Copyright Samuel Cabrero 2014 <samuelcabrero@kernevil.me>
6 # Copyright Andrew Bartlett 2014 <abartlet@samba.org>
7 #
8 # Licenced under the GPLv3
9 #
10
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
20
21 sys.path.insert(0, "bin/python")
22 from samba.tests.subunitrun import TestProgram, SubunitOptions
23
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
44
45
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))
50
51 # use command line creds if available
52 credopts = options.CredentialsOptions(parser)
53 parser.add_option_group(credopts)
54 opts, args = parser.parse_args()
55
56 if len(args) < 1:
57     parser.print_usage()
58     sys.exit(1)
59 host = args[0]
60
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)
67
68 lp = sambaopts.get_loadparm()
69 creds = credopts.get_credentials(lp)
70 creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
71
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)]
85
86 account_types = set([UF_NORMAL_ACCOUNT, UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT])
87
88
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(msg_dict.items() + others.items())
102
103         msg = ldb.Message.from_dict(self.samdb, msg_dict)
104         msg["sAMAccountName"] = samaccountname
105
106         print("Adding computer account %s" % computername)
107         samdb.add(msg)
108
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
120
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()
129
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)
133
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))
137
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))
143
144         self.unpriv_user_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
145         self.unpriv_user_dn = res[0].dn
146
147         self.samdb = SamDB(url=ldaphost, credentials=self.unpriv_creds, lp=lp)
148
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)
152
153         self.sd_utils = sd_utils.SDUtils(self.admin_samdb)
154
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)
158
159         old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
160
161         self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
162
163         self.add_computer_ldap("testcomputer-t")
164
165         self.sd_utils.modify_sd_on_dn("OU=test_computer_ou1," + self.base_dn, old_sd)
166
167         self.computernames = ["testcomputer-0"]
168
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))
173
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
178
179         # Now reconnect without domain admin rights
180         self.samdb = SamDB(url=ldaphost, credentials=self.unpriv_creds, lp=lp)
181
182
183     def tearDown(self):
184         super(UserAccountControlTests, self).tearDown()
185         for computername in self.computernames:
186             delete_force(self.admin_samdb, "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn))
187         delete_force(self.admin_samdb, "CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn))
188         delete_force(self.admin_samdb, "OU=test_computer_ou1,%s" % (self.base_dn))
189         delete_force(self.admin_samdb, "CN=%s,CN=Users,%s" % (self.unpriv_user, self.base_dn))
190
191     def test_add_computer_sd_cc(self):
192         user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
193         mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
194
195         old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
196
197         self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
198
199         computername = self.computernames[0]
200         sd = ldb.MessageElement((ndr_pack(self.sd_reference_modify)),
201                                 ldb.FLAG_MOD_ADD,
202                                 "nTSecurityDescriptor")
203         self.add_computer_ldap(computername,
204                                others={"nTSecurityDescriptor": sd})
205
206         res = self.admin_samdb.search("%s" % self.base_dn,
207                                       expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
208                                       scope=SCOPE_SUBTREE,
209                                       attrs=["ntSecurityDescriptor"])
210
211         desc = res[0]["nTSecurityDescriptor"][0]
212         desc = ndr_unpack(security.descriptor, desc, allow_remaining=True)
213
214         sddl = desc.as_sddl(self.domain_sid)
215         self.assertEqual(self.sd_reference_modify.as_sddl(self.domain_sid), sddl)
216
217         m = ldb.Message()
218         m.dn = res[0].dn
219         m["description"] = ldb.MessageElement(
220             ("A description"), ldb.FLAG_MOD_REPLACE,
221             "description")
222         self.samdb.modify(m)
223
224         m = ldb.Message()
225         m.dn = res[0].dn
226         m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_SERVER_TRUST_ACCOUNT),
227                                                      ldb.FLAG_MOD_REPLACE, "userAccountControl")
228         try:
229             self.samdb.modify(m)
230             self.fail("Unexpectedly able to set userAccountControl to be a DC on %s" % m.dn)
231         except LdbError as e5:
232             (enum, estr) = e5.args
233             self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
234
235         m = ldb.Message()
236         m.dn = res[0].dn
237         m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |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)
245
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)
256
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)
262
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()
274
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)
278
279         old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
280
281         self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
282
283         computername = self.computernames[0]
284         self.add_computer_ldap(computername)
285
286         res = self.admin_samdb.search("%s" % self.base_dn,
287                                       expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
288                                       scope=SCOPE_SUBTREE,
289                                       attrs=[])
290
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)
297
298         m = ldb.Message()
299         m.dn = res[0].dn
300         m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT |samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT),
301                                                      ldb.FLAG_MOD_REPLACE, "userAccountControl")
302         try:
303             self.samdb.modify(m)
304             self.fail("Unexpectedly able to set userAccountControl on %s" % m.dn)
305         except LdbError as e9:
306             (enum, estr) = e9.args
307             self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
308
309         m = ldb.Message()
310         m.dn = res[0].dn
311         m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_SERVER_TRUST_ACCOUNT),
312                                                      ldb.FLAG_MOD_REPLACE, "userAccountControl")
313         try:
314             self.samdb.modify(m)
315             self.fail()
316         except LdbError as e10:
317             (enum, estr) = e10.args
318             self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
319
320         m = ldb.Message()
321         m.dn = res[0].dn
322         m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT),
323                                                      ldb.FLAG_MOD_REPLACE, "userAccountControl")
324         self.samdb.modify(m)
325
326         m = ldb.Message()
327         m.dn = res[0].dn
328         m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT),
329                                                      ldb.FLAG_MOD_REPLACE, "userAccountControl")
330         try:
331             self.samdb.modify(m)
332             self.fail("Unexpectedly able to set userAccountControl to be an Workstation on %s" % m.dn)
333         except LdbError as e11:
334             (enum, estr) = e11.args
335             self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
336
337
338     def test_admin_mod_uac(self):
339         computername = self.computernames[0]
340         self.add_computer_ldap(computername, samdb=self.admin_samdb)
341
342         res = self.admin_samdb.search("%s" % self.base_dn,
343                                       expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
344                                       scope=SCOPE_SUBTREE,
345                                       attrs=["userAccountControl"])
346
347         self.assertEqual(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT |UF_ACCOUNTDISABLE |UF_PASSWD_NOTREQD)
348
349         m = ldb.Message()
350         m.dn = res[0].dn
351         m["userAccountControl"] = ldb.MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT |UF_PARTIAL_SECRETS_ACCOUNT |UF_TRUSTED_FOR_DELEGATION),
352                                                      ldb.FLAG_MOD_REPLACE, "userAccountControl")
353         try:
354             self.admin_samdb.modify(m)
355             self.fail("Unexpectedly able to set userAccountControl to UF_WORKSTATION_TRUST_ACCOUNT|UF_PARTIAL_SECRETS_ACCOUNT|UF_TRUSTED_FOR_DELEGATION on %s" % m.dn)
356         except LdbError as e12:
357             (enum, estr) = e12.args
358             self.assertEqual(ldb.ERR_OTHER, enum)
359
360         m = ldb.Message()
361         m.dn = res[0].dn
362         m["userAccountControl"] = ldb.MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT |UF_PARTIAL_SECRETS_ACCOUNT),
363                                                      ldb.FLAG_MOD_REPLACE, "userAccountControl")
364         self.admin_samdb.modify(m)
365
366         res = self.admin_samdb.search("%s" % self.base_dn,
367                                       expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
368                                       scope=SCOPE_SUBTREE,
369                                       attrs=["userAccountControl"])
370
371         self.assertEqual(int(res[0]["userAccountControl"][0]), UF_WORKSTATION_TRUST_ACCOUNT |UF_PARTIAL_SECRETS_ACCOUNT)
372         m = ldb.Message()
373         m.dn = res[0].dn
374         m["userAccountControl"] = ldb.MessageElement(str(UF_ACCOUNTDISABLE),
375                                                      ldb.FLAG_MOD_REPLACE, "userAccountControl")
376         self.admin_samdb.modify(m)
377
378         res = self.admin_samdb.search("%s" % self.base_dn,
379                                       expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
380                                       scope=SCOPE_SUBTREE,
381                                       attrs=["userAccountControl"])
382
383         self.assertEqual(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE)
384
385
386     def test_uac_bits_set(self):
387         user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
388         mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
389
390         old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
391
392         self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
393
394         computername = self.computernames[0]
395         self.add_computer_ldap(computername)
396
397         res = self.admin_samdb.search("%s" % self.base_dn,
398                                       expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
399                                       scope=SCOPE_SUBTREE,
400                                       attrs=[])
401
402         m = ldb.Message()
403         m.dn = res[0].dn
404         m["description"] = ldb.MessageElement(
405             ("A description"), ldb.FLAG_MOD_REPLACE,
406             "description")
407         self.samdb.modify(m)
408
409         # These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test
410         priv_to_auth_users_bits = set([UF_PASSWD_NOTREQD, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,
411                                        UF_DONT_EXPIRE_PASSWD])
412
413         # These bits really are privileged, or can't be changed from UF_NORMAL as a non-admin
414         priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT,
415                          UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION,
416                          UF_WORKSTATION_TRUST_ACCOUNT])
417
418         invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT])
419
420         for bit in bits:
421             m = ldb.Message()
422             m.dn = res[0].dn
423             m["userAccountControl"] = ldb.MessageElement(str(bit |UF_PASSWD_NOTREQD),
424                                                          ldb.FLAG_MOD_REPLACE, "userAccountControl")
425             try:
426                 self.samdb.modify(m)
427                 if (bit in priv_bits):
428                     self.fail("Unexpectedly able to set userAccountControl bit 0x%08X on %s" % (bit, m.dn))
429             except LdbError as e:
430                 (enum, estr) = e.args
431                 if bit in invalid_bits:
432                     self.assertEqual(enum, ldb.ERR_OTHER, "was not able to set 0x%08X on %s" % (bit, m.dn))
433                     # No point going on, try the next bit
434                     continue
435                 elif (bit in priv_bits):
436                     self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
437                 else:
438                     self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
439
440
441     def uac_bits_unrelated_modify_helper(self, account_type):
442         user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
443         mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
444
445         old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
446
447         self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
448
449         computername = self.computernames[0]
450         self.add_computer_ldap(computername, others={"userAccountControl": [str(account_type)]})
451
452         res = self.admin_samdb.search("%s" % self.base_dn,
453                                       expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
454                                       scope=SCOPE_SUBTREE,
455                                       attrs=["userAccountControl"])
456         self.assertEqual(int(res[0]["userAccountControl"][0]), account_type)
457
458         m = ldb.Message()
459         m.dn = res[0].dn
460         m["description"] = ldb.MessageElement(
461             ("A description"), ldb.FLAG_MOD_REPLACE,
462             "description")
463         self.samdb.modify(m)
464
465         invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT])
466
467         # UF_LOCKOUT isn't actually ignored, it changes other
468         # attributes but does not stick here.  See MS-SAMR 2.2.1.13
469         # UF_FLAG Codes clarification that UF_SCRIPT and
470         # UF_PASSWD_CANT_CHANGE are simply ignored by both clients and
471         # servers.  Other bits are ignored as they are undefined, or
472         # are not set into the attribute (instead triggering other
473         # events).
474         ignored_bits = set([UF_SCRIPT, UF_00000004, UF_LOCKOUT, UF_PASSWD_CANT_CHANGE,
475                             UF_00000400, UF_00004000, UF_00008000, UF_PASSWORD_EXPIRED,
476                             int("0x10000000", 16), int("0x20000000", 16), int("0x40000000", 16), int("0x80000000", 16)])
477         super_priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT])
478
479         priv_to_remove_bits = set([UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, UF_WORKSTATION_TRUST_ACCOUNT])
480
481         for bit in bits:
482             # Reset this to the initial position, just to be sure
483             m = ldb.Message()
484             m.dn = res[0].dn
485             m["userAccountControl"] = ldb.MessageElement(str(account_type),
486                                                          ldb.FLAG_MOD_REPLACE, "userAccountControl")
487             self.admin_samdb.modify(m)
488
489             res = self.admin_samdb.search("%s" % self.base_dn,
490                                           expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
491                                           scope=SCOPE_SUBTREE,
492                                           attrs=["userAccountControl"])
493
494             self.assertEqual(int(res[0]["userAccountControl"][0]), account_type)
495
496             m = ldb.Message()
497             m.dn = res[0].dn
498             m["userAccountControl"] = ldb.MessageElement(str(bit |UF_PASSWD_NOTREQD),
499                                                          ldb.FLAG_MOD_REPLACE, "userAccountControl")
500             try:
501                 self.admin_samdb.modify(m)
502                 if bit in invalid_bits:
503                     self.fail("Should have been unable to set userAccountControl bit 0x%08X on %s" % (bit, m.dn))
504
505             except LdbError as e1:
506                 (enum, estr) = e1.args
507                 if bit in invalid_bits:
508                     self.assertEqual(enum, ldb.ERR_OTHER)
509                     # No point going on, try the next bit
510                     continue
511                 elif bit in super_priv_bits:
512                     self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)
513                     # No point going on, try the next bit
514                     continue
515                 else:
516                     self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
517
518             res = self.admin_samdb.search("%s" % self.base_dn,
519                                           expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
520                                           scope=SCOPE_SUBTREE,
521                                           attrs=["userAccountControl"])
522
523             if bit in ignored_bits:
524                 self.assertEqual(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT |UF_PASSWD_NOTREQD, "Bit 0x%08x shouldn't stick" % bit)
525             else:
526                 if bit in account_types:
527                     self.assertEqual(int(res[0]["userAccountControl"][0]), bit |UF_PASSWD_NOTREQD, "Bit 0x%08x didn't stick" % bit)
528                 else:
529                     self.assertEqual(int(res[0]["userAccountControl"][0]), bit |UF_NORMAL_ACCOUNT |UF_PASSWD_NOTREQD, "Bit 0x%08x didn't stick" % bit)
530
531             try:
532                 m = ldb.Message()
533                 m.dn = res[0].dn
534                 m["userAccountControl"] = ldb.MessageElement(str(bit |UF_PASSWD_NOTREQD |UF_ACCOUNTDISABLE),
535                                                              ldb.FLAG_MOD_REPLACE, "userAccountControl")
536                 self.samdb.modify(m)
537
538             except LdbError as e2:
539                 (enum, estr) = e2.args
540                 self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
541
542             res = self.admin_samdb.search("%s" % self.base_dn,
543                                           expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
544                                           scope=SCOPE_SUBTREE,
545                                           attrs=["userAccountControl"])
546
547             if bit in account_types:
548                 self.assertEqual(int(res[0]["userAccountControl"][0]),
549                                  bit |UF_ACCOUNTDISABLE |UF_PASSWD_NOTREQD,
550                                  "bit 0X%08x should have been added (0X%08x vs 0X%08x)"
551                                  % (bit, int(res[0]["userAccountControl"][0]),
552                                     bit |UF_ACCOUNTDISABLE |UF_PASSWD_NOTREQD))
553             elif bit in ignored_bits:
554                 self.assertEqual(int(res[0]["userAccountControl"][0]),
555                                  UF_NORMAL_ACCOUNT |UF_ACCOUNTDISABLE |UF_PASSWD_NOTREQD,
556                                  "bit 0X%08x should have been added (0X%08x vs 0X%08x)"
557                                  % (bit, int(res[0]["userAccountControl"][0]),
558                                     UF_NORMAL_ACCOUNT |UF_ACCOUNTDISABLE |UF_PASSWD_NOTREQD))
559
560             else:
561                 self.assertEqual(int(res[0]["userAccountControl"][0]),
562                                  bit |UF_NORMAL_ACCOUNT |UF_ACCOUNTDISABLE |UF_PASSWD_NOTREQD,
563                                  "bit 0X%08x should have been added (0X%08x vs 0X%08x)"
564                                  % (bit, int(res[0]["userAccountControl"][0]),
565                                     bit |UF_NORMAL_ACCOUNT |UF_ACCOUNTDISABLE |UF_PASSWD_NOTREQD))
566
567             try:
568                 m = ldb.Message()
569                 m.dn = res[0].dn
570                 m["userAccountControl"] = ldb.MessageElement(str(UF_PASSWD_NOTREQD |UF_ACCOUNTDISABLE),
571                                                              ldb.FLAG_MOD_REPLACE, "userAccountControl")
572                 self.samdb.modify(m)
573                 if bit in priv_to_remove_bits:
574                     self.fail("Should have been unable to remove userAccountControl bit 0x%08X on %s" % (bit, m.dn))
575
576             except LdbError as e3:
577                 (enum, estr) = e3.args
578                 if bit in priv_to_remove_bits:
579                     self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)
580                 else:
581                     self.fail("Unexpectedly unable to remove userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
582
583             res = self.admin_samdb.search("%s" % self.base_dn,
584                                           expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
585                                           scope=SCOPE_SUBTREE,
586                                           attrs=["userAccountControl"])
587
588             if bit in priv_to_remove_bits:
589                 if bit in account_types:
590                     self.assertEqual(int(res[0]["userAccountControl"][0]),
591                                      bit |UF_ACCOUNTDISABLE |UF_PASSWD_NOTREQD,
592                                      "bit 0X%08x should not have been removed" % bit)
593                 else:
594                     self.assertEqual(int(res[0]["userAccountControl"][0]),
595                                      bit |UF_NORMAL_ACCOUNT |UF_ACCOUNTDISABLE |UF_PASSWD_NOTREQD,
596                                      "bit 0X%08x should not have been removed" % bit)
597             else:
598                 self.assertEqual(int(res[0]["userAccountControl"][0]),
599                                  UF_NORMAL_ACCOUNT |UF_ACCOUNTDISABLE |UF_PASSWD_NOTREQD,
600                                  "bit 0X%08x should have been removed" % bit)
601
602     def test_uac_bits_unrelated_modify_normal(self):
603         self.uac_bits_unrelated_modify_helper(UF_NORMAL_ACCOUNT)
604
605     def test_uac_bits_unrelated_modify_workstation(self):
606         self.uac_bits_unrelated_modify_helper(UF_WORKSTATION_TRUST_ACCOUNT)
607
608     def test_uac_bits_add(self):
609         computername = self.computernames[0]
610
611         user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
612         mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
613
614         old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
615
616         self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
617
618         invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT, UF_PARTIAL_SECRETS_ACCOUNT])
619         # These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test
620         priv_to_auth_users_bits = set([UF_PASSWD_NOTREQD, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,
621                                        UF_DONT_EXPIRE_PASSWD])
622
623         # These bits really are privileged
624         priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT,
625                          UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION])
626
627         for bit in bits:
628             try:
629                 self.add_computer_ldap(computername, others={"userAccountControl": [str(bit)]})
630                 delete_force(self.admin_samdb, "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn))
631                 if bit in priv_bits:
632                     self.fail("Unexpectdly able to set userAccountControl bit 0x%08X on %s" % (bit, computername))
633
634             except LdbError as e4:
635                 (enum, estr) = e4.args
636                 if bit in invalid_bits:
637                     self.assertEqual(enum, ldb.ERR_OTHER, "Invalid bit 0x%08X was able to be set on %s" % (bit, computername))
638                     # No point going on, try the next bit
639                     continue
640                 elif bit in priv_bits:
641                     self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)
642                     continue
643                 else:
644                     self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, computername, estr))
645
646     def test_primarygroupID_cc_add(self):
647         computername = self.computernames[0]
648
649         user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
650         mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
651
652         old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
653
654         self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
655         try:
656             # When creating a new object, you can not ever set the primaryGroupID
657             self.add_computer_ldap(computername, others={"primaryGroupID": [str(security.DOMAIN_RID_ADMINS)]})
658             self.fail("Unexpectedly able to set primaryGruopID to be an admin on %s" % computername)
659         except LdbError as e13:
660             (enum, estr) = e13.args
661             self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM)
662
663
664     def test_primarygroupID_priv_DC_modify(self):
665         computername = self.computernames[0]
666
667         self.add_computer_ldap(computername,
668                                others={"userAccountControl": [str(UF_SERVER_TRUST_ACCOUNT)]},
669                                samdb=self.admin_samdb)
670         res = self.admin_samdb.search("%s" % self.base_dn,
671                                       expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
672                                       scope=SCOPE_SUBTREE,
673                                       attrs=[""])
674
675
676         m = ldb.Message()
677         m.dn = ldb.Dn(self.admin_samdb, "<SID=%s-%d>" % (str(self.domain_sid),
678                                                          security.DOMAIN_RID_USERS))
679         m["member"] = ldb.MessageElement(
680             [str(res[0].dn)], ldb.FLAG_MOD_ADD,
681             "member")
682         self.admin_samdb.modify(m)
683
684         m = ldb.Message()
685         m.dn = res[0].dn
686         m["primaryGroupID"] = ldb.MessageElement(
687             [str(security.DOMAIN_RID_USERS)], ldb.FLAG_MOD_REPLACE,
688             "primaryGroupID")
689         try:
690             self.admin_samdb.modify(m)
691
692             # When creating a new object, you can not ever set the primaryGroupID
693             self.fail("Unexpectedly able to set primaryGroupID to be other than DCS on %s" % computername)
694         except LdbError as e14:
695             (enum, estr) = e14.args
696             self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM)
697
698     def test_primarygroupID_priv_member_modify(self):
699         computername = self.computernames[0]
700
701         self.add_computer_ldap(computername,
702                                others={"userAccountControl": [str(UF_WORKSTATION_TRUST_ACCOUNT |UF_PARTIAL_SECRETS_ACCOUNT)]},
703                                samdb=self.admin_samdb)
704         res = self.admin_samdb.search("%s" % self.base_dn,
705                                       expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
706                                       scope=SCOPE_SUBTREE,
707                                       attrs=[""])
708
709
710         m = ldb.Message()
711         m.dn = ldb.Dn(self.admin_samdb, "<SID=%s-%d>" % (str(self.domain_sid),
712                                                          security.DOMAIN_RID_USERS))
713         m["member"] = ldb.MessageElement(
714             [str(res[0].dn)], ldb.FLAG_MOD_ADD,
715             "member")
716         self.admin_samdb.modify(m)
717
718         m = ldb.Message()
719         m.dn = res[0].dn
720         m["primaryGroupID"] = ldb.MessageElement(
721             [str(security.DOMAIN_RID_USERS)], ldb.FLAG_MOD_REPLACE,
722             "primaryGroupID")
723         try:
724             self.admin_samdb.modify(m)
725
726             # When creating a new object, you can not ever set the primaryGroupID
727             self.fail("Unexpectedly able to set primaryGroupID to be other than DCS on %s" % computername)
728         except LdbError as e15:
729             (enum, estr) = e15.args
730             self.assertEqual(enum, ldb.ERR_UNWILLING_TO_PERFORM)
731
732
733     def test_primarygroupID_priv_user_modify(self):
734         computername = self.computernames[0]
735
736         self.add_computer_ldap(computername,
737                                others={"userAccountControl": [str(UF_WORKSTATION_TRUST_ACCOUNT)]},
738                                samdb=self.admin_samdb)
739         res = self.admin_samdb.search("%s" % self.base_dn,
740                                       expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
741                                       scope=SCOPE_SUBTREE,
742                                       attrs=[""])
743
744
745         m = ldb.Message()
746         m.dn = ldb.Dn(self.admin_samdb, "<SID=%s-%d>" % (str(self.domain_sid),
747                                                          security.DOMAIN_RID_ADMINS))
748         m["member"] = ldb.MessageElement(
749             [str(res[0].dn)], ldb.FLAG_MOD_ADD,
750             "member")
751         self.admin_samdb.modify(m)
752
753         m = ldb.Message()
754         m.dn = res[0].dn
755         m["primaryGroupID"] = ldb.MessageElement(
756             [str(security.DOMAIN_RID_ADMINS)], ldb.FLAG_MOD_REPLACE,
757             "primaryGroupID")
758         self.admin_samdb.modify(m)
759
760
761 runner = SubunitTestRunner()
762 rc = 0
763 if not runner.run(unittest.makeSuite(UserAccountControlTests)).wasSuccessful():
764     rc = 1
765 sys.exit(rc)