dsdb-tests: Add new test samba4.user_account_control.python
authorAndrew Bartlett <abartlet@samba.org>
Mon, 8 Dec 2014 02:07:59 +0000 (15:07 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 22 Jan 2015 06:50:06 +0000 (07:50 +0100)
This confirms security behaviour of the userAccountControl attribute
as well as the behaviour on ADD as well as MODIFY, for every
userAccountControl bit.

Bug: https://bugzilla.samba.org/show_bug.cgi?id=10993

Change-Id: I8cd0e0b3c8d40e8b8aea844189703c756cc372f0
Pair-programmed-with: Garming Sam <garming@catalyst.net.nz>
Signed-off-by: Andrew Bartlett <abartlet@samba.org>
Signed-off-by: Garming Sam <garming@catalyst.net.nz>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
source4/dsdb/tests/python/user_account_control.py [new file with mode: 0644]
source4/selftest/tests.py

diff --git a/source4/dsdb/tests/python/user_account_control.py b/source4/dsdb/tests/python/user_account_control.py
new file mode 100644 (file)
index 0000000..00501bb
--- /dev/null
@@ -0,0 +1,521 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# This tests the restrictions on userAccountControl that apply even if write access is permitted
+#
+# Copyright Samuel Cabrero 2014 <samuelcabrero@kernevil.me>
+# Copyright Andrew Bartlett 2014 <abartlet@samba.org>
+#
+# Licenced under the GPLv3
+#
+
+import optparse
+import sys
+import unittest
+import samba
+import samba.getopt as options
+import samba.tests
+import ldb
+import base64
+
+sys.path.insert(0, "bin/python")
+from samba.tests.subunitrun import TestProgram, SubunitOptions
+
+from subunit.run import SubunitTestRunner
+from samba.auth import system_session
+from samba.samdb import SamDB
+from samba.dcerpc import samr, security, lsa
+from samba.credentials import Credentials
+from samba.ndr import ndr_unpack, ndr_pack
+from samba.tests import delete_force
+from samba import gensec, sd_utils
+from samba.credentials import DONT_USE_KERBEROS
+from ldb import SCOPE_SUBTREE, SCOPE_BASE, LdbError
+from ldb import Message, MessageElement, Dn
+from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE
+from samba.dsdb import UF_SCRIPT, UF_ACCOUNTDISABLE, UF_00000004, UF_HOMEDIR_REQUIRED, \
+    UF_LOCKOUT,UF_PASSWD_NOTREQD, UF_PASSWD_CANT_CHANGE, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,\
+    UF_TEMP_DUPLICATE_ACCOUNT, UF_NORMAL_ACCOUNT, UF_00000400, UF_INTERDOMAIN_TRUST_ACCOUNT, \
+    UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, UF_00004000, \
+    UF_00008000, UF_DONT_EXPIRE_PASSWD, UF_MNS_LOGON_ACCOUNT, UF_SMARTCARD_REQUIRED, \
+    UF_TRUSTED_FOR_DELEGATION, UF_NOT_DELEGATED, UF_USE_DES_KEY_ONLY, UF_DONT_REQUIRE_PREAUTH, \
+    UF_PASSWORD_EXPIRED, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, UF_NO_AUTH_DATA_REQUIRED, \
+    UF_PARTIAL_SECRETS_ACCOUNT, UF_USE_AES_KEYS
+
+
+parser = optparse.OptionParser("machine_account_privilege.py [options] <host>")
+sambaopts = options.SambaOptions(parser)
+parser.add_option_group(sambaopts)
+parser.add_option_group(options.VersionOptions(parser))
+
+# use command line creds if available
+credopts = options.CredentialsOptions(parser)
+parser.add_option_group(credopts)
+opts, args = parser.parse_args()
+
+if len(args) < 1:
+    parser.print_usage()
+    sys.exit(1)
+host = args[0]
+
+if not "://" in host:
+    ldaphost = "ldap://%s" % host
+else:
+    ldaphost = host
+    start = host.rindex("://")
+    host = host.lstrip(start+3)
+
+lp = sambaopts.get_loadparm()
+creds = credopts.get_credentials(lp)
+creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
+
+bits = [UF_SCRIPT, UF_ACCOUNTDISABLE, UF_00000004, UF_HOMEDIR_REQUIRED,
+        UF_LOCKOUT,UF_PASSWD_NOTREQD, UF_PASSWD_CANT_CHANGE,
+        UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,
+        UF_TEMP_DUPLICATE_ACCOUNT, UF_NORMAL_ACCOUNT, UF_00000400,
+        UF_INTERDOMAIN_TRUST_ACCOUNT,
+        UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT, UF_00004000,
+        UF_00008000, UF_DONT_EXPIRE_PASSWD, UF_MNS_LOGON_ACCOUNT, UF_SMARTCARD_REQUIRED,
+        UF_TRUSTED_FOR_DELEGATION, UF_NOT_DELEGATED, UF_USE_DES_KEY_ONLY,
+        UF_DONT_REQUIRE_PREAUTH,
+        UF_PASSWORD_EXPIRED, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION,
+        UF_NO_AUTH_DATA_REQUIRED,
+        UF_PARTIAL_SECRETS_ACCOUNT, UF_USE_AES_KEYS,
+        int("0x10000000", 16), int("0x20000000", 16), int("0x40000000", 16), int("0x80000000", 16)]
+
+
+class UserAccountControlTests(samba.tests.TestCase):
+    def add_computer_ldap(self, computername, others=None, samdb=None):
+        if samdb is None:
+            samdb = self.samdb
+        dn = "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn)
+        domainname = ldb.Dn(self.samdb, self.samdb.domain_dn()).canonical_str().replace("/", "")
+        samaccountname = "%s$" % computername
+        dnshostname = "%s.%s" % (computername, domainname)
+        msg_dict = {
+            "dn": dn,
+            "objectclass": "computer"}
+        if others is not None:
+            msg_dict = dict(msg_dict.items() + others.items())
+
+        msg = ldb.Message.from_dict(self.samdb, msg_dict )
+        msg["sAMAccountName"] = samaccountname
+
+        print "Adding computer account %s" % computername
+        samdb.add(msg)
+
+    def get_creds(self, target_username, target_password):
+        creds_tmp = Credentials()
+        creds_tmp.set_username(target_username)
+        creds_tmp.set_password(target_password)
+        creds_tmp.set_domain(creds.get_domain())
+        creds_tmp.set_realm(creds.get_realm())
+        creds_tmp.set_workstation(creds.get_workstation())
+        creds_tmp.set_gensec_features(creds_tmp.get_gensec_features()
+                                      | gensec.FEATURE_SEAL)
+        creds_tmp.set_kerberos_state(DONT_USE_KERBEROS) # kinit is too expensive to use in a tight loop
+        return creds_tmp
+
+    def setUp(self):
+        super(UserAccountControlTests, self).setUp()
+        self.admin_creds = creds
+        self.admin_samdb = SamDB(url=ldaphost,
+                                 session_info=system_session(),
+                                 credentials=self.admin_creds, lp=lp)
+
+        self.unpriv_user = "testuser1"
+        self.unpriv_user_pw = "samba123@"
+        self.unpriv_creds = self.get_creds(self.unpriv_user, self.unpriv_user_pw)
+
+        self.admin_samdb.newuser(self.unpriv_user, self.unpriv_user_pw)
+        res = self.admin_samdb.search("CN=%s,CN=Users,%s" % (self.unpriv_user, self.admin_samdb.domain_dn()),
+                                      scope=SCOPE_BASE,
+                                      attrs=["objectSid"])
+        self.assertEqual(1, len(res))
+
+        self.unpriv_user_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
+        self.unpriv_user_dn = res[0].dn
+
+        self.samdb = SamDB(url=ldaphost, credentials=self.unpriv_creds, lp=lp)
+        self.domain_sid = security.dom_sid(self.samdb.get_domain_sid())
+        self.base_dn = self.samdb.domain_dn()
+
+        self.samr = samr.samr("ncacn_ip_tcp:%s[sign]" % host, lp, self.unpriv_creds)
+        self.samr_handle = self.samr.Connect2(None, security.SEC_FLAG_MAXIMUM_ALLOWED)
+        self.samr_domain = self.samr.OpenDomain(self.samr_handle, security.SEC_FLAG_MAXIMUM_ALLOWED, self.domain_sid)
+
+        self.sd_utils = sd_utils.SDUtils(self.admin_samdb)
+
+        self.admin_samdb.create_ou("OU=test_computer_ou1," + self.base_dn)
+        self.unpriv_user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
+        mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.unpriv_user_sid)
+
+        old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
+
+        self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
+
+        self.add_computer_ldap("testcomputer-t")
+
+        self.sd_utils.modify_sd_on_dn("OU=test_computer_ou1," + self.base_dn, old_sd)
+
+        self.computernames = ["testcomputer-0"]
+
+        # Get the SD of the template account, then force it to match
+        # what we expect for SeMachineAccountPrivilege accounts, so we
+        # can confirm we created the accounts correctly
+        self.sd_reference_cc = self.sd_utils.read_sd_on_dn("CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn))
+
+        self.sd_reference_modify = self.sd_utils.read_sd_on_dn("CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn))
+        for ace in self.sd_reference_modify.dacl.aces:
+            if ace.type == security.SEC_ACE_TYPE_ACCESS_ALLOWED and ace.trustee == self.unpriv_user_sid:
+                ace.access_mask = ace.access_mask | security.SEC_ADS_SELF_WRITE | security.SEC_ADS_WRITE_PROP
+
+        # Now reconnect without domain admin rights
+        self.samdb = SamDB(url=ldaphost, credentials=self.unpriv_creds, lp=lp)
+
+
+    def tearDown(self):
+        super(UserAccountControlTests, self).tearDown()
+        for computername in self.computernames:
+            delete_force(self.admin_samdb, "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn))
+        delete_force(self.admin_samdb, "CN=testcomputer-t,OU=test_computer_ou1,%s" % (self.base_dn))
+        delete_force(self.admin_samdb, "OU=test_computer_ou1,%s" % (self.base_dn))
+        delete_force(self.admin_samdb, "CN=%s,CN=Users,%s" % (self.unpriv_user, self.base_dn))
+
+    def test_add_computer_sd_cc(self):
+        user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
+        mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
+
+        old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
+
+        self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
+
+        computername=self.computernames[0]
+        sd = ldb.MessageElement((ndr_pack(self.sd_reference_modify)),
+                                ldb.FLAG_MOD_ADD,
+                                "nTSecurityDescriptor")
+        self.add_computer_ldap(computername,
+                               others={"nTSecurityDescriptor": sd})
+
+        res = self.admin_samdb.search("%s" % self.base_dn,
+                                      expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
+                                      scope=SCOPE_SUBTREE,
+                                      attrs=["ntSecurityDescriptor"])
+
+        desc = res[0]["nTSecurityDescriptor"][0]
+        desc = ndr_unpack(security.descriptor, desc, allow_remaining=True)
+
+        sddl = desc.as_sddl(self.domain_sid)
+        self.assertEqual(self.sd_reference_modify.as_sddl(self.domain_sid), sddl)
+
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["description"]= ldb.MessageElement(
+            ("A description"), ldb.FLAG_MOD_REPLACE,
+            "description")
+        self.samdb.modify(m)
+
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_SERVER_TRUST_ACCOUNT),
+                                                     ldb.FLAG_MOD_REPLACE, "userAccountControl")
+        try:
+            self.samdb.modify(m)
+            self.fail("Unexpectedly able to set userAccountControl to be a DC on %s" % m.dn)
+        except LdbError, (enum, estr):
+            self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
+
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT|samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT),
+                                                     ldb.FLAG_MOD_REPLACE, "userAccountControl")
+        try:
+            self.samdb.modify(m)
+            self.fail("Unexpectedly able to set userAccountControl to be an RODC on %s" % m.dn)
+        except LdbError, (enum, estr):
+            self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
+
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT),
+                                                     ldb.FLAG_MOD_REPLACE, "userAccountControl")
+        self.samdb.modify(m)
+
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["primaryGroupID"] = ldb.MessageElement(str(security.DOMAIN_RID_ADMINS),
+                                                 ldb.FLAG_MOD_REPLACE, "primaryGroupID")
+        try:
+            self.samdb.modify(m)
+        except LdbError, (enum, estr):
+            self.assertEqual(ldb.ERR_UNWILLING_TO_PERFORM, enum)
+            return
+        self.fail()
+
+    def test_mod_computer_cc(self):
+        user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
+        mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
+
+        old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
+
+        self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
+
+        computername=self.computernames[0]
+        self.add_computer_ldap(computername)
+
+        res = self.admin_samdb.search("%s" % self.base_dn,
+                                      expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
+                                      scope=SCOPE_SUBTREE,
+                                      attrs=[])
+
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["description"]= ldb.MessageElement(
+            ("A description"), ldb.FLAG_MOD_REPLACE,
+            "description")
+        self.samdb.modify(m)
+
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT|samba.dsdb.UF_PARTIAL_SECRETS_ACCOUNT),
+                                                     ldb.FLAG_MOD_REPLACE, "userAccountControl")
+        try:
+            self.samdb.modify(m)
+            self.fail("Unexpectedly able to set userAccountControl on %s" % m.dn)
+        except LdbError, (enum, estr):
+            self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
+
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_SERVER_TRUST_ACCOUNT),
+                                                     ldb.FLAG_MOD_REPLACE, "userAccountControl")
+        try:
+             self.samdb.modify(m)
+             self.fail()
+        except LdbError, (enum, estr):
+             self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
+
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_NORMAL_ACCOUNT),
+                                                     ldb.FLAG_MOD_REPLACE, "userAccountControl")
+        self.samdb.modify(m)
+
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["userAccountControl"] = ldb.MessageElement(str(samba.dsdb.UF_WORKSTATION_TRUST_ACCOUNT),
+                                                     ldb.FLAG_MOD_REPLACE, "userAccountControl")
+        self.samdb.modify(m)
+
+    def test_admin_mod_uac(self):
+        computername=self.computernames[0]
+        self.add_computer_ldap(computername, samdb=self.admin_samdb)
+
+        res = self.admin_samdb.search("%s" % self.base_dn,
+                                      expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
+                                      scope=SCOPE_SUBTREE,
+                                      attrs=["userAccountControl"])
+
+        self.assertEqual(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT|UF_ACCOUNTDISABLE|UF_PASSWD_NOTREQD)
+
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["userAccountControl"] = ldb.MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT|UF_PARTIAL_SECRETS_ACCOUNT),
+                                                     ldb.FLAG_MOD_REPLACE, "userAccountControl")
+        self.admin_samdb.modify(m)
+
+        res = self.admin_samdb.search("%s" % self.base_dn,
+                                      expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
+                                      scope=SCOPE_SUBTREE,
+                                      attrs=["userAccountControl"])
+
+        self.assertEqual(int(res[0]["userAccountControl"][0]), UF_WORKSTATION_TRUST_ACCOUNT|UF_PARTIAL_SECRETS_ACCOUNT)
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["userAccountControl"] = ldb.MessageElement(str(UF_ACCOUNTDISABLE),
+                                                     ldb.FLAG_MOD_REPLACE, "userAccountControl")
+        self.admin_samdb.modify(m)
+
+        res = self.admin_samdb.search("%s" % self.base_dn,
+                                      expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
+                                      scope=SCOPE_SUBTREE,
+                                      attrs=["userAccountControl"])
+
+        self.assertEqual(int(res[0]["userAccountControl"][0]), UF_WORKSTATION_TRUST_ACCOUNT| UF_ACCOUNTDISABLE)
+
+
+    def test_uac_bits_set(self):
+        user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
+        mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
+
+        old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
+
+        self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
+
+        computername=self.computernames[0]
+        self.add_computer_ldap(computername)
+
+        res = self.admin_samdb.search("%s" % self.base_dn,
+                                      expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
+                                      scope=SCOPE_SUBTREE,
+                                      attrs=[])
+
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["description"]= ldb.MessageElement(
+            ("A description"), ldb.FLAG_MOD_REPLACE,
+            "description")
+        self.samdb.modify(m)
+
+        # These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test
+        priv_to_auth_users_bits = set([UF_PASSWD_NOTREQD, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,
+                                       UF_DONT_EXPIRE_PASSWD])
+
+        # These bits really are privileged
+        priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT,
+                         UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION,
+                         UF_PARTIAL_SECRETS_ACCOUNT])
+
+        invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT])
+
+        for bit in bits:
+            m = ldb.Message()
+            m.dn = res[0].dn
+            m["userAccountControl"] = ldb.MessageElement(str(bit|UF_PASSWD_NOTREQD),
+                                                         ldb.FLAG_MOD_REPLACE, "userAccountControl")
+            try:
+                self.samdb.modify(m)
+                if (bit in priv_bits):
+                    self.fail("Unexpectedly able to set userAccountControl bit 0x%08X on %s" % (bit, m.dn))
+            except LdbError, (enum, estr):
+                if bit in invalid_bits:
+                    self.assertEqual(enum, ldb.ERR_OTHER, "was not able to set 0x%08X on %s" % (bit, m.dn))
+                    # No point going on, try the next bit
+                    continue
+                elif (bit in priv_bits):
+                    self.assertEqual(ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS, enum)
+                else:
+                    self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
+
+
+    def test_uac_bits_unrelated_modify(self):
+        user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
+        mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
+
+        old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
+
+        self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
+
+        computername=self.computernames[0]
+        self.add_computer_ldap(computername)
+
+        res = self.admin_samdb.search("%s" % self.base_dn,
+                                      expression="(&(objectClass=computer)(samAccountName=%s$))" % computername,
+                                      scope=SCOPE_SUBTREE,
+                                      attrs=[])
+
+        m = ldb.Message()
+        m.dn = res[0].dn
+        m["description"]= ldb.MessageElement(
+            ("A description"), ldb.FLAG_MOD_REPLACE,
+            "description")
+        self.samdb.modify(m)
+
+        invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT])
+
+        super_priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT])
+
+        priv_to_remove_bits = set([UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION])
+
+        for bit in bits:
+            m = ldb.Message()
+            m.dn = res[0].dn
+            m["userAccountControl"] = ldb.MessageElement(str(bit|UF_PASSWD_NOTREQD),
+                                                         ldb.FLAG_MOD_REPLACE, "userAccountControl")
+            try:
+                self.admin_samdb.modify(m)
+                if bit in invalid_bits:
+                    self.fail("Should have been unable to set userAccountControl bit 0x%08X on %s" % (bit, m.dn))
+
+            except LdbError, (enum, estr):
+                if bit in invalid_bits:
+                    self.assertEqual(enum, ldb.ERR_OTHER)
+                    # No point going on, try the next bit
+                    continue
+                elif bit in super_priv_bits:
+                    self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)
+                    # No point going on, try the next bit
+                    continue
+                else:
+                    self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
+
+            try:
+                m = ldb.Message()
+                m.dn = res[0].dn
+                m["userAccountControl"] = ldb.MessageElement(str(bit|UF_PASSWD_NOTREQD|UF_ACCOUNTDISABLE),
+                                                             ldb.FLAG_MOD_REPLACE, "userAccountControl")
+                self.samdb.modify(m)
+
+            except LdbError, (enum, estr):
+                self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
+
+            try:
+                m = ldb.Message()
+                m.dn = res[0].dn
+                m["userAccountControl"] = ldb.MessageElement(str(UF_PASSWD_NOTREQD|UF_ACCOUNTDISABLE),
+                                                             ldb.FLAG_MOD_REPLACE, "userAccountControl")
+                self.samdb.modify(m)
+                if bit in priv_to_remove_bits:
+                    self.fail("Should have been unable to remove userAccountControl bit 0x%08X on %s" % (bit, m.dn))
+
+            except LdbError, (enum, estr):
+                if bit in priv_to_remove_bits:
+                    self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)
+                else:
+                    self.fail("Unexpectedly able to remove userAccountControl bit 0x%08X on %s: %s" % (bit, m.dn, estr))
+
+    def test_uac_bits_add(self):
+        computername=self.computernames[0]
+
+        user_sid = self.sd_utils.get_object_sid(self.unpriv_user_dn)
+        mod = "(OA;;CC;bf967a86-0de6-11d0-a285-00aa003049e2;;%s)" % str(user_sid)
+
+        old_sd = self.sd_utils.read_sd_on_dn("OU=test_computer_ou1," + self.base_dn)
+
+        self.sd_utils.dacl_add_ace("OU=test_computer_ou1," + self.base_dn, mod)
+
+        invalid_bits = set([UF_TEMP_DUPLICATE_ACCOUNT])
+
+        # These bits are privileged, but authenticated users have that CAR by default, so this is a pain to test
+        priv_to_auth_users_bits = set([UF_PASSWD_NOTREQD, UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,
+                                       UF_DONT_EXPIRE_PASSWD])
+
+        # These bits really are privileged
+        priv_bits = set([UF_INTERDOMAIN_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT,
+                         UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION,
+                         UF_PARTIAL_SECRETS_ACCOUNT])
+
+        for bit in bits:
+            try:
+                self.add_computer_ldap(computername, others={"userAccountControl": [str(bit)]})
+                delete_force(self.admin_samdb, "CN=%s,OU=test_computer_ou1,%s" % (computername, self.base_dn))
+                if bit in priv_bits:
+                    self.fail("Unexpectdly able to set userAccountControl bit 0x%08X on %s" % (bit, computername))
+
+            except LdbError, (enum, estr):
+                if bit in invalid_bits:
+                    self.assertEqual(enum, ldb.ERR_OTHER, "Invalid bit 0x%08X was able to be set on %s" % (bit, computername))
+                    # No point going on, try the next bit
+                    continue
+                elif bit in priv_bits:
+                    self.assertEqual(enum, ldb.ERR_INSUFFICIENT_ACCESS_RIGHTS)
+                    continue
+                else:
+                    self.fail("Unable to set userAccountControl bit 0x%08X on %s: %s" % (bit, computername, estr))
+
+
+
+runner = SubunitTestRunner()
+rc = 0
+if not runner.run(unittest.makeSuite(UserAccountControlTests)).wasSuccessful():
+    rc = 1
+sys.exit(rc)
index ad2bcec9eb34613a560e61eab49f0326e17f4a27..a991e1271f4382f4cf4d5df1ffa5b3e0a74c166e 100755 (executable)
@@ -465,6 +465,7 @@ planoldpythontestsuite("plugin_s4_dc", "samba.tests.dcerpc.dnsserver", extra_arg
 plantestsuite_loadlist("samba4.ldap.python(dc)", "dc", [python, os.path.join(samba4srcdir, "dsdb/tests/python/ldap.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT'])
 plantestsuite_loadlist("samba4.tokengroups.python(dc)", "dc:local", [python, os.path.join(samba4srcdir, "dsdb/tests/python/token_group.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT'])
 plantestsuite("samba4.sam.python(dc)", "dc", [python, os.path.join(samba4srcdir, "dsdb/tests/python/sam.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN'])
+plantestsuite("samba4.user_account_control.python(dc)", "dc", [python, os.path.join(samba4srcdir, "dsdb/tests/python/user_account_control.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN'])
 planoldpythontestsuite("dc", "dsdb_schema_info",
         extra_path=[os.path.join(samba4srcdir, 'dsdb/tests/python')],
         name="samba4.schemaInfo.python(dc)",