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