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