libndr: Avoid assigning duplicate versions to symbols
[amitay/samba.git] / source4 / dsdb / tests / python / sam.py
index ce720b76cbf38f1c5b94d300374c60946f848e46..41c348bce5f150d0bc324280fdc8867a5584f944 100755 (executable)
@@ -1,54 +1,59 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 # This is a port of the original in testprogs/ejs/ldap.js
 
+from __future__ import print_function
 import optparse
 import sys
-import time
-import base64
 import os
+import time
 
-sys.path.append("bin/python")
+sys.path.insert(0, "bin/python")
 import samba
-samba.ensure_external_module("subunit", "subunit/python")
-samba.ensure_external_module("testtools", "testtools")
+from samba.tests.subunitrun import SubunitOptions, TestProgram
 
 import samba.getopt as options
 
+from samba.credentials import Credentials, DONT_USE_KERBEROS
 from samba.auth import system_session
-from ldb import SCOPE_SUBTREE, SCOPE_ONELEVEL, SCOPE_BASE, LdbError
+from samba.common import get_string
+
+from ldb import SCOPE_BASE, LdbError
 from ldb import ERR_NO_SUCH_OBJECT, ERR_ATTRIBUTE_OR_VALUE_EXISTS
 from ldb import ERR_ENTRY_ALREADY_EXISTS, ERR_UNWILLING_TO_PERFORM
-from ldb import ERR_NOT_ALLOWED_ON_NON_LEAF, ERR_OTHER, ERR_INVALID_DN_SYNTAX
-from ldb import ERR_NO_SUCH_ATTRIBUTE
-from ldb import ERR_OBJECT_CLASS_VIOLATION, ERR_NOT_ALLOWED_ON_RDN
-from ldb import ERR_NAMING_VIOLATION, ERR_CONSTRAINT_VIOLATION
-from ldb import ERR_UNDEFINED_ATTRIBUTE_TYPE, ERR_INSUFFICIENT_ACCESS_RIGHTS
+from ldb import ERR_OTHER, ERR_NO_SUCH_ATTRIBUTE
+from ldb import ERR_OBJECT_CLASS_VIOLATION
+from ldb import ERR_CONSTRAINT_VIOLATION
+from ldb import ERR_UNDEFINED_ATTRIBUTE_TYPE
+from ldb import ERR_INSUFFICIENT_ACCESS_RIGHTS
+from ldb import ERR_INVALID_CREDENTIALS
+from ldb import ERR_STRONG_AUTH_REQUIRED
 from ldb import Message, MessageElement, Dn
 from ldb import FLAG_MOD_ADD, FLAG_MOD_REPLACE, FLAG_MOD_DELETE
-from samba import Ldb
-from samba.dsdb import (UF_NORMAL_ACCOUNT, UF_INTERDOMAIN_TRUST_ACCOUNT,
-    UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT,
-    UF_PARTIAL_SECRETS_ACCOUNT, UF_TEMP_DUPLICATE_ACCOUNT,
-    UF_PASSWD_NOTREQD, UF_ACCOUNTDISABLE, ATYPE_NORMAL_ACCOUNT,
-    GTYPE_SECURITY_BUILTIN_LOCAL_GROUP, GTYPE_SECURITY_DOMAIN_LOCAL_GROUP,
-    GTYPE_SECURITY_GLOBAL_GROUP, GTYPE_SECURITY_UNIVERSAL_GROUP,
-    GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP, GTYPE_DISTRIBUTION_GLOBAL_GROUP,
-    GTYPE_DISTRIBUTION_UNIVERSAL_GROUP,
-    ATYPE_SECURITY_GLOBAL_GROUP, ATYPE_SECURITY_UNIVERSAL_GROUP,
-    ATYPE_SECURITY_LOCAL_GROUP, ATYPE_DISTRIBUTION_GLOBAL_GROUP,
-    ATYPE_DISTRIBUTION_UNIVERSAL_GROUP, ATYPE_DISTRIBUTION_LOCAL_GROUP,
-    ATYPE_WORKSTATION_TRUST, SYSTEM_FLAG_DOMAIN_DISALLOW_MOVE,
-    SYSTEM_FLAG_CONFIG_ALLOW_RENAME, SYSTEM_FLAG_CONFIG_ALLOW_MOVE,
-    SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE)
-from samba.dcerpc.security import (DOMAIN_RID_USERS, DOMAIN_RID_DOMAIN_MEMBERS,
-    DOMAIN_RID_DCS, DOMAIN_RID_READONLY_DCS)
-
-from subunit.run import SubunitTestRunner
-import unittest
-
-from samba.ndr import ndr_pack, ndr_unpack
+from samba.samdb import SamDB
+from samba.dsdb import (UF_NORMAL_ACCOUNT, UF_ACCOUNTDISABLE,
+                        UF_WORKSTATION_TRUST_ACCOUNT, UF_SERVER_TRUST_ACCOUNT,
+                        UF_PARTIAL_SECRETS_ACCOUNT, UF_TEMP_DUPLICATE_ACCOUNT,
+                        UF_INTERDOMAIN_TRUST_ACCOUNT, UF_SMARTCARD_REQUIRED,
+                        UF_PASSWD_NOTREQD, UF_LOCKOUT, UF_PASSWORD_EXPIRED, ATYPE_NORMAL_ACCOUNT,
+                        GTYPE_SECURITY_BUILTIN_LOCAL_GROUP, GTYPE_SECURITY_DOMAIN_LOCAL_GROUP,
+                        GTYPE_SECURITY_GLOBAL_GROUP, GTYPE_SECURITY_UNIVERSAL_GROUP,
+                        GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP, GTYPE_DISTRIBUTION_GLOBAL_GROUP,
+                        GTYPE_DISTRIBUTION_UNIVERSAL_GROUP,
+                        ATYPE_SECURITY_GLOBAL_GROUP, ATYPE_SECURITY_UNIVERSAL_GROUP,
+                        ATYPE_SECURITY_LOCAL_GROUP, ATYPE_DISTRIBUTION_GLOBAL_GROUP,
+                        ATYPE_DISTRIBUTION_UNIVERSAL_GROUP, ATYPE_DISTRIBUTION_LOCAL_GROUP,
+                        ATYPE_WORKSTATION_TRUST)
+from samba.dcerpc.security import (DOMAIN_RID_USERS, DOMAIN_RID_ADMINS,
+                                   DOMAIN_RID_DOMAIN_MEMBERS, DOMAIN_RID_DCS, DOMAIN_RID_READONLY_DCS)
+
+from samba.ndr import ndr_unpack
+from samba.dcerpc import drsblobs
+from samba.dcerpc import drsuapi
 from samba.dcerpc import security
+from samba.tests import delete_force
+from samba import gensec
+from samba import werror
 
 parser = optparse.OptionParser("sam.py [options] <host>")
 sambaopts = options.SambaOptions(parser)
@@ -57,6 +62,8 @@ parser.add_option_group(options.VersionOptions(parser))
 # use command line creds if available
 credopts = options.CredentialsOptions(parser)
 parser.add_option_group(credopts)
+subunitopts = SubunitOptions(parser)
+parser.add_option_group(subunitopts)
 opts, args = parser.parse_args()
 
 if len(args) < 1:
@@ -67,43 +74,28 @@ host = args[0]
 
 lp = sambaopts.get_loadparm()
 creds = credopts.get_credentials(lp)
+creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
 
-class SamTests(unittest.TestCase):
-
-    def delete_force(self, ldb, dn):
-        try:
-            ldb.delete(dn)
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_NO_SUCH_OBJECT)
-
-    def find_basedn(self, ldb):
-        res = ldb.search(base="", expression="", scope=SCOPE_BASE,
-                         attrs=["defaultNamingContext"])
-        self.assertEquals(len(res), 1)
-        return res[0]["defaultNamingContext"][0]
 
-    def find_domain_sid(self):
-        res = self.ldb.search(base=self.base_dn, expression="(objectClass=*)", scope=SCOPE_BASE)
-        return ndr_unpack( security.dom_sid,res[0]["objectSid"][0])
+class SamTests(samba.tests.TestCase):
 
     def setUp(self):
         super(SamTests, self).setUp()
         self.ldb = ldb
-        self.gc_ldb = gc_ldb
-        self.base_dn = self.find_basedn(ldb)
-        self.domain_sid = self.find_domain_sid()
+        self.base_dn = ldb.domain_dn()
 
-        print "baseDN: %s\n" % self.base_dn
+        print("baseDN: %s\n" % self.base_dn)
 
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        self.delete_force(self.ldb, "cn=ldaptestuser2,cn=users," + self.base_dn)
-        self.delete_force(self.ldb, "cn=ldaptestcomputer,cn=users," + self.base_dn)
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
-        self.delete_force(self.ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser2,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptest\,specialuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn)
 
     def test_users_groups(self):
         """This tests the SAM users and groups behaviour"""
-        print "Testing users and groups behaviour\n"
+        print("Testing users and groups behaviour\n")
 
         ldb.add({
             "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
@@ -116,233 +108,308 @@ class SamTests(unittest.TestCase):
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["objectSID"])
         self.assertTrue(len(res1) == 1)
-        group_rid_1 = security.dom_sid(ldb.schema_format_value("objectSID",
-          res1[0]["objectSID"][0])).split()[1]
+        obj_sid = get_string(ldb.schema_format_value("objectSID",
+                                                     res1[0]["objectSID"][0]))
+        group_rid_1 = security.dom_sid(obj_sid).split()[1]
 
         res1 = ldb.search("cn=ldaptestgroup2,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["objectSID"])
         self.assertTrue(len(res1) == 1)
-        group_rid_2 = security.dom_sid(ldb.schema_format_value("objectSID",
-          res1[0]["objectSID"][0])).split()[1]
+        obj_sid = get_string(ldb.schema_format_value("objectSID",
+                                                     res1[0]["objectSID"][0]))
+        group_rid_2 = security.dom_sid(obj_sid).split()[1]
+
+        # Try to create a user with an invalid account name
+        try:
+            ldb.add({
+                "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+                "objectclass": "user",
+                "sAMAccountName": "administrator"})
+            self.fail()
+        except LdbError as e9:
+            (num, _) = e9.args
+            self.assertEqual(num, ERR_ENTRY_ALREADY_EXISTS)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+        # Try to create a user with an invalid account name
+        try:
+            ldb.add({
+                "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+                "objectclass": "user",
+                "sAMAccountName": []})
+            self.fail()
+        except LdbError as e10:
+            (num, _) = e10.args
+            self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         # Try to create a user with an invalid primary group
         try:
             ldb.add({
                 "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-                "objectclass": ["user", "person"],
+                "objectclass": "user",
                 "primaryGroupID": "0"})
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        except LdbError as e11:
+            (num, _) = e11.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         # Try to Create a user with a valid primary group
         try:
             ldb.add({
                 "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-                "objectclass": ["user", "person"],
+                "objectclass": "user",
                 "primaryGroupID": str(group_rid_1)})
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        except LdbError as e12:
+            (num, _) = e12.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         # Test to see how we should behave when the user account doesn't
         # exist
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
         m["primaryGroupID"] = MessageElement("0", FLAG_MOD_REPLACE,
-          "primaryGroupID")
+                                             "primaryGroupID")
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_NO_SUCH_OBJECT)
+        except LdbError as e13:
+            (num, _) = e13.args
+            self.assertEqual(num, ERR_NO_SUCH_OBJECT)
 
         # Test to see how we should behave when the account isn't a user
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["primaryGroupID"] = MessageElement("0", FLAG_MOD_REPLACE,
-          "primaryGroupID")
+                                             "primaryGroupID")
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_OBJECT_CLASS_VIOLATION)
+        except LdbError as e14:
+            (num, _) = e14.args
+            self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION)
 
         # Test default primary groups on add operations
 
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["user", "person"]})
+            "objectclass": "user"})
 
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["primaryGroupID"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(res1[0]["primaryGroupID"][0], str(DOMAIN_RID_USERS))
+        self.assertEqual(int(res1[0]["primaryGroupID"][0]), DOMAIN_RID_USERS)
 
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["user", "person"],
-            "userAccountControl": str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD) })
+            "objectclass": "user",
+            "userAccountControl": str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD)})
 
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["primaryGroupID"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(res1[0]["primaryGroupID"][0], str(DOMAIN_RID_USERS))
+        self.assertEqual(int(res1[0]["primaryGroupID"][0]), DOMAIN_RID_USERS)
 
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         # unfortunately the INTERDOMAIN_TRUST_ACCOUNT case cannot be tested
         # since such accounts aren't directly creatable (ACCESS_DENIED)
 
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["computer"],
-            "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PASSWD_NOTREQD) })
+            "objectclass": "computer",
+            "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT |
+                                      UF_PASSWD_NOTREQD)})
 
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["primaryGroupID"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(res1[0]["primaryGroupID"][0], str(DOMAIN_RID_DOMAIN_MEMBERS))
+        self.assertEqual(int(res1[0]["primaryGroupID"][0]),
+                          DOMAIN_RID_DOMAIN_MEMBERS)
 
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["computer"],
-            "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT | UF_PASSWD_NOTREQD) })
+            "objectclass": "computer",
+            "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT |
+                                      UF_PASSWD_NOTREQD)})
 
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["primaryGroupID"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(res1[0]["primaryGroupID"][0], str(DOMAIN_RID_DCS))
+        self.assertEqual(int(res1[0]["primaryGroupID"][0]), DOMAIN_RID_DCS)
 
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         # Read-only DC accounts are only creatable by
         # UF_WORKSTATION_TRUST_ACCOUNT and work only on DCs >= 2008 (therefore
         # we have a fallback in the assertion)
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["computer"],
-            "userAccountControl": str(UF_PARTIAL_SECRETS_ACCOUNT | UF_WORKSTATION_TRUST_ACCOUNT | UF_PASSWD_NOTREQD) })
+            "objectclass": "computer",
+            "userAccountControl": str(UF_PARTIAL_SECRETS_ACCOUNT |
+                                      UF_WORKSTATION_TRUST_ACCOUNT |
+                                      UF_PASSWD_NOTREQD)})
 
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["primaryGroupID"])
         self.assertTrue(len(res1) == 1)
-        self.assertTrue(res1[0]["primaryGroupID"][0] == str(DOMAIN_RID_READONLY_DCS) or
-                        res1[0]["primaryGroupID"][0] == str(DOMAIN_RID_DOMAIN_MEMBERS))
+        self.assertTrue(int(res1[0]["primaryGroupID"][0]) == DOMAIN_RID_READONLY_DCS or
+                        int(res1[0]["primaryGroupID"][0]) == DOMAIN_RID_DOMAIN_MEMBERS)
 
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         # Test default primary groups on modify operations
 
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["user", "person"]})
+            "objectclass": "user"})
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        m["userAccountControl"] = MessageElement(str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD), FLAG_MOD_REPLACE,
-          "userAccountControl")
+        m["userAccountControl"] = MessageElement(str(UF_NORMAL_ACCOUNT |
+                                                     UF_PASSWD_NOTREQD),
+                                                 FLAG_MOD_REPLACE,
+                                                 "userAccountControl")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["primaryGroupID"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(res1[0]["primaryGroupID"][0], str(DOMAIN_RID_USERS))
+        self.assertEqual(int(res1[0]["primaryGroupID"][0]), DOMAIN_RID_USERS)
 
         # unfortunately the INTERDOMAIN_TRUST_ACCOUNT case cannot be tested
         # since such accounts aren't directly creatable (ACCESS_DENIED)
 
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["computer"]})
+            "objectclass": "computer"})
 
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["primaryGroupID"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(res1[0]["primaryGroupID"][0], str(DOMAIN_RID_USERS))
+        self.assertEqual(int(res1[0]["primaryGroupID"][0]), DOMAIN_RID_USERS)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        m["userAccountControl"] = MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PASSWD_NOTREQD), FLAG_MOD_REPLACE,
-          "userAccountControl")
+        m["userAccountControl"] = MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT |
+                                                     UF_PASSWD_NOTREQD),
+                                                 FLAG_MOD_REPLACE,
+                                                 "userAccountControl")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["primaryGroupID"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(res1[0]["primaryGroupID"][0], str(DOMAIN_RID_DOMAIN_MEMBERS))
+        self.assertEqual(int(res1[0]["primaryGroupID"][0]), DOMAIN_RID_DOMAIN_MEMBERS)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        m["userAccountControl"] = MessageElement(str(UF_SERVER_TRUST_ACCOUNT | UF_PASSWD_NOTREQD), FLAG_MOD_REPLACE,
-          "userAccountControl")
+        m["userAccountControl"] = MessageElement(str(UF_SERVER_TRUST_ACCOUNT |
+                                                     UF_PASSWD_NOTREQD),
+                                                 FLAG_MOD_REPLACE,
+                                                 "userAccountControl")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["primaryGroupID"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(res1[0]["primaryGroupID"][0], str(DOMAIN_RID_DCS))
+        self.assertEqual(int(res1[0]["primaryGroupID"][0]), DOMAIN_RID_DCS)
 
         # Read-only DC accounts are only creatable by
         # UF_WORKSTATION_TRUST_ACCOUNT and work only on DCs >= 2008 (therefore
         # we have a fallback in the assertion)
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        m["userAccountControl"] = MessageElement(str(UF_PARTIAL_SECRETS_ACCOUNT | UF_WORKSTATION_TRUST_ACCOUNT | UF_PASSWD_NOTREQD), FLAG_MOD_REPLACE,
-          "userAccountControl")
+        m["userAccountControl"] = MessageElement(str(UF_PARTIAL_SECRETS_ACCOUNT |
+                                                     UF_WORKSTATION_TRUST_ACCOUNT |
+                                                     UF_PASSWD_NOTREQD),
+                                                 FLAG_MOD_REPLACE,
+                                                 "userAccountControl")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["primaryGroupID"])
         self.assertTrue(len(res1) == 1)
-        self.assertTrue(res1[0]["primaryGroupID"][0] == str(DOMAIN_RID_READONLY_DCS) or
-                        res1[0]["primaryGroupID"][0] == str(DOMAIN_RID_DOMAIN_MEMBERS))
+        self.assertTrue(int(res1[0]["primaryGroupID"][0]) == DOMAIN_RID_READONLY_DCS or
+                        int(res1[0]["primaryGroupID"][0]) == DOMAIN_RID_DOMAIN_MEMBERS)
 
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         # Recreate account for further tests
 
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["user", "person"]})
+            "objectclass": "user"})
+
+        # Try to set an invalid account name
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["sAMAccountName"] = MessageElement("administrator", FLAG_MOD_REPLACE,
+                                             "sAMAccountName")
+        try:
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e15:
+            (num, _) = e15.args
+            self.assertEqual(num, ERR_ENTRY_ALREADY_EXISTS)
+
+        # But to reset the actual "sAMAccountName" should still be possible
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE, attrs=["sAMAccountName"])
+        self.assertTrue(len(res1) == 1)
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["sAMAccountName"] = MessageElement(res1[0]["sAMAccountName"][0], FLAG_MOD_REPLACE,
+                                             "sAMAccountName")
+        ldb.modify(m)
+
+        # And another (free) name should be possible as well
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["sAMAccountName"] = MessageElement("xxx_ldaptestuser_xxx", FLAG_MOD_REPLACE,
+                                             "sAMAccountName")
+        ldb.modify(m)
 
         # We should be able to reset our actual primary group
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
         m["primaryGroupID"] = MessageElement(str(DOMAIN_RID_USERS), FLAG_MOD_REPLACE,
-          "primaryGroupID")
+                                             "primaryGroupID")
         ldb.modify(m)
 
         # Try to add invalid primary group
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
         m["primaryGroupID"] = MessageElement("0", FLAG_MOD_REPLACE,
-          "primaryGroupID")
+                                             "primaryGroupID")
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e16:
+            (num, _) = e16.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Try to make group 1 primary - should be denied since it is not yet
         # secondary
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
         m["primaryGroupID"] = MessageElement(str(group_rid_1),
-          FLAG_MOD_REPLACE, "primaryGroupID")
+                                             FLAG_MOD_REPLACE, "primaryGroupID")
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e17:
+            (num, _) = e17.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Make group 1 secondary
         m = Message()
@@ -355,15 +422,16 @@ class SamTests(unittest.TestCase):
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
         m["primaryGroupID"] = MessageElement(str(group_rid_1),
-          FLAG_MOD_REPLACE, "primaryGroupID")
+                                             FLAG_MOD_REPLACE, "primaryGroupID")
         ldb.modify(m)
 
         # Try to delete group 1 - should be denied
         try:
             ldb.delete("cn=ldaptestgroup,cn=users," + self.base_dn)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_ENTRY_ALREADY_EXISTS)
+        except LdbError as e18:
+            (num, _) = e18.args
+            self.assertEqual(num, ERR_ENTRY_ALREADY_EXISTS)
 
         # Try to add group 1 also as secondary - should be denied
         m = Message()
@@ -373,20 +441,22 @@ class SamTests(unittest.TestCase):
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_ENTRY_ALREADY_EXISTS)
+        except LdbError as e19:
+            (num, _) = e19.args
+            self.assertEqual(num, ERR_ENTRY_ALREADY_EXISTS)
 
         # Try to add invalid member to group 1 - should be denied
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["member"] = MessageElement(
-          "cn=ldaptestuser3,cn=users," + self.base_dn,
-          FLAG_MOD_ADD, "member")
+            "cn=ldaptestuser3,cn=users," + self.base_dn,
+            FLAG_MOD_ADD, "member")
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_NO_SUCH_OBJECT)
+        except LdbError as e20:
+            (num, _) = e20.args
+            self.assertEqual(num, ERR_NO_SUCH_OBJECT)
 
         # Make group 2 secondary
         m = Message()
@@ -399,7 +469,16 @@ class SamTests(unittest.TestCase):
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
         m["primaryGroupID"] = MessageElement(str(group_rid_2),
-          FLAG_MOD_REPLACE, "primaryGroupID")
+                                             FLAG_MOD_REPLACE, "primaryGroupID")
+        ldb.modify(m)
+
+        # Swap the groups (does not really make sense but does the same)
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["primaryGroupID"] = MessageElement(str(group_rid_1),
+                                             FLAG_MOD_REPLACE, "primaryGroupID")
+        m["primaryGroupID"] = MessageElement(str(group_rid_2),
+                                             FLAG_MOD_REPLACE, "primaryGroupID")
         ldb.modify(m)
 
         # Old primary group should contain a "member" attribute for the user,
@@ -408,8 +487,8 @@ class SamTests(unittest.TestCase):
                           scope=SCOPE_BASE, attrs=["member"])
         self.assertTrue(len(res1) == 1)
         self.assertTrue(len(res1[0]["member"]) == 1)
-        self.assertEquals(res1[0]["member"][0].lower(),
-          ("cn=ldaptestuser,cn=users," + self.base_dn).lower())
+        self.assertEqual(str(res1[0]["member"][0]).lower(),
+                          ("cn=ldaptestuser,cn=users," + self.base_dn).lower())
 
         res1 = ldb.search("cn=ldaptestgroup2, cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["member"])
@@ -424,8 +503,9 @@ class SamTests(unittest.TestCase):
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e21:
+            (num, _) = e21.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Delete invalid group member
         m = Message()
@@ -435,30 +515,32 @@ class SamTests(unittest.TestCase):
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e22:
+            (num, _) = e22.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Also this should be denied
         try:
             ldb.add({
-              "dn": "cn=ldaptestuser2,cn=users," + self.base_dn,
-              "objectclass": ["user", "person"],
-              "primaryGroupID": "0"})
+                "dn": "cn=ldaptestuser2,cn=users," + self.base_dn,
+                "objectclass": "user",
+                "primaryGroupID": "0"})
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e23:
+            (num, _) = e23.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Recreate user accounts
 
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["user", "person"]})
+            "objectclass": "user"})
 
         ldb.add({
             "dn": "cn=ldaptestuser2,cn=users," + self.base_dn,
-            "objectclass": ["user", "person"]})
+            "objectclass": "user"})
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn)
@@ -474,8 +556,26 @@ class SamTests(unittest.TestCase):
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_ENTRY_ALREADY_EXISTS)
+        except LdbError as e24:
+            (num, _) = e24.args
+            self.assertEqual(num, ERR_ENTRY_ALREADY_EXISTS)
+
+        # Already added, but as <SID=...>
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE, attrs=["objectSid"])
+        self.assertTrue(len(res1) == 1)
+        sid_bin = res1[0]["objectSid"][0]
+        sid_str = ("<SID=" + get_string(ldb.schema_format_value("objectSid", sid_bin)) + ">").upper()
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn)
+        m["member"] = MessageElement(sid_str, FLAG_MOD_ADD, "member")
+        try:
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e25:
+            (num, _) = e25.args
+            self.assertEqual(num, ERR_ENTRY_ALREADY_EXISTS)
 
         # Invalid member
         m = Message()
@@ -485,8 +585,9 @@ class SamTests(unittest.TestCase):
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_NO_SUCH_OBJECT)
+        except LdbError as e26:
+            (num, _) = e26.args
+            self.assertEqual(num, ERR_NO_SUCH_OBJECT)
 
         # Invalid member
         m = Message()
@@ -497,8 +598,9 @@ class SamTests(unittest.TestCase):
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_NO_SUCH_OBJECT)
+        except LdbError as e27:
+            (num, _) = e27.args
+            self.assertEqual(num, ERR_NO_SUCH_OBJECT)
 
         # Invalid member
         m = Message()
@@ -510,8 +612,9 @@ class SamTests(unittest.TestCase):
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_NO_SUCH_OBJECT)
+        except LdbError as e28:
+            (num, _) = e28.args
+            self.assertEqual(num, ERR_NO_SUCH_OBJECT)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn)
@@ -520,118 +623,155 @@ class SamTests(unittest.TestCase):
                                      FLAG_MOD_REPLACE, "member")
         ldb.modify(m)
 
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        self.delete_force(self.ldb, "cn=ldaptestuser2,cn=users," + self.base_dn)
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
-        self.delete_force(self.ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser2,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestgroup2,cn=users," + self.base_dn)
+
+        # Make also a small test for accounts with special DNs ("," in this case)
+        ldb.add({
+            "dn": "cn=ldaptest\,specialuser,cn=users," + self.base_dn,
+            "objectclass": "user"})
+        delete_force(self.ldb, "cn=ldaptest\,specialuser,cn=users," + self.base_dn)
 
     def test_sam_attributes(self):
         """Test the behaviour of special attributes of SAM objects"""
-        print "Testing the behaviour of special attributes of SAM objects\n"""
+        print("Testing the behaviour of special attributes of SAM objects\n")
 
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["user", "person"]})
+            "objectclass": "user"})
         ldb.add({
             "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
             "objectclass": "group"})
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
-        m["groupType"] = MessageElement("0", FLAG_MOD_ADD,
-          "groupType")
+        m["groupType"] = MessageElement(str(GTYPE_SECURITY_GLOBAL_GROUP), FLAG_MOD_ADD,
+                                        "groupType")
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
+        except LdbError as e29:
+            (num, _) = e29.args
+            self.assertEqual(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
 
-        m = Message()
-        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
-        m["groupType"] = MessageElement([], FLAG_MOD_DELETE,
-          "groupType")
-        try:
-            ldb.modify(m)
-            self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        # Delete protection tests
 
-        m = Message()
-        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        m["primaryGroupID"] = MessageElement("0", FLAG_MOD_ADD,
-          "primaryGroupID")
-        try:
-            ldb.modify(m)
-            self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
+        for attr in ["nTSecurityDescriptor", "objectSid", "sAMAccountType",
+                     "sAMAccountName", "groupType"]:
+
+            m = Message()
+            m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+            m[attr] = MessageElement([], FLAG_MOD_REPLACE, attr)
+            try:
+                ldb.modify(m)
+                self.fail()
+            except LdbError as e:
+                (num, _) = e.args
+                self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+
+            m = Message()
+            m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+            m[attr] = MessageElement([], FLAG_MOD_DELETE, attr)
+            try:
+                ldb.modify(m)
+                self.fail()
+            except LdbError as e1:
+                (num, _) = e1.args
+                self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        m["primaryGroupID"] = MessageElement([], FLAG_MOD_DELETE,
-          "primaryGroupID")
+        m["primaryGroupID"] = MessageElement("513", FLAG_MOD_ADD,
+                                             "primaryGroupID")
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e30:
+            (num, _) = e30.args
+            self.assertEqual(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        m["userAccountControl"] = MessageElement("0", FLAG_MOD_ADD,
-          "userAccountControl")
+        m["userAccountControl"] = MessageElement(str(UF_NORMAL_ACCOUNT |
+                                                     UF_PASSWD_NOTREQD),
+                                                 FLAG_MOD_ADD,
+                                                 "userAccountControl")
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
+        except LdbError as e31:
+            (num, _) = e31.args
+            self.assertEqual(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        m["userAccountControl"] = MessageElement([], FLAG_MOD_DELETE,
-          "userAccountControl")
+        m["objectSid"] = MessageElement("xxxxxxxxxxxxxxxx", FLAG_MOD_ADD,
+                                        "objectSid")
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e32:
+            (num, _) = e32.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
         m["sAMAccountType"] = MessageElement("0", FLAG_MOD_ADD,
-          "sAMAccountType")
+                                             "sAMAccountType")
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e33:
+            (num, _) = e33.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        m["sAMAccountType"] = MessageElement([], FLAG_MOD_REPLACE,
-          "sAMAccountType")
+        m["sAMAccountName"] = MessageElement("test", FLAG_MOD_ADD,
+                                             "sAMAccountName")
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e34:
+            (num, _) = e34.args
+            self.assertEqual(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
 
-        m = Message()
-        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        m["sAMAccountType"] = MessageElement([], FLAG_MOD_DELETE,
-          "sAMAccountType")
-        try:
-            ldb.modify(m)
-            self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        # Delete protection tests
 
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        for attr in ["nTSecurityDescriptor", "objectSid", "sAMAccountType",
+                     "sAMAccountName", "primaryGroupID", "userAccountControl",
+                     "accountExpires", "badPasswordTime", "badPwdCount",
+                     "codePage", "countryCode", "lastLogoff", "lastLogon",
+                     "logonCount", "pwdLastSet"]:
+
+            m = Message()
+            m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+            m[attr] = MessageElement([], FLAG_MOD_REPLACE, attr)
+            try:
+                ldb.modify(m)
+                self.fail()
+            except LdbError as e2:
+                (num, _) = e2.args
+                self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+
+            m = Message()
+            m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+            m[attr] = MessageElement([], FLAG_MOD_DELETE, attr)
+            try:
+                ldb.modify(m)
+                self.fail()
+            except LdbError as e3:
+                (num, _) = e3.args
+                self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
 
     def test_primary_group_token_constructed(self):
         """Test the primary group token behaviour (hidden-generated-readonly attribute on groups) and some other constructed attributes"""
-        print "Testing primary group token behaviour and other constructed attributes\n"
+        print("Testing primary group token behaviour and other constructed attributes\n")
 
         try:
             ldb.add({
@@ -639,13 +779,14 @@ class SamTests(unittest.TestCase):
                 "objectclass": "group",
                 "primaryGroupToken": "100"})
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNDEFINED_ATTRIBUTE_TYPE)
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        except LdbError as e35:
+            (num, _) = e35.args
+            self.assertEqual(num, ERR_UNDEFINED_ATTRIBUTE_TYPE)
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
 
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["user", "person"]})
+            "objectclass": "user"})
 
         ldb.add({
             "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
@@ -688,8 +829,9 @@ class SamTests(unittest.TestCase):
         self.assertTrue(len(res1) == 1)
         primary_group_token = int(res1[0]["primaryGroupToken"][0])
 
-        rid = security.dom_sid(ldb.schema_format_value("objectSID", res1[0]["objectSID"][0])).split()[1]
-        self.assertEquals(primary_group_token, rid)
+        obj_sid = get_string(ldb.schema_format_value("objectSID", res1[0]["objectSID"][0]))
+        rid = security.dom_sid(obj_sid).split()[1]
+        self.assertEqual(primary_group_token, rid)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
@@ -697,15 +839,16 @@ class SamTests(unittest.TestCase):
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_CONSTRAINT_VIOLATION)
+        except LdbError as e36:
+            (num, _) = e36.args
+            self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
 
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
 
     def test_tokenGroups(self):
         """Test the tokenGroups behaviour (hidden-generated-readonly attribute on SAM objects)"""
-        print "Testing tokenGroups behaviour\n"
+        print("Testing tokenGroups behaviour\n")
 
         # The domain object shouldn't contain any "tokenGroups" entry
         res = ldb.search(self.base_dn, scope=SCOPE_BASE, attrs=["tokenGroups"])
@@ -722,7 +865,7 @@ class SamTests(unittest.TestCase):
 
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["user", "person"]})
+            "objectclass": "user"})
 
         # This testuser should contain at least two "tokenGroups" entries
         # (exactly two on an unmodified "Domain Users" and "Users" group)
@@ -736,7 +879,8 @@ class SamTests(unittest.TestCase):
         domain_users_group_found = False
         users_group_found = False
         for sid in res[0]["tokenGroups"]:
-            rid = security.dom_sid(ldb.schema_format_value("objectSID", sid)).split()[1]
+            obj_sid = get_string(ldb.schema_format_value("objectSID", sid))
+            rid = security.dom_sid(obj_sid).split()[1]
             if rid == 513:
                 domain_users_group_found = True
             if rid == 545:
@@ -745,11 +889,11 @@ class SamTests(unittest.TestCase):
         self.assertTrue(domain_users_group_found)
         self.assertTrue(users_group_found)
 
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
     def test_groupType(self):
         """Test the groupType behaviour"""
-        print "Testing groupType behaviour\n"
+        print("Testing groupType behaviour\n")
 
         # You can never create or change to a
         # "GTYPE_SECURITY_BUILTIN_LOCAL_GROUP"
@@ -763,9 +907,10 @@ class SamTests(unittest.TestCase):
                 "objectclass": "group",
                 "groupType": "0"})
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        except LdbError as e37:
+            (num, _) = e37.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
 
         try:
             ldb.add({
@@ -773,9 +918,10 @@ class SamTests(unittest.TestCase):
                 "objectclass": "group",
                 "groupType": str(GTYPE_SECURITY_BUILTIN_LOCAL_GROUP)})
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        except LdbError as e38:
+            (num, _) = e38.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
 
         ldb.add({
             "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
@@ -785,9 +931,9 @@ class SamTests(unittest.TestCase):
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_GLOBAL_GROUP)
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_GLOBAL_GROUP)
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
 
         ldb.add({
             "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
@@ -797,9 +943,9 @@ class SamTests(unittest.TestCase):
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_UNIVERSAL_GROUP)
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_UNIVERSAL_GROUP)
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
 
         ldb.add({
             "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
@@ -809,9 +955,9 @@ class SamTests(unittest.TestCase):
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_LOCAL_GROUP)
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_LOCAL_GROUP)
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
 
         ldb.add({
             "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
@@ -821,9 +967,9 @@ class SamTests(unittest.TestCase):
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_DISTRIBUTION_GLOBAL_GROUP)
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_DISTRIBUTION_GLOBAL_GROUP)
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
 
         ldb.add({
             "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
@@ -833,9 +979,9 @@ class SamTests(unittest.TestCase):
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_DISTRIBUTION_UNIVERSAL_GROUP)
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_DISTRIBUTION_UNIVERSAL_GROUP)
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
 
         ldb.add({
             "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
@@ -845,9 +991,9 @@ class SamTests(unittest.TestCase):
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_DISTRIBUTION_LOCAL_GROUP)
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_DISTRIBUTION_LOCAL_GROUP)
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
 
         # Modify operation
 
@@ -863,19 +1009,20 @@ class SamTests(unittest.TestCase):
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_GLOBAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_GLOBAL_GROUP)
 
         # Invalid attribute
         try:
             m = Message()
             m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
             m["groupType"] = MessageElement("0",
-              FLAG_MOD_REPLACE, "groupType")
+                                            FLAG_MOD_REPLACE, "groupType")
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e39:
+            (num, _) = e39.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Security groups
 
@@ -884,15 +1031,15 @@ class SamTests(unittest.TestCase):
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-          str(GTYPE_SECURITY_GLOBAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_SECURITY_GLOBAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_GLOBAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_GLOBAL_GROUP)
 
         # Change to "local" (shouldn't work)
 
@@ -900,72 +1047,73 @@ class SamTests(unittest.TestCase):
             m = Message()
             m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
             m["groupType"] = MessageElement(
-              str(GTYPE_SECURITY_DOMAIN_LOCAL_GROUP),
-              FLAG_MOD_REPLACE, "groupType")
+                str(GTYPE_SECURITY_DOMAIN_LOCAL_GROUP),
+                FLAG_MOD_REPLACE, "groupType")
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e40:
+            (num, _) = e40.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Change to "universal"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-         str(GTYPE_SECURITY_UNIVERSAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_SECURITY_UNIVERSAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_UNIVERSAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_UNIVERSAL_GROUP)
 
         # Change back to "global"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-          str(GTYPE_SECURITY_GLOBAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_SECURITY_GLOBAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_GLOBAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_GLOBAL_GROUP)
 
         # Change back to "universal"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-         str(GTYPE_SECURITY_UNIVERSAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_SECURITY_UNIVERSAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_UNIVERSAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_UNIVERSAL_GROUP)
 
         # Change to "local"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-          str(GTYPE_SECURITY_DOMAIN_LOCAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_SECURITY_DOMAIN_LOCAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_LOCAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_LOCAL_GROUP)
 
         # Change to "global" (shouldn't work)
 
@@ -973,12 +1121,13 @@ class SamTests(unittest.TestCase):
             m = Message()
             m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
             m["groupType"] = MessageElement(
-              str(GTYPE_SECURITY_GLOBAL_GROUP),
-              FLAG_MOD_REPLACE, "groupType")
+                str(GTYPE_SECURITY_GLOBAL_GROUP),
+                FLAG_MOD_REPLACE, "groupType")
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e41:
+            (num, _) = e41.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Change to "builtin local" (shouldn't work)
 
@@ -986,12 +1135,13 @@ class SamTests(unittest.TestCase):
             m = Message()
             m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
             m["groupType"] = MessageElement(
-              str(GTYPE_SECURITY_BUILTIN_LOCAL_GROUP),
-              FLAG_MOD_REPLACE, "groupType")
+                str(GTYPE_SECURITY_BUILTIN_LOCAL_GROUP),
+                FLAG_MOD_REPLACE, "groupType")
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e42:
+            (num, _) = e42.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
@@ -1001,15 +1151,15 @@ class SamTests(unittest.TestCase):
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-         str(GTYPE_SECURITY_UNIVERSAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_SECURITY_UNIVERSAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_UNIVERSAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_UNIVERSAL_GROUP)
 
         # Change to "builtin local" (shouldn't work)
 
@@ -1017,27 +1167,28 @@ class SamTests(unittest.TestCase):
             m = Message()
             m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
             m["groupType"] = MessageElement(
-              str(GTYPE_SECURITY_BUILTIN_LOCAL_GROUP),
-              FLAG_MOD_REPLACE, "groupType")
+                str(GTYPE_SECURITY_BUILTIN_LOCAL_GROUP),
+                FLAG_MOD_REPLACE, "groupType")
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e43:
+            (num, _) = e43.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Change back to "global"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-          str(GTYPE_SECURITY_GLOBAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_SECURITY_GLOBAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_GLOBAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_GLOBAL_GROUP)
 
         # Change to "builtin local" (shouldn't work)
 
@@ -1045,12 +1196,13 @@ class SamTests(unittest.TestCase):
             m = Message()
             m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
             m["groupType"] = MessageElement(
-              str(GTYPE_SECURITY_BUILTIN_LOCAL_GROUP),
-              FLAG_MOD_REPLACE, "groupType")
+                str(GTYPE_SECURITY_BUILTIN_LOCAL_GROUP),
+                FLAG_MOD_REPLACE, "groupType")
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e44:
+            (num, _) = e44.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Distribution groups
 
@@ -1059,15 +1211,15 @@ class SamTests(unittest.TestCase):
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-          str(GTYPE_DISTRIBUTION_GLOBAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_DISTRIBUTION_GLOBAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_DISTRIBUTION_GLOBAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_DISTRIBUTION_GLOBAL_GROUP)
 
         # Change to local (shouldn't work)
 
@@ -1075,72 +1227,73 @@ class SamTests(unittest.TestCase):
             m = Message()
             m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
             m["groupType"] = MessageElement(
-              str(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP),
-              FLAG_MOD_REPLACE, "groupType")
+                str(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP),
+                FLAG_MOD_REPLACE, "groupType")
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e45:
+            (num, _) = e45.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Change to "universal"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-         str(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_DISTRIBUTION_UNIVERSAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_DISTRIBUTION_UNIVERSAL_GROUP)
 
         # Change back to "global"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-          str(GTYPE_DISTRIBUTION_GLOBAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_DISTRIBUTION_GLOBAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_DISTRIBUTION_GLOBAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_DISTRIBUTION_GLOBAL_GROUP)
 
         # Change back to "universal"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-         str(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_DISTRIBUTION_UNIVERSAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_DISTRIBUTION_UNIVERSAL_GROUP)
 
         # Change to "local"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-          str(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_DISTRIBUTION_LOCAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_DISTRIBUTION_LOCAL_GROUP)
 
         # Change to "global" (shouldn't work)
 
@@ -1148,12 +1301,13 @@ class SamTests(unittest.TestCase):
             m = Message()
             m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
             m["groupType"] = MessageElement(
-              str(GTYPE_DISTRIBUTION_GLOBAL_GROUP),
-              FLAG_MOD_REPLACE, "groupType")
+                str(GTYPE_DISTRIBUTION_GLOBAL_GROUP),
+                FLAG_MOD_REPLACE, "groupType")
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e46:
+            (num, _) = e46.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Change back to "universal"
 
@@ -1161,42 +1315,43 @@ class SamTests(unittest.TestCase):
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["member"] = MessageElement(
-          "cn=ldaptestuser3,cn=users," + self.base_dn,
-          FLAG_MOD_ADD, "member")
+            "cn=ldaptestuser3,cn=users," + self.base_dn,
+            FLAG_MOD_ADD, "member")
         try:
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_NO_SUCH_OBJECT)
+        except LdbError as e47:
+            (num, _) = e47.args
+            self.assertEqual(num, ERR_NO_SUCH_OBJECT)
 
         # Make group 2 secondary
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-         str(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_DISTRIBUTION_UNIVERSAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_DISTRIBUTION_UNIVERSAL_GROUP)
 
         # Change back to "global"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-          str(GTYPE_DISTRIBUTION_GLOBAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_DISTRIBUTION_GLOBAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_DISTRIBUTION_GLOBAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_DISTRIBUTION_GLOBAL_GROUP)
 
         # Both group types: this performs only random checks - all possibilities
         # would require too much code.
@@ -1206,15 +1361,15 @@ class SamTests(unittest.TestCase):
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-          str(GTYPE_SECURITY_GLOBAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_SECURITY_GLOBAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_GLOBAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_GLOBAL_GROUP)
 
         # Change to "local" (shouldn't work)
 
@@ -1222,72 +1377,73 @@ class SamTests(unittest.TestCase):
             m = Message()
             m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
             m["groupType"] = MessageElement(
-              str(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP),
-              FLAG_MOD_REPLACE, "groupType")
+                str(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP),
+                FLAG_MOD_REPLACE, "groupType")
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e48:
+            (num, _) = e48.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Change to "universal"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-         str(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_DISTRIBUTION_UNIVERSAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_DISTRIBUTION_UNIVERSAL_GROUP)
 
         # Change back to "global"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-          str(GTYPE_SECURITY_GLOBAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_SECURITY_GLOBAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_GLOBAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_GLOBAL_GROUP)
 
         # Change back to "universal"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-         str(GTYPE_SECURITY_UNIVERSAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_SECURITY_UNIVERSAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_UNIVERSAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_UNIVERSAL_GROUP)
 
         # Change to "local"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-          str(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_DISTRIBUTION_LOCAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_DISTRIBUTION_LOCAL_GROUP)
 
         # Change to "global" (shouldn't work)
 
@@ -1295,348 +1451,577 @@ class SamTests(unittest.TestCase):
             m = Message()
             m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
             m["groupType"] = MessageElement(
-              str(GTYPE_DISTRIBUTION_GLOBAL_GROUP),
-              FLAG_MOD_REPLACE, "groupType")
+                str(GTYPE_DISTRIBUTION_GLOBAL_GROUP),
+                FLAG_MOD_REPLACE, "groupType")
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        except LdbError as e49:
+            (num, _) = e49.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         # Change back to "universal"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-         str(GTYPE_SECURITY_UNIVERSAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_SECURITY_UNIVERSAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_UNIVERSAL_GROUP)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_UNIVERSAL_GROUP)
 
         # Change back to "global"
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
         m["groupType"] = MessageElement(
-          str(GTYPE_SECURITY_GLOBAL_GROUP),
-          FLAG_MOD_REPLACE, "groupType")
+            str(GTYPE_SECURITY_GLOBAL_GROUP),
+            FLAG_MOD_REPLACE, "groupType")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_SECURITY_GLOBAL_GROUP)
-
-        self.delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_SECURITY_GLOBAL_GROUP)
 
-    def test_userAccountControl(self):
-        """Test the userAccountControl behaviour"""
-        print "Testing userAccountControl behaviour\n"
-
-        # With a user object
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
 
-        # Add operation
+    def test_pwdLastSet(self):
+        """Test the pwdLastSet behaviour"""
+        print("Testing pwdLastSet behaviour\n")
 
-        # As user you can only set a normal account.
-        # The UF_PASSWD_NOTREQD flag is needed since we haven't requested a
-        # password yet.
-        # With SYSTEM rights you can set a interdomain trust account.
+        ldb.add({
+            "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+            "objectclass": "user",
+            "pwdLastSet": "0"})
 
-        # Invalid attribute
-        try:
-            ldb.add({
-                "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-                "objectclass": ["user", "person"],
-                "userAccountControl": "0"})
-            self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-
-# This has to wait until s4 supports it (needs a password module change)
-#        try:
-#            ldb.add({
-#                "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-#                "objectclass": ["user", "person"],
-#                "userAccountControl": str(UF_NORMAL_ACCOUNT)})
-#            self.fail()
-#        except LdbError, (num, _):
-#            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
-#        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl", "pwdLastSet"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res1[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD)
+        self.assertEqual(int(res1[0]["pwdLastSet"][0]), 0)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["user", "person"],
-            "userAccountControl": str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD)})
+            "objectclass": "user",
+            "pwdLastSet": "-1"})
 
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
-                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl", "pwdLastSet"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_NORMAL_ACCOUNT)
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res1[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD)
+        self.assertNotEqual(int(res1[0]["pwdLastSet"][0]), 0)
+        lastset = int(res1[0]["pwdLastSet"][0])
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         try:
             ldb.add({
                 "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-                "objectclass": ["user", "person"],
-                "userAccountControl": str(UF_TEMP_DUPLICATE_ACCOUNT)})
+                "objectclass": "user",
+                "pwdLastSet": str(1)})
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_OTHER)
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-
-# This isn't supported yet in s4
-#        try:
-#            ldb.add({
-#                "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-#                "objectclass": ["user", "person"],
-#                "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT)})
-#            self.fail()
-#        except LdbError, (num, _):
-#            self.assertEquals(num, ERR_OBJECT_CLASS_VIOLATION)
-#        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-#
-#        try:
-#            ldb.add({
-#                "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-#                "objectclass": ["user", "person"],
-#                "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT)})
-#        except LdbError, (num, _):
-#            self.assertEquals(num, ERR_OBJECT_CLASS_VIOLATION)
-#        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-
-# This isn't supported yet in s4 - needs ACL module adaption
-#        try:
-#            ldb.add({
-#                "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-#                "objectclass": ["user", "person"],
-#                "userAccountControl": str(UF_INTERDOMAIN_TRUST_ACCOUNT)})
-#            self.fail()
-#        except LdbError, (num, _):
-#            self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
-#        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        except LdbError as e50:
+            (num, msg) = e50.args
+            self.assertEqual(num, ERR_OTHER)
+            self.assertTrue('00000057' in msg)
 
-        # Modify operation
+        try:
+            ldb.add({
+                "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+                "objectclass": "user",
+                "pwdLastSet": str(lastset)})
+            self.fail()
+        except LdbError as e51:
+            (num, msg) = e51.args
+            self.assertEqual(num, ERR_OTHER)
+            self.assertTrue('00000057' in msg)
 
         ldb.add({
             "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
-            "objectclass": ["user", "person"]})
+            "objectclass": "user"})
 
-        # After creation we should have a normal account
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
-                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl", "pwdLastSet"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res1[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD)
+        self.assertEqual(int(res1[0]["pwdLastSet"][0]), 0)
 
-        # As user you can only switch from a normal account to a workstation
-        # trust account and back.
-        # The UF_PASSWD_NOTREQD flag is needed since we haven't requested a
-        # password yet.
-        # With SYSTEM rights you can switch to a interdomain trust account.
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["pls1"] = MessageElement(str(0),
+                                   FLAG_MOD_REPLACE,
+                                   "pwdLastSet")
+        ldb.modify(m)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["pls1"] = MessageElement(str(0),
+                                   FLAG_MOD_DELETE,
+                                   "pwdLastSet")
+        m["pls2"] = MessageElement(str(0),
+                                   FLAG_MOD_ADD,
+                                   "pwdLastSet")
+        ldb.modify(m)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["pls1"] = MessageElement(str(-1),
+                                   FLAG_MOD_REPLACE,
+                                   "pwdLastSet")
+        ldb.modify(m)
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl", "pwdLastSet"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res1[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD)
+        self.assertGreater(int(res1[0]["pwdLastSet"][0]), lastset)
+        lastset = int(res1[0]["pwdLastSet"][0])
 
-        # Invalid attribute
         try:
             m = Message()
             m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-            m["userAccountControl"] = MessageElement("0",
-              FLAG_MOD_REPLACE, "userAccountControl")
+            m["pls1"] = MessageElement(str(0),
+                                       FLAG_MOD_DELETE,
+                                       "pwdLastSet")
+            m["pls2"] = MessageElement(str(0),
+                                       FLAG_MOD_ADD,
+                                       "pwdLastSet")
             ldb.modify(m)
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+            self.fail()
+        except LdbError as e52:
+            (num, msg) = e52.args
+            self.assertEqual(num, ERR_NO_SUCH_ATTRIBUTE)
+            self.assertTrue('00002085' in msg)
 
-# This has to wait until s4 supports it (needs a password module change)
-#        try:
-#            m = Message()
-#            m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-#            m["userAccountControl"] = MessageElement(
-#              str(UF_NORMAL_ACCOUNT),
-#              FLAG_MOD_REPLACE, "userAccountControl")
-#            ldb.modify(m)
-#        except LdbError, (num, _):
-#            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        try:
+            m = Message()
+            m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+            m["pls1"] = MessageElement(str(-1),
+                                       FLAG_MOD_DELETE,
+                                       "pwdLastSet")
+            m["pls2"] = MessageElement(str(0),
+                                       FLAG_MOD_ADD,
+                                       "pwdLastSet")
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e53:
+            (num, msg) = e53.args
+            self.assertEqual(num, ERR_NO_SUCH_ATTRIBUTE)
+            self.assertTrue('00002085' in msg)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        m["userAccountControl"] = MessageElement(
-          str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD),
-          FLAG_MOD_REPLACE, "userAccountControl")
+        m["pls1"] = MessageElement(str(lastset),
+                                   FLAG_MOD_DELETE,
+                                   "pwdLastSet")
+        m["pls2"] = MessageElement(str(-1),
+                                   FLAG_MOD_ADD,
+                                   "pwdLastSet")
+        time.sleep(0.2)
         ldb.modify(m)
-
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
-                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl", "pwdLastSet"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res1[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD)
+        self.assertEqual(int(res1[0]["pwdLastSet"][0]), lastset)
 
         try:
             m = Message()
             m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-            m["userAccountControl"] = MessageElement(
-              str(UF_TEMP_DUPLICATE_ACCOUNT),
-              FLAG_MOD_REPLACE, "userAccountControl")
+            m["pls1"] = MessageElement(str(lastset),
+                                       FLAG_MOD_DELETE,
+                                       "pwdLastSet")
+            m["pls2"] = MessageElement(str(lastset),
+                                       FLAG_MOD_ADD,
+                                       "pwdLastSet")
             ldb.modify(m)
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_OTHER)
+        except LdbError as e54:
+            (num, msg) = e54.args
+            self.assertEqual(num, ERR_OTHER)
+            self.assertTrue('00000057' in msg)
 
-# This isn't supported yet in s4
-#        try:
-#            m = Message()
-#            m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-#            m["userAccountControl"] = MessageElement(
-#              str(UF_SERVER_TRUST_ACCOUNT),
-#              FLAG_MOD_REPLACE, "userAccountControl")
-#            ldb.modify(m)
-#            self.fail()
-#        except LdbError, (num, _):
-#            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["pls1"] = MessageElement(str(lastset),
+                                   FLAG_MOD_DELETE,
+                                   "pwdLastSet")
+        m["pls2"] = MessageElement(str(0),
+                                   FLAG_MOD_ADD,
+                                   "pwdLastSet")
+        ldb.modify(m)
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl", "pwdLastSet"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res1[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD)
+        uac = int(res1[0]["userAccountControl"][0])
+        self.assertEqual(int(res1[0]["pwdLastSet"][0]), 0)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        m["userAccountControl"] = MessageElement(
-          str(UF_WORKSTATION_TRUST_ACCOUNT),
-          FLAG_MOD_REPLACE, "userAccountControl")
+        m["uac1"] = MessageElement(str(uac |UF_PASSWORD_EXPIRED),
+                                   FLAG_MOD_REPLACE,
+                                   "userAccountControl")
         ldb.modify(m)
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl", "pwdLastSet"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res1[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE | UF_PASSWD_NOTREQD)
+        self.assertEqual(int(res1[0]["pwdLastSet"][0]), 0)
+
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+    def test_ldap_bind_must_change_pwd(self):
+        """Test the error messages for failing LDAP binds"""
+        print("Test the error messages for failing LDAP binds\n")
+
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+        def format_error_msg(hresult_v, dsid_v, werror_v):
+            #
+            # There are 4 lower case hex digits following 'v' at the end,
+            # but different Windows Versions return different values:
+            #
+            # Windows 2008R2 uses 'v1db1'
+            # Windows 2012R2 uses 'v2580'
+            #
+            return "%08X: LdapErr: DSID-%08X, comment: AcceptSecurityContext error, data %x, v" % (
+                    hresult_v, dsid_v, werror_v)
+
+        HRES_SEC_E_LOGON_DENIED = 0x8009030C
+        HRES_SEC_E_INVALID_TOKEN = 0x80090308
+
+        sasl_bind_dsid = 0x0C0904DC
+        simple_bind_dsid = 0x0C0903A9
+
+        error_msg_sasl_wrong_pw = format_error_msg(
+                                HRES_SEC_E_LOGON_DENIED,
+                                sasl_bind_dsid,
+                                werror.WERR_LOGON_FAILURE)
+        error_msg_sasl_must_change = format_error_msg(
+                                HRES_SEC_E_LOGON_DENIED,
+                                sasl_bind_dsid,
+                                werror.WERR_PASSWORD_MUST_CHANGE)
+        error_msg_simple_wrong_pw = format_error_msg(
+                                HRES_SEC_E_INVALID_TOKEN,
+                                simple_bind_dsid,
+                                werror.WERR_LOGON_FAILURE)
+        error_msg_simple_must_change = format_error_msg(
+                                HRES_SEC_E_INVALID_TOKEN,
+                                simple_bind_dsid,
+                                werror.WERR_PASSWORD_MUST_CHANGE)
+
+        username = "ldaptestuser"
+        password = "thatsAcomplPASS2"
+        utf16pw = ('"' + password + '"').encode('utf-16-le')
+
+        ldb.add({
+            "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+            "objectclass": "user",
+            "sAMAccountName": username,
+            "userAccountControl": str(UF_NORMAL_ACCOUNT),
+            "unicodePwd": utf16pw,
+        })
 
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
-                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountName", "sAMAccountType", "userAccountControl", "pwdLastSet"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_WORKSTATION_TRUST)
+        self.assertEqual(str(res1[0]["sAMAccountName"][0]), username)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]), ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res1[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT)
+        self.assertNotEqual(int(res1[0]["pwdLastSet"][0]), 0)
+
+        # Open a second LDB connection with the user credentials. Use the
+        # command line credentials for information like the domain, the realm
+        # and the workstation.
+        sasl_creds = Credentials()
+        sasl_creds.set_username(username)
+        sasl_creds.set_password(password)
+        sasl_creds.set_domain(creds.get_domain())
+        sasl_creds.set_workstation(creds.get_workstation())
+        sasl_creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
+        sasl_creds.set_kerberos_state(DONT_USE_KERBEROS)
+
+        sasl_wrong_creds = Credentials()
+        sasl_wrong_creds.set_username(username)
+        sasl_wrong_creds.set_password("wrong")
+        sasl_wrong_creds.set_domain(creds.get_domain())
+        sasl_wrong_creds.set_workstation(creds.get_workstation())
+        sasl_wrong_creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
+        sasl_wrong_creds.set_kerberos_state(DONT_USE_KERBEROS)
+
+        simple_creds = Credentials()
+        simple_creds.set_bind_dn("cn=ldaptestuser,cn=users," + self.base_dn)
+        simple_creds.set_username(username)
+        simple_creds.set_password(password)
+        simple_creds.set_domain(creds.get_domain())
+        simple_creds.set_workstation(creds.get_workstation())
+        simple_creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
+        simple_creds.set_kerberos_state(DONT_USE_KERBEROS)
+
+        simple_wrong_creds = Credentials()
+        simple_wrong_creds.set_bind_dn("cn=ldaptestuser,cn=users," + self.base_dn)
+        simple_wrong_creds.set_username(username)
+        simple_wrong_creds.set_password("wrong")
+        simple_wrong_creds.set_domain(creds.get_domain())
+        simple_wrong_creds.set_workstation(creds.get_workstation())
+        simple_wrong_creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL)
+        simple_wrong_creds.set_kerberos_state(DONT_USE_KERBEROS)
+
+        sasl_ldb = SamDB(url=host, credentials=sasl_creds, lp=lp)
+        self.assertIsNotNone(sasl_ldb)
+        sasl_ldb = None
+
+        requires_strong_auth = False
+        try:
+            simple_ldb = SamDB(url=host, credentials=simple_creds, lp=lp)
+            self.assertIsNotNone(simple_ldb)
+            simple_ldb = None
+        except LdbError as e55:
+            (num, msg) = e55.args
+            if num != ERR_STRONG_AUTH_REQUIRED:
+                raise
+            requires_strong_auth = True
+
+        def assertLDAPErrorMsg(msg, expected_msg):
+            self.assertTrue(expected_msg in msg,
+                            "msg[%s] does not contain expected[%s]" % (
+                                msg, expected_msg))
+
+        try:
+            ldb_fail = SamDB(url=host, credentials=sasl_wrong_creds, lp=lp)
+            self.fail()
+        except LdbError as e56:
+            (num, msg) = e56.args
+            self.assertEqual(num, ERR_INVALID_CREDENTIALS)
+            self.assertTrue(error_msg_sasl_wrong_pw in msg)
+
+        if not requires_strong_auth:
+            try:
+                ldb_fail = SamDB(url=host, credentials=simple_wrong_creds, lp=lp)
+                self.fail()
+            except LdbError as e4:
+                (num, msg) = e4.args
+                self.assertEqual(num, ERR_INVALID_CREDENTIALS)
+                assertLDAPErrorMsg(msg, error_msg_simple_wrong_pw)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        m["userAccountControl"] = MessageElement(
-          str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD),
-          FLAG_MOD_REPLACE, "userAccountControl")
+        m["pls1"] = MessageElement(str(0),
+                                   FLAG_MOD_REPLACE,
+                                   "pwdLastSet")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
-                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
-        self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_NORMAL_ACCOUNT)
-
-# This isn't supported yet in s4 - needs ACL module adaption
-#        try:
-#            m = Message()
-#            m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-#            m["userAccountControl"] = MessageElement(
-#              str(UF_INTERDOMAIN_TRUST_ACCOUNT),
-#              FLAG_MOD_REPLACE, "userAccountControl")
-#            ldb.modify(m)
-#            self.fail()
-#        except LdbError, (num, _):
-#            self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
+                          scope=SCOPE_BASE, attrs=["pwdLastSet"])
+        self.assertEqual(int(res1[0]["pwdLastSet"][0]), 0)
 
-        # With a computer object
+        try:
+            ldb_fail = SamDB(url=host, credentials=sasl_wrong_creds, lp=lp)
+            self.fail()
+        except LdbError as e57:
+            (num, msg) = e57.args
+            self.assertEqual(num, ERR_INVALID_CREDENTIALS)
+            assertLDAPErrorMsg(msg, error_msg_sasl_wrong_pw)
+
+        try:
+            ldb_fail = SamDB(url=host, credentials=sasl_creds, lp=lp)
+            self.fail()
+        except LdbError as e58:
+            (num, msg) = e58.args
+            self.assertEqual(num, ERR_INVALID_CREDENTIALS)
+            assertLDAPErrorMsg(msg, error_msg_sasl_must_change)
+
+        if not requires_strong_auth:
+            try:
+                ldb_fail = SamDB(url=host, credentials=simple_wrong_creds, lp=lp)
+                self.fail()
+            except LdbError as e5:
+                (num, msg) = e5.args
+                self.assertEqual(num, ERR_INVALID_CREDENTIALS)
+                assertLDAPErrorMsg(msg, error_msg_simple_wrong_pw)
+
+            try:
+                ldb_fail = SamDB(url=host, credentials=simple_creds, lp=lp)
+                self.fail()
+            except LdbError as e6:
+                (num, msg) = e6.args
+                self.assertEqual(num, ERR_INVALID_CREDENTIALS)
+                assertLDAPErrorMsg(msg, error_msg_simple_must_change)
+
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+    def test_userAccountControl(self):
+        """Test the userAccountControl behaviour"""
+        print("Testing userAccountControl behaviour\n")
+
+        # With a user object
 
         # Add operation
 
-        # As computer you can set a normal account and a server trust account.
+        # As user you can only set a normal account.
         # The UF_PASSWD_NOTREQD flag is needed since we haven't requested a
         # password yet.
         # With SYSTEM rights you can set a interdomain trust account.
 
-        # Invalid attribute
-        try:
-            ldb.add({
-                "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
-                "objectclass": ["computer"],
-                "userAccountControl": "0"})
-            self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
-        self.delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
-
-# This has to wait until s4 supports it (needs a password module change)
-#        try:
-#            ldb.add({
-#                "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
-#                "objectclass": ["computer"],
-#                "userAccountControl": str(UF_NORMAL_ACCOUNT)})
-#            self.fail()
-#        except LdbError, (num, _):
-#            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
-#        self.delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        ldb.add({
+            "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+            "objectclass": "user",
+            "userAccountControl": "0"})
+
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE == 0)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_PASSWD_NOTREQD == 0)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         ldb.add({
-            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
-            "objectclass": ["computer"],
+            "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+            "objectclass": "user",
+            "userAccountControl": str(UF_NORMAL_ACCOUNT)})
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+            "objectclass": "user",
             "userAccountControl": str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD)})
 
-        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
-                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE == 0)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+            "objectclass": "user",
+            "userAccountControl": str(UF_NORMAL_ACCOUNT |
+                                      UF_PASSWD_NOTREQD |
+                                      UF_LOCKOUT |
+                                      UF_PASSWORD_EXPIRED)})
+
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl", "lockoutTime", "pwdLastSet"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_NORMAL_ACCOUNT)
-        self.delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & (UF_LOCKOUT | UF_PASSWORD_EXPIRED) == 0)
+        self.assertFalse("lockoutTime" in res1[0])
+        self.assertTrue(int(res1[0]["pwdLastSet"][0]) == 0)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         try:
             ldb.add({
-                "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
-                "objectclass": ["computer"],
+                "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+                "objectclass": "user",
                 "userAccountControl": str(UF_TEMP_DUPLICATE_ACCOUNT)})
             self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_OTHER)
-        self.delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
-
-        ldb.add({
-            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
-            "objectclass": ["computer"],
-            "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT)})
+        except LdbError as e59:
+            (num, _) = e59.args
+            self.assertEqual(num, ERR_OTHER)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
-        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
-                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
-        self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_WORKSTATION_TRUST)
-        self.delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        try:
+            ldb.add({
+                "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+                "objectclass": "user",
+                "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT)})
+            self.fail()
+        except LdbError as e60:
+            (num, _) = e60.args
+            self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         try:
             ldb.add({
-                "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
-                "objectclass": ["computer"],
+                "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+                "objectclass": "user",
                 "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT)})
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_OBJECT_CLASS_VIOLATION)
-        self.delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
-
-# This isn't supported yet in s4 - needs ACL module adaption
-#        try:
-#            ldb.add({
-#                "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
-#                "objectclass": ["computer"],
-#                "userAccountControl": str(UF_INTERDOMAIN_TRUST_ACCOUNT)})
-#            self.fail()
-#        except LdbError, (num, _):
-#            self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
-#        self.delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        except LdbError as e61:
+            (num, _) = e61.args
+            self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+        try:
+            ldb.add({
+                "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+                "objectclass": "user",
+                "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)})
+        except LdbError as e62:
+            (num, _) = e62.args
+            self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+        try:
+            ldb.add({
+                "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+                "objectclass": "user",
+                "userAccountControl": str(UF_INTERDOMAIN_TRUST_ACCOUNT)})
+            self.fail()
+        except LdbError as e63:
+            (num, _) = e63.args
+            self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
 
         # Modify operation
 
         ldb.add({
-            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
-            "objectclass": ["computer"]})
+            "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+            "objectclass": "user"})
 
         # After creation we should have a normal account
-        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
-                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE != 0)
 
-        # As computer you can switch from a normal account to a workstation
-        # or server trust account and back (also swapping between trust
-        # accounts is allowed).
+        # As user you can only switch from a normal account to a workstation
+        # trust account and back.
         # The UF_PASSWD_NOTREQD flag is needed since we haven't requested a
         # password yet.
         # With SYSTEM rights you can switch to a interdomain trust account.
@@ -1644,156 +2029,1773 @@ class SamTests(unittest.TestCase):
         # Invalid attribute
         try:
             m = Message()
-            m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+            m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
             m["userAccountControl"] = MessageElement("0",
-              FLAG_MOD_REPLACE, "userAccountControl")
+                                                     FLAG_MOD_REPLACE, "userAccountControl")
             ldb.modify(m)
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
-
-# This has to wait until s4 supports it (needs a password module change)
-#        try:
-#            m = Message()
-#            m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
-#            m["userAccountControl"] = MessageElement(
-#              str(UF_NORMAL_ACCOUNT),
-#              FLAG_MOD_REPLACE, "userAccountControl")
-#            ldb.modify(m)
-#        except LdbError, (num, _):
-#            self.assertEquals(num, ERR_UNWILLING_TO_PERFORM)
-
-        m = Message()
-        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
-        m["userAccountControl"] = MessageElement(
-          str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD),
-          FLAG_MOD_REPLACE, "userAccountControl")
-        ldb.modify(m)
-
-        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
-                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
-        self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_NORMAL_ACCOUNT)
+        except LdbError as e64:
+            (num, _) = e64.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         try:
             m = Message()
-            m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+            m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
             m["userAccountControl"] = MessageElement(
-              str(UF_TEMP_DUPLICATE_ACCOUNT),
-              FLAG_MOD_REPLACE, "userAccountControl")
+                str(UF_NORMAL_ACCOUNT),
+                FLAG_MOD_REPLACE, "userAccountControl")
             ldb.modify(m)
-            self.fail()
-        except LdbError, (num, _):
-            self.assertEquals(num, ERR_OTHER)
+        except LdbError as e65:
+            (num, _) = e65.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
 
         m = Message()
-        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
         m["userAccountControl"] = MessageElement(
-          str(UF_SERVER_TRUST_ACCOUNT),
-          FLAG_MOD_REPLACE, "userAccountControl")
+            str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD),
+            FLAG_MOD_REPLACE, "userAccountControl")
         ldb.modify(m)
 
-        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
-                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_WORKSTATION_TRUST)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE == 0)
 
         m = Message()
-        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
         m["userAccountControl"] = MessageElement(
-          str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD),
-          FLAG_MOD_REPLACE, "userAccountControl")
+            str(UF_ACCOUNTDISABLE),
+            FLAG_MOD_REPLACE, "userAccountControl")
         ldb.modify(m)
 
-        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
-                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_NORMAL_ACCOUNT != 0)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE != 0)
 
         m = Message()
-        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
-        m["userAccountControl"] = MessageElement(
-          str(UF_WORKSTATION_TRUST_ACCOUNT),
-          FLAG_MOD_REPLACE, "userAccountControl")
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["lockoutTime"] = MessageElement(str(samba.unix2nttime(0)), FLAG_MOD_REPLACE, "lockoutTime")
+        m["pwdLastSet"] = MessageElement(str(samba.unix2nttime(0)), FLAG_MOD_REPLACE, "pwdLastSet")
         ldb.modify(m)
 
-        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_LOCKOUT | UF_PASSWORD_EXPIRED),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl", "lockoutTime", "pwdLastSet"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_NORMAL_ACCOUNT != 0)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & (UF_LOCKOUT | UF_PASSWORD_EXPIRED) == 0)
+        self.assertTrue(int(res1[0]["lockoutTime"][0]) == 0)
+        self.assertTrue(int(res1[0]["pwdLastSet"][0]) == 0)
+
+        try:
+            m = Message()
+            m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+            m["userAccountControl"] = MessageElement(
+                str(UF_TEMP_DUPLICATE_ACCOUNT),
+                FLAG_MOD_REPLACE, "userAccountControl")
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e66:
+            (num, _) = e66.args
+            self.assertEqual(num, ERR_OTHER)
+
+        try:
+            m = Message()
+            m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+            m["userAccountControl"] = MessageElement(
+                str(UF_SERVER_TRUST_ACCOUNT),
+                FLAG_MOD_REPLACE, "userAccountControl")
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e67:
+            (num, _) = e67.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_WORKSTATION_TRUST_ACCOUNT),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        try:
+            m = Message()
+            m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+            m["userAccountControl"] = MessageElement(
+                str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT),
+                FLAG_MOD_REPLACE, "userAccountControl")
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e68:
+            (num, _) = e68.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_WORKSTATION_TRUST)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+
+        try:
+            m = Message()
+            m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+            m["userAccountControl"] = MessageElement(
+                str(UF_INTERDOMAIN_TRUST_ACCOUNT),
+                FLAG_MOD_REPLACE, "userAccountControl")
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e69:
+            (num, _) = e69.args
+            self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
+
+        # With a computer object
+
+        # Add operation
+
+        # As computer you can set a normal account and a server trust account.
+        # The UF_PASSWD_NOTREQD flag is needed since we haven't requested a
+        # password yet.
+        # With SYSTEM rights you can set a interdomain trust account.
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "userAccountControl": "0"})
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE == 0)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_PASSWD_NOTREQD == 0)
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "userAccountControl": str(UF_NORMAL_ACCOUNT)})
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "userAccountControl": str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD)})
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE == 0)
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "userAccountControl": str(UF_NORMAL_ACCOUNT |
+                                      UF_PASSWD_NOTREQD |
+                                      UF_LOCKOUT |
+                                      UF_PASSWORD_EXPIRED)})
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl", "lockoutTime", "pwdLastSet"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & (UF_LOCKOUT | UF_PASSWORD_EXPIRED) == 0)
+        self.assertFalse("lockoutTime" in res1[0])
+        self.assertTrue(int(res1[0]["pwdLastSet"][0]) == 0)
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        try:
+            ldb.add({
+                "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                "objectclass": "computer",
+                "userAccountControl": str(UF_TEMP_DUPLICATE_ACCOUNT)})
+            self.fail()
+        except LdbError as e70:
+            (num, _) = e70.args
+            self.assertEqual(num, ERR_OTHER)
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT)})
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_WORKSTATION_TRUST)
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        try:
+            ldb.add({
+                "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                "objectclass": "computer",
+                "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT)})
+        except LdbError as e71:
+            (num, _) = e71.args
+            self.assertEqual(num, ERR_OBJECT_CLASS_VIOLATION)
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        try:
+            ldb.add({
+                "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                "objectclass": "computer",
+                "userAccountControl": str(UF_INTERDOMAIN_TRUST_ACCOUNT)})
+            self.fail()
+        except LdbError as e72:
+            (num, _) = e72.args
+            self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        # Modify operation
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer"})
+
+        # After creation we should have a normal account
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE != 0)
+
+        # As computer you can switch from a normal account to a workstation
+        # or server trust account and back (also swapping between trust
+        # accounts is allowed).
+        # The UF_PASSWD_NOTREQD flag is needed since we haven't requested a
+        # password yet.
+        # With SYSTEM rights you can switch to a interdomain trust account.
+
+        # Invalid attribute
+        try:
+            m = Message()
+            m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+            m["userAccountControl"] = MessageElement("0",
+                                                     FLAG_MOD_REPLACE, "userAccountControl")
+            ldb.modify(m)
+        except LdbError as e73:
+            (num, _) = e73.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+
+        try:
+            m = Message()
+            m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+            m["userAccountControl"] = MessageElement(
+                str(UF_NORMAL_ACCOUNT),
+                FLAG_MOD_REPLACE, "userAccountControl")
+            ldb.modify(m)
+        except LdbError as e74:
+            (num, _) = e74.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE == 0)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_ACCOUNTDISABLE),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_NORMAL_ACCOUNT != 0)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE != 0)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["lockoutTime"] = MessageElement(str(samba.unix2nttime(0)), FLAG_MOD_REPLACE, "lockoutTime")
+        m["pwdLastSet"] = MessageElement(str(samba.unix2nttime(0)), FLAG_MOD_REPLACE, "pwdLastSet")
+        ldb.modify(m)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_LOCKOUT | UF_PASSWORD_EXPIRED),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["sAMAccountType", "userAccountControl", "lockoutTime", "pwdLastSet"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_NORMAL_ACCOUNT != 0)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & (UF_LOCKOUT | UF_PASSWORD_EXPIRED) == 0)
+        self.assertTrue(int(res1[0]["lockoutTime"][0]) == 0)
+        self.assertTrue(int(res1[0]["pwdLastSet"][0]) == 0)
+
+        try:
+            m = Message()
+            m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+            m["userAccountControl"] = MessageElement(
+                str(UF_TEMP_DUPLICATE_ACCOUNT),
+                FLAG_MOD_REPLACE, "userAccountControl")
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e75:
+            (num, _) = e75.args
+            self.assertEqual(num, ERR_OTHER)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_SERVER_TRUST_ACCOUNT),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_WORKSTATION_TRUST)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_WORKSTATION_TRUST)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
         m["userAccountControl"] = MessageElement(
-          str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD),
-          FLAG_MOD_REPLACE, "userAccountControl")
+            str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD),
+            FLAG_MOD_REPLACE, "userAccountControl")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
         m["userAccountControl"] = MessageElement(
-          str(UF_SERVER_TRUST_ACCOUNT),
-          FLAG_MOD_REPLACE, "userAccountControl")
+            str(UF_WORKSTATION_TRUST_ACCOUNT),
+            FLAG_MOD_REPLACE, "userAccountControl")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_WORKSTATION_TRUST)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_WORKSTATION_TRUST)
 
         m = Message()
         m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
         m["userAccountControl"] = MessageElement(
-          str(UF_WORKSTATION_TRUST_ACCOUNT),
-          FLAG_MOD_REPLACE, "userAccountControl")
+            str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD),
+            FLAG_MOD_REPLACE, "userAccountControl")
         ldb.modify(m)
 
         res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
                           scope=SCOPE_BASE, attrs=["sAMAccountType"])
         self.assertTrue(len(res1) == 1)
-        self.assertEquals(int(res1[0]["sAMAccountType"][0]),
-          ATYPE_WORKSTATION_TRUST)
-
-# This isn't supported yet in s4 - needs ACL module adaption
-#        try:
-#            m = Message()
-#            m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
-#            m["userAccountControl"] = MessageElement(
-#              str(UF_INTERDOMAIN_TRUST_ACCOUNT),
-#              FLAG_MOD_REPLACE, "userAccountControl")
-#            ldb.modify(m)
-#            self.fail()
-#        except LdbError, (num, _):
-#            self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
-
-        self.delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
-        self.delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
-
-if not "://" in host:
-    if os.path.isfile(host):
-        host = "tdb://%s" % host
-    else:
-        host = "ldap://%s" % host
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_NORMAL_ACCOUNT)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_SERVER_TRUST_ACCOUNT),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_WORKSTATION_TRUST)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_WORKSTATION_TRUST_ACCOUNT),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE, attrs=["sAMAccountType"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["sAMAccountType"][0]),
+                          ATYPE_WORKSTATION_TRUST)
+
+        try:
+            m = Message()
+            m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+            m["userAccountControl"] = MessageElement(
+                str(UF_INTERDOMAIN_TRUST_ACCOUNT),
+                FLAG_MOD_REPLACE, "userAccountControl")
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e76:
+            (num, _) = e76.args
+            self.assertEqual(num, ERR_INSUFFICIENT_ACCESS_RIGHTS)
+
+        # "primaryGroupID" does not change if account type remains the same
+
+        # For a user account
+
+        ldb.add({
+            "dn": "cn=ldaptestuser2,cn=users," + self.base_dn,
+            "objectclass": "user",
+            "userAccountControl": str(UF_NORMAL_ACCOUNT |
+                                      UF_PASSWD_NOTREQD |
+                                      UF_ACCOUNTDISABLE)})
+
+        res1 = ldb.search("cn=ldaptestuser2,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["userAccountControl"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["userAccountControl"][0]),
+                          UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE)
+
+        m = Message()
+        m.dn = Dn(ldb, "<SID=" + ldb.get_domain_sid() + "-" + str(DOMAIN_RID_ADMINS) + ">")
+        m["member"] = MessageElement(
+            "cn=ldaptestuser2,cn=users," + self.base_dn, FLAG_MOD_ADD, "member")
+        ldb.modify(m)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser2,cn=users," + self.base_dn)
+        m["primaryGroupID"] = MessageElement(str(DOMAIN_RID_ADMINS),
+                                             FLAG_MOD_REPLACE, "primaryGroupID")
+        ldb.modify(m)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser2,cn=users," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestuser2,cn=users," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["userAccountControl", "primaryGroupID"])
+        self.assertTrue(len(res1) == 1)
+        self.assertTrue(int(res1[0]["userAccountControl"][0]) & UF_ACCOUNTDISABLE == 0)
+        self.assertEqual(int(res1[0]["primaryGroupID"][0]), DOMAIN_RID_ADMINS)
+
+        # For a workstation account
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["primaryGroupID"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["primaryGroupID"][0]), DOMAIN_RID_DOMAIN_MEMBERS)
+
+        m = Message()
+        m.dn = Dn(ldb, "<SID=" + ldb.get_domain_sid() + "-" + str(DOMAIN_RID_USERS) + ">")
+        m["member"] = MessageElement(
+            "cn=ldaptestcomputer,cn=computers," + self.base_dn, FLAG_MOD_ADD, "member")
+        ldb.modify(m)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["primaryGroupID"] = MessageElement(str(DOMAIN_RID_USERS),
+                                             FLAG_MOD_REPLACE, "primaryGroupID")
+        ldb.modify(m)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_WORKSTATION_TRUST_ACCOUNT),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["primaryGroupID"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(int(res1[0]["primaryGroupID"][0]), DOMAIN_RID_USERS)
+
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestuser2,cn=users," + self.base_dn)
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+    def find_repl_meta_data(self, rpmd, attid):
+        for i in range(0, rpmd.ctr.count):
+            m = rpmd.ctr.array[i]
+            if m.attid == attid:
+                return m
+        return None
+
+    def test_smartcard_required1(self):
+        """Test the UF_SMARTCARD_REQUIRED behaviour"""
+        print("Testing UF_SMARTCARD_REQUIRED behaviour\n")
+
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+            "objectclass": "user",
+            "userAccountControl": str(UF_NORMAL_ACCOUNT),
+            "unicodePwd": "\"thatsAcomplPASS2\"".encode('utf-16-le')
+        })
+
+        res = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE,
+                         attrs=["sAMAccountType", "userAccountControl",
+                                "pwdLastSet", "msDS-KeyVersionNumber",
+                                "replPropertyMetaData"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(int(res[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT)
+        self.assertNotEqual(int(res[0]["pwdLastSet"][0]), 0)
+        lastset = int(res[0]["pwdLastSet"][0])
+        self.assertEqual(int(res[0]["msDS-KeyVersionNumber"][0]), 1)
+        self.assertTrue(len(res[0]["replPropertyMetaData"]) == 1)
+        rpmd = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+                          res[0]["replPropertyMetaData"][0])
+        lastsetmd = self.find_repl_meta_data(rpmd,
+                                             drsuapi.DRSUAPI_ATTID_pwdLastSet)
+        self.assertIsNotNone(lastsetmd)
+        self.assertEqual(lastsetmd.version, 1)
+        nthashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_unicodePwd)
+        self.assertIsNotNone(nthashmd)
+        self.assertEqual(nthashmd.version, 1)
+        nthistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_ntPwdHistory)
+        self.assertIsNotNone(nthistmd)
+        self.assertEqual(nthistmd.version, 1)
+        lmhashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_dBCSPwd)
+        self.assertIsNotNone(lmhashmd)
+        self.assertEqual(lmhashmd.version, 1)
+        lmhistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_lmPwdHistory)
+        self.assertIsNotNone(lmhistmd)
+        self.assertEqual(lmhistmd.version, 1)
+        spcbmd = self.find_repl_meta_data(rpmd,
+                                          drsuapi.DRSUAPI_ATTID_supplementalCredentials)
+        self.assertIsNotNone(spcbmd)
+        self.assertEqual(spcbmd.version, 1)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_NORMAL_ACCOUNT |UF_SMARTCARD_REQUIRED),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE,
+                         attrs=["sAMAccountType", "userAccountControl",
+                                "pwdLastSet", "msDS-KeyVersionNumber",
+                                "replPropertyMetaData"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(int(res[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT |UF_SMARTCARD_REQUIRED)
+        self.assertEqual(int(res[0]["pwdLastSet"][0]), lastset)
+        lastset1 = int(res[0]["pwdLastSet"][0])
+        self.assertEqual(int(res[0]["msDS-KeyVersionNumber"][0]), 2)
+        self.assertTrue(len(res[0]["replPropertyMetaData"]) == 1)
+        rpmd = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+                          res[0]["replPropertyMetaData"][0])
+        lastsetmd = self.find_repl_meta_data(rpmd,
+                                             drsuapi.DRSUAPI_ATTID_pwdLastSet)
+        self.assertIsNotNone(lastsetmd)
+        self.assertEqual(lastsetmd.version, 1)
+        nthashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_unicodePwd)
+        self.assertIsNotNone(nthashmd)
+        self.assertEqual(nthashmd.version, 2)
+        nthistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_ntPwdHistory)
+        self.assertIsNotNone(nthistmd)
+        self.assertEqual(nthistmd.version, 2)
+        lmhashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_dBCSPwd)
+        self.assertIsNotNone(lmhashmd)
+        self.assertEqual(lmhashmd.version, 2)
+        lmhistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_lmPwdHistory)
+        self.assertIsNotNone(lmhistmd)
+        self.assertEqual(lmhistmd.version, 2)
+        spcbmd = self.find_repl_meta_data(rpmd,
+                                          drsuapi.DRSUAPI_ATTID_supplementalCredentials)
+        self.assertIsNotNone(spcbmd)
+        self.assertEqual(spcbmd.version, 2)
+
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+    def test_smartcard_required2(self):
+        """Test the UF_SMARTCARD_REQUIRED behaviour"""
+        print("Testing UF_SMARTCARD_REQUIRED behaviour\n")
+
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+            "objectclass": "user",
+            "userAccountControl": str(UF_NORMAL_ACCOUNT |UF_ACCOUNTDISABLE),
+        })
+
+        res = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE,
+                         attrs=["sAMAccountType", "userAccountControl",
+                                "pwdLastSet", "msDS-KeyVersionNumber",
+                                "replPropertyMetaData"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(int(res[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT |UF_ACCOUNTDISABLE)
+        self.assertEqual(int(res[0]["pwdLastSet"][0]), 0)
+        self.assertTrue("msDS-KeyVersionNumber" in res[0])
+        self.assertEqual(int(res[0]["msDS-KeyVersionNumber"][0]), 1)
+        self.assertTrue(len(res[0]["replPropertyMetaData"]) == 1)
+        rpmd = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+                          res[0]["replPropertyMetaData"][0])
+        lastsetmd = self.find_repl_meta_data(rpmd,
+                                             drsuapi.DRSUAPI_ATTID_pwdLastSet)
+        self.assertIsNotNone(lastsetmd)
+        self.assertEqual(lastsetmd.version, 1)
+        nthashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_unicodePwd)
+        self.assertIsNotNone(nthashmd)
+        self.assertEqual(nthashmd.version, 1)
+        nthistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_ntPwdHistory)
+        self.assertIsNotNone(nthistmd)
+        self.assertEqual(nthistmd.version, 1)
+        lmhashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_dBCSPwd)
+        self.assertIsNotNone(lmhashmd)
+        self.assertEqual(lmhashmd.version, 1)
+        lmhistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_lmPwdHistory)
+        self.assertIsNotNone(lmhistmd)
+        self.assertEqual(lmhistmd.version, 1)
+        spcbmd = self.find_repl_meta_data(rpmd,
+                                          drsuapi.DRSUAPI_ATTID_supplementalCredentials)
+        self.assertIsNone(spcbmd)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_NORMAL_ACCOUNT |UF_ACCOUNTDISABLE |UF_SMARTCARD_REQUIRED),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE,
+                         attrs=["sAMAccountType", "userAccountControl",
+                                "pwdLastSet", "msDS-KeyVersionNumber",
+                                "replPropertyMetaData"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(int(res[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT |UF_ACCOUNTDISABLE |UF_SMARTCARD_REQUIRED)
+        self.assertEqual(int(res[0]["pwdLastSet"][0]), 0)
+        self.assertEqual(int(res[0]["msDS-KeyVersionNumber"][0]), 2)
+        self.assertTrue(len(res[0]["replPropertyMetaData"]) == 1)
+        rpmd = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+                          res[0]["replPropertyMetaData"][0])
+        lastsetmd = self.find_repl_meta_data(rpmd,
+                                             drsuapi.DRSUAPI_ATTID_pwdLastSet)
+        self.assertIsNotNone(lastsetmd)
+        self.assertEqual(lastsetmd.version, 1)
+        nthashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_unicodePwd)
+        self.assertIsNotNone(nthashmd)
+        self.assertEqual(nthashmd.version, 2)
+        nthistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_ntPwdHistory)
+        self.assertIsNotNone(nthistmd)
+        self.assertEqual(nthistmd.version, 2)
+        lmhashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_dBCSPwd)
+        self.assertIsNotNone(lmhashmd)
+        self.assertEqual(lmhashmd.version, 2)
+        lmhistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_lmPwdHistory)
+        self.assertIsNotNone(lmhistmd)
+        self.assertEqual(lmhistmd.version, 2)
+        spcbmd = self.find_repl_meta_data(rpmd,
+                                          drsuapi.DRSUAPI_ATTID_supplementalCredentials)
+        self.assertIsNotNone(spcbmd)
+        self.assertEqual(spcbmd.version, 1)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_NORMAL_ACCOUNT |UF_SMARTCARD_REQUIRED),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE,
+                         attrs=["sAMAccountType", "userAccountControl",
+                                "pwdLastSet", "msDS-KeyVersionNumber",
+                                "replPropertyMetaData"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(int(res[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT |UF_SMARTCARD_REQUIRED)
+        self.assertEqual(int(res[0]["pwdLastSet"][0]), 0)
+        self.assertEqual(int(res[0]["msDS-KeyVersionNumber"][0]), 2)
+        self.assertTrue(len(res[0]["replPropertyMetaData"]) == 1)
+        rpmd = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+                          res[0]["replPropertyMetaData"][0])
+        lastsetmd = self.find_repl_meta_data(rpmd,
+                                             drsuapi.DRSUAPI_ATTID_pwdLastSet)
+        self.assertIsNotNone(lastsetmd)
+        self.assertEqual(lastsetmd.version, 1)
+        nthashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_unicodePwd)
+        self.assertIsNotNone(nthashmd)
+        self.assertEqual(nthashmd.version, 2)
+        nthistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_ntPwdHistory)
+        self.assertIsNotNone(nthistmd)
+        self.assertEqual(nthistmd.version, 2)
+        lmhashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_dBCSPwd)
+        self.assertIsNotNone(lmhashmd)
+        self.assertEqual(lmhashmd.version, 2)
+        lmhistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_lmPwdHistory)
+        self.assertIsNotNone(lmhistmd)
+        self.assertEqual(lmhistmd.version, 2)
+        spcbmd = self.find_repl_meta_data(rpmd,
+                                          drsuapi.DRSUAPI_ATTID_supplementalCredentials)
+        self.assertIsNotNone(spcbmd)
+        self.assertEqual(spcbmd.version, 1)
+
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+    def test_smartcard_required3(self):
+        """Test the UF_SMARTCARD_REQUIRED behaviour"""
+        print("Testing UF_SMARTCARD_REQUIRED behaviour\n")
+
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestuser,cn=users," + self.base_dn,
+            "objectclass": "user",
+            "userAccountControl": str(UF_NORMAL_ACCOUNT |UF_SMARTCARD_REQUIRED |UF_ACCOUNTDISABLE),
+        })
+
+        res = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE,
+                         attrs=["sAMAccountType", "userAccountControl",
+                                "pwdLastSet", "msDS-KeyVersionNumber",
+                                "replPropertyMetaData"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(int(res[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT |UF_SMARTCARD_REQUIRED |UF_ACCOUNTDISABLE)
+        self.assertEqual(int(res[0]["pwdLastSet"][0]), 0)
+        self.assertEqual(int(res[0]["msDS-KeyVersionNumber"][0]), 1)
+        self.assertTrue(len(res[0]["replPropertyMetaData"]) == 1)
+        rpmd = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+                          res[0]["replPropertyMetaData"][0])
+        lastsetmd = self.find_repl_meta_data(rpmd,
+                                             drsuapi.DRSUAPI_ATTID_pwdLastSet)
+        self.assertIsNotNone(lastsetmd)
+        self.assertEqual(lastsetmd.version, 1)
+        nthashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_unicodePwd)
+        self.assertIsNotNone(nthashmd)
+        self.assertEqual(nthashmd.version, 1)
+        nthistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_ntPwdHistory)
+        self.assertIsNotNone(nthistmd)
+        self.assertEqual(nthistmd.version, 1)
+        lmhashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_dBCSPwd)
+        self.assertIsNotNone(lmhashmd)
+        self.assertEqual(lmhashmd.version, 1)
+        lmhistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_lmPwdHistory)
+        self.assertIsNotNone(lmhistmd)
+        self.assertEqual(lmhistmd.version, 1)
+        spcbmd = self.find_repl_meta_data(rpmd,
+                                          drsuapi.DRSUAPI_ATTID_supplementalCredentials)
+        self.assertIsNotNone(spcbmd)
+        self.assertEqual(spcbmd.version, 1)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_NORMAL_ACCOUNT |UF_SMARTCARD_REQUIRED),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestuser,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE,
+                         attrs=["sAMAccountType", "userAccountControl",
+                                "pwdLastSet", "msDS-KeyVersionNumber",
+                                "replPropertyMetaData"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(int(res[0]["sAMAccountType"][0]),
+                         ATYPE_NORMAL_ACCOUNT)
+        self.assertEqual(int(res[0]["userAccountControl"][0]),
+                         UF_NORMAL_ACCOUNT |UF_SMARTCARD_REQUIRED)
+        self.assertEqual(int(res[0]["pwdLastSet"][0]), 0)
+        self.assertEqual(int(res[0]["msDS-KeyVersionNumber"][0]), 1)
+        self.assertTrue(len(res[0]["replPropertyMetaData"]) == 1)
+        rpmd = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+                          res[0]["replPropertyMetaData"][0])
+        lastsetmd = self.find_repl_meta_data(rpmd,
+                                             drsuapi.DRSUAPI_ATTID_pwdLastSet)
+        self.assertIsNotNone(lastsetmd)
+        self.assertEqual(lastsetmd.version, 1)
+        nthashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_unicodePwd)
+        self.assertIsNotNone(nthashmd)
+        self.assertEqual(nthashmd.version, 1)
+        nthistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_ntPwdHistory)
+        self.assertIsNotNone(nthistmd)
+        self.assertEqual(nthistmd.version, 1)
+        lmhashmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_dBCSPwd)
+        self.assertIsNotNone(lmhashmd)
+        self.assertEqual(lmhashmd.version, 1)
+        lmhistmd = self.find_repl_meta_data(rpmd,
+                                            drsuapi.DRSUAPI_ATTID_lmPwdHistory)
+        self.assertIsNotNone(lmhistmd)
+        self.assertEqual(lmhistmd.version, 1)
+        spcbmd = self.find_repl_meta_data(rpmd,
+                                          drsuapi.DRSUAPI_ATTID_supplementalCredentials)
+        self.assertIsNotNone(spcbmd)
+        self.assertEqual(spcbmd.version, 1)
+
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+    def test_isCriticalSystemObject(self):
+        """Test the isCriticalSystemObject behaviour"""
+        print("Testing isCriticalSystemObject behaviour\n")
+
+        # Add tests
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer"})
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["isCriticalSystemObject"])
+        self.assertTrue(len(res1) == 1)
+        self.assertTrue("isCriticalSystemObject" not in res1[0])
+
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT)})
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["isCriticalSystemObject"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(str(res1[0]["isCriticalSystemObject"][0]), "FALSE")
+
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)})
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["isCriticalSystemObject"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(str(res1[0]["isCriticalSystemObject"][0]), "TRUE")
+
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "userAccountControl": str(UF_SERVER_TRUST_ACCOUNT)})
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["isCriticalSystemObject"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(str(res1[0]["isCriticalSystemObject"][0]), "TRUE")
+
+        # Modification tests
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["userAccountControl"] = MessageElement(str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD),
+                                                 FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["isCriticalSystemObject"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(str(res1[0]["isCriticalSystemObject"][0]), "TRUE")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["userAccountControl"] = MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT),
+                                                 FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["isCriticalSystemObject"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(str(res1[0]["isCriticalSystemObject"][0]), "FALSE")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["userAccountControl"] = MessageElement(
+            str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT),
+            FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["isCriticalSystemObject"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(str(res1[0]["isCriticalSystemObject"][0]), "TRUE")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["userAccountControl"] = MessageElement(str(UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD),
+                                                 FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["isCriticalSystemObject"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(str(res1[0]["isCriticalSystemObject"][0]), "TRUE")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["userAccountControl"] = MessageElement(str(UF_SERVER_TRUST_ACCOUNT),
+                                                 FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["isCriticalSystemObject"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(str(res1[0]["isCriticalSystemObject"][0]), "TRUE")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["userAccountControl"] = MessageElement(str(UF_WORKSTATION_TRUST_ACCOUNT),
+                                                 FLAG_MOD_REPLACE, "userAccountControl")
+        ldb.modify(m)
+
+        res1 = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                          scope=SCOPE_BASE,
+                          attrs=["isCriticalSystemObject"])
+        self.assertTrue(len(res1) == 1)
+        self.assertEqual(str(res1[0]["isCriticalSystemObject"][0]), "FALSE")
+
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+    def test_service_principal_name_updates(self):
+        """Test the servicePrincipalNames update behaviour"""
+        print("Testing servicePrincipalNames update behaviour\n")
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "dNSHostName": "testname.testdom"})
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertFalse("servicePrincipalName" in res[0])
+
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "servicePrincipalName": "HOST/testname.testdom"})
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["dNSHostName"])
+        self.assertTrue(len(res) == 1)
+        self.assertFalse("dNSHostName" in res[0])
+
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "dNSHostName": "testname2.testdom",
+            "servicePrincipalName": "HOST/testname.testdom"})
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["dNSHostName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["dNSHostName"][0]), "testname2.testdom")
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/testname.testdom")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["dNSHostName"] = MessageElement("testname.testdoM",
+                                          FLAG_MOD_REPLACE, "dNSHostName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/testname.testdom")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["dNSHostName"] = MessageElement("testname2.testdom2",
+                                          FLAG_MOD_REPLACE, "dNSHostName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/testname2.testdom2")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["dNSHostName"] = MessageElement([],
+                                          FLAG_MOD_DELETE, "dNSHostName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/testname2.testdom2")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["dNSHostName"] = MessageElement("testname.testdom3",
+                                          FLAG_MOD_REPLACE, "dNSHostName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/testname2.testdom2")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["dNSHostName"] = MessageElement("testname2.testdom2",
+                                          FLAG_MOD_REPLACE, "dNSHostName")
+        ldb.modify(m)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["dNSHostName"] = MessageElement("testname3.testdom3",
+                                          FLAG_MOD_REPLACE, "dNSHostName")
+        m["servicePrincipalName"] = MessageElement("HOST/testname2.testdom2",
+                                                   FLAG_MOD_REPLACE,
+                                                   "servicePrincipalName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/testname3.testdom3")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["servicePrincipalName"] = MessageElement("HOST/testname2.testdom2",
+                                                   FLAG_MOD_REPLACE,
+                                                   "servicePrincipalName")
+        m["dNSHostName"] = MessageElement("testname4.testdom4",
+                                          FLAG_MOD_REPLACE, "dNSHostName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/testname2.testdom2")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["servicePrincipalName"] = MessageElement([],
+                                                   FLAG_MOD_DELETE,
+                                                   "servicePrincipalName")
+        ldb.modify(m)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["dNSHostName"] = MessageElement("testname2.testdom2",
+                                          FLAG_MOD_REPLACE, "dNSHostName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertFalse("servicePrincipalName" in res[0])
+
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "sAMAccountName": "testname$"})
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertFalse("servicePrincipalName" in res[0])
+
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "servicePrincipalName": "HOST/testname"})
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["sAMAccountName"])
+        self.assertTrue(len(res) == 1)
+        self.assertTrue("sAMAccountName" in res[0])
+
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "sAMAccountName": "testname$",
+            "servicePrincipalName": "HOST/testname"})
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["sAMAccountName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["sAMAccountName"][0]), "testname$")
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/testname")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["sAMAccountName"] = MessageElement("testnamE$",
+                                             FLAG_MOD_REPLACE, "sAMAccountName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/testname")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["sAMAccountName"] = MessageElement("testname",
+                                             FLAG_MOD_REPLACE, "sAMAccountName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/testname")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["sAMAccountName"] = MessageElement("test$name$",
+                                             FLAG_MOD_REPLACE, "sAMAccountName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/test$name")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["sAMAccountName"] = MessageElement("testname2",
+                                             FLAG_MOD_REPLACE, "sAMAccountName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/testname2")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["sAMAccountName"] = MessageElement("testname3",
+                                             FLAG_MOD_REPLACE, "sAMAccountName")
+        m["servicePrincipalName"] = MessageElement("HOST/testname2",
+                                                   FLAG_MOD_REPLACE,
+                                                   "servicePrincipalName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/testname3")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["servicePrincipalName"] = MessageElement("HOST/testname2",
+                                                   FLAG_MOD_REPLACE,
+                                                   "servicePrincipalName")
+        m["sAMAccountName"] = MessageElement("testname4",
+                                             FLAG_MOD_REPLACE, "sAMAccountName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["servicePrincipalName"][0]),
+                          "HOST/testname2")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["servicePrincipalName"] = MessageElement([],
+                                                   FLAG_MOD_DELETE,
+                                                   "servicePrincipalName")
+        ldb.modify(m)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["sAMAccountName"] = MessageElement("testname2",
+                                             FLAG_MOD_REPLACE, "sAMAccountName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertFalse("servicePrincipalName" in res[0])
+
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "dNSHostName": "testname.testdom",
+            "sAMAccountName": "testname$",
+            "servicePrincipalName": ["HOST/testname.testdom", "HOST/testname"]
+        })
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["dNSHostName"] = MessageElement("testname2.testdom",
+                                          FLAG_MOD_REPLACE, "dNSHostName")
+        m["sAMAccountName"] = MessageElement("testname2$",
+                                             FLAG_MOD_REPLACE, "sAMAccountName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["dNSHostName", "sAMAccountName", "servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["dNSHostName"][0]), "testname2.testdom")
+        self.assertEqual(str(res[0]["sAMAccountName"][0]), "testname2$")
+        self.assertTrue(str(res[0]["servicePrincipalName"][0]) == "HOST/testname2" or
+                        str(res[0]["servicePrincipalName"][1]) == "HOST/testname2")
+        self.assertTrue(str(res[0]["servicePrincipalName"][0]) == "HOST/testname2.testdom" or
+                        str(res[0]["servicePrincipalName"][1]) == "HOST/testname2.testdom")
+
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+        ldb.add({
+            "dn": "cn=ldaptestcomputer,cn=computers," + self.base_dn,
+            "objectclass": "computer",
+            "dNSHostName": "testname.testdom",
+            "sAMAccountName": "testname$",
+            "servicePrincipalName": ["HOST/testname.testdom", "HOST/testname"]
+        })
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["sAMAccountName"] = MessageElement("testname2$",
+                                             FLAG_MOD_REPLACE, "sAMAccountName")
+        m["dNSHostName"] = MessageElement("testname2.testdom",
+                                          FLAG_MOD_REPLACE, "dNSHostName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["dNSHostName", "sAMAccountName", "servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["dNSHostName"][0]), "testname2.testdom")
+        self.assertEqual(str(res[0]["sAMAccountName"][0]), "testname2$")
+        self.assertTrue(len(res[0]["servicePrincipalName"]) == 2)
+        self.assertTrue("HOST/testname2" in [str(x) for x in res[0]["servicePrincipalName"]])
+        self.assertTrue("HOST/testname2.testdom" in [str(x) for x in res[0]["servicePrincipalName"]])
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["servicePrincipalName"] = MessageElement("HOST/testname2.testdom",
+                                                   FLAG_MOD_ADD, "servicePrincipalName")
+        try:
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e77:
+            (num, _) = e77.args
+            self.assertEqual(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["servicePrincipalName"] = MessageElement("HOST/testname3",
+                                                   FLAG_MOD_ADD, "servicePrincipalName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["dNSHostName", "sAMAccountName", "servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["dNSHostName"][0]), "testname2.testdom")
+        self.assertEqual(str(res[0]["sAMAccountName"][0]), "testname2$")
+        self.assertTrue(len(res[0]["servicePrincipalName"]) == 3)
+        self.assertTrue("HOST/testname2" in [str(x) for x in res[0]["servicePrincipalName"]])
+        self.assertTrue("HOST/testname3" in [str(x) for x in res[0]["servicePrincipalName"]])
+        self.assertTrue("HOST/testname2.testdom" in [str(x) for x in res[0]["servicePrincipalName"]])
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["dNSHostName"] = MessageElement("testname3.testdom",
+                                          FLAG_MOD_REPLACE, "dNSHostName")
+        m["servicePrincipalName"] = MessageElement("HOST/testname3.testdom",
+                                                   FLAG_MOD_ADD, "servicePrincipalName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["dNSHostName", "sAMAccountName", "servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEqual(str(res[0]["dNSHostName"][0]), "testname3.testdom")
+        self.assertEqual(str(res[0]["sAMAccountName"][0]), "testname2$")
+        self.assertTrue(len(res[0]["servicePrincipalName"]) == 3)
+        self.assertTrue("HOST/testname2" in [str(x) for x in res[0]["servicePrincipalName"]])
+        self.assertTrue("HOST/testname3" in [str(x) for x in res[0]["servicePrincipalName"]])
+        self.assertTrue("HOST/testname3.testdom" in [str(x) for x in res[0]["servicePrincipalName"]])
+
+        delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+
+    def test_sam_description_attribute(self):
+        """Test SAM description attribute"""
+        print("Test SAM description attribute")
+
+        self.ldb.add({
+            "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
+            "objectclass": "group",
+            "description": "desc1"
+        })
+
+        res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["description"])
+        self.assertTrue(len(res) == 1)
+        self.assertTrue("description" in res[0])
+        self.assertTrue(len(res[0]["description"]) == 1)
+        self.assertEqual(str(res[0]["description"][0]), "desc1")
+
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+
+        self.ldb.add({
+            "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
+            "objectclass": "group",
+            "description": ["desc1", "desc2"]})
+
+        res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["description"])
+        self.assertTrue(len(res) == 1)
+        self.assertTrue("description" in res[0])
+        self.assertTrue(len(res[0]["description"]) == 2)
+        self.assertTrue(str(res[0]["description"][0]) == "desc1" or
+                        str(res[0]["description"][1]) == "desc1")
+        self.assertTrue(str(res[0]["description"][0]) == "desc2" or
+                        str(res[0]["description"][1]) == "desc2")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m["description"] = MessageElement(["desc1", "desc2"], FLAG_MOD_REPLACE,
+                                          "description")
+        try:
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e78:
+            (num, _) = e78.args
+            self.assertEqual(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m["description"] = MessageElement(["desc1", "desc2"], FLAG_MOD_DELETE,
+                                          "description")
+        ldb.modify(m)
+
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+
+        self.ldb.add({
+            "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
+            "objectclass": "group"})
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m["description"] = MessageElement("desc1", FLAG_MOD_REPLACE,
+                                          "description")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["description"])
+        self.assertTrue(len(res) == 1)
+        self.assertTrue("description" in res[0])
+        self.assertTrue(len(res[0]["description"]) == 1)
+        self.assertEqual(str(res[0]["description"][0]), "desc1")
+
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+
+        self.ldb.add({
+            "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
+            "objectclass": "group",
+            "description": ["desc1", "desc2"]})
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m["description"] = MessageElement("desc1", FLAG_MOD_REPLACE,
+                                          "description")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["description"])
+        self.assertTrue(len(res) == 1)
+        self.assertTrue("description" in res[0])
+        self.assertTrue(len(res[0]["description"]) == 1)
+        self.assertEqual(str(res[0]["description"][0]), "desc1")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m["description"] = MessageElement("desc3", FLAG_MOD_ADD,
+                                          "description")
+        try:
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e79:
+            (num, _) = e79.args
+            self.assertEqual(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m["description"] = MessageElement(["desc1", "desc2"], FLAG_MOD_DELETE,
+                                          "description")
+        try:
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e80:
+            (num, _) = e80.args
+            self.assertEqual(num, ERR_NO_SUCH_ATTRIBUTE)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m["description"] = MessageElement("desc1", FLAG_MOD_DELETE,
+                                          "description")
+        ldb.modify(m)
+        res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["description"])
+        self.assertTrue(len(res) == 1)
+        self.assertFalse("description" in res[0])
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m["description"] = MessageElement(["desc1", "desc2"], FLAG_MOD_REPLACE,
+                                          "description")
+        try:
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e81:
+            (num, _) = e81.args
+            self.assertEqual(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m["description"] = MessageElement(["desc3", "desc4"], FLAG_MOD_ADD,
+                                          "description")
+        try:
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e82:
+            (num, _) = e82.args
+            self.assertEqual(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m["description"] = MessageElement("desc1", FLAG_MOD_ADD,
+                                          "description")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["description"])
+        self.assertTrue(len(res) == 1)
+        self.assertTrue("description" in res[0])
+        self.assertTrue(len(res[0]["description"]) == 1)
+        self.assertEqual(str(res[0]["description"][0]), "desc1")
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m.add(MessageElement("desc1", FLAG_MOD_DELETE, "description"))
+        m.add(MessageElement("desc2", FLAG_MOD_ADD, "description"))
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["description"])
+        self.assertTrue(len(res) == 1)
+        self.assertTrue("description" in res[0])
+        self.assertTrue(len(res[0]["description"]) == 1)
+        self.assertEqual(str(res[0]["description"][0]), "desc2")
+
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+
+    def test_fSMORoleOwner_attribute(self):
+        """Test fSMORoleOwner attribute"""
+        print("Test fSMORoleOwner attribute")
+
+        ds_service_name = self.ldb.get_dsServiceName()
+
+        # The "fSMORoleOwner" attribute can only be set to "nTDSDSA" entries,
+        # invalid DNs return ERR_UNWILLING_TO_PERFORM
+
+        try:
+            self.ldb.add({
+                "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
+                "objectclass": "group",
+                "fSMORoleOwner": self.base_dn})
+            self.fail()
+        except LdbError as e83:
+            (num, _) = e83.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+
+        try:
+            self.ldb.add({
+                "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
+                "objectclass": "group",
+                "fSMORoleOwner": []})
+            self.fail()
+        except LdbError as e84:
+            (num, _) = e84.args
+            self.assertEqual(num, ERR_CONSTRAINT_VIOLATION)
+
+        # We are able to set it to a valid "nTDSDSA" entry if the server is
+        # capable of handling the role
+
+        self.ldb.add({
+            "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
+            "objectclass": "group",
+            "fSMORoleOwner": ds_service_name})
+
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+
+        self.ldb.add({
+            "dn": "cn=ldaptestgroup,cn=users," + self.base_dn,
+            "objectclass": "group"})
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m.add(MessageElement(self.base_dn, FLAG_MOD_REPLACE, "fSMORoleOwner"))
+        try:
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e85:
+            (num, _) = e85.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m.add(MessageElement([], FLAG_MOD_REPLACE, "fSMORoleOwner"))
+        try:
+            ldb.modify(m)
+            self.fail()
+        except LdbError as e86:
+            (num, _) = e86.args
+            self.assertEqual(num, ERR_UNWILLING_TO_PERFORM)
+
+        # We are able to set it to a valid "nTDSDSA" entry if the server is
+        # capable of handling the role
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m.add(MessageElement(ds_service_name, FLAG_MOD_REPLACE, "fSMORoleOwner"))
+        ldb.modify(m)
+
+        # A clean-out works on plain entries, not master (schema, PDC...) DNs
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+        m.add(MessageElement([], FLAG_MOD_DELETE, "fSMORoleOwner"))
+        ldb.modify(m)
+
+        delete_force(self.ldb, "cn=ldaptestgroup,cn=users," + self.base_dn)
+
+    def test_protected_sid_objects(self):
+        """Test deletion of objects with RID < 1000"""
+        # a list of some well-known sids
+        # objects in Builtin are aready covered by objectclass
+        protected_list = [
+            ["CN=Domain Admins", "CN=Users,"],
+            ["CN=Schema Admins", "CN=Users,"],
+            ["CN=Enterprise Admins", "CN=Users,"],
+            ["CN=Administrator", "CN=Users,"],
+            ["CN=Domain Controllers", "CN=Users,"],
+        ]
+
+        for pr_object in protected_list:
+            try:
+                self.ldb.delete(pr_object[0] + "," + pr_object[1] + self.base_dn)
+            except LdbError as e7:
+                (num, _) = e7.args
+                self.assertEqual(num, ERR_OTHER)
+            else:
+                self.fail("Deleted " + pr_object[0])
+
+            try:
+                self.ldb.rename(pr_object[0] + "," + pr_object[1] + self.base_dn,
+                                pr_object[0] + "2," + pr_object[1] + self.base_dn)
+            except LdbError as e8:
+                (num, _) = e8.args
+                self.fail("Could not rename " + pr_object[0])
+
+            self.ldb.rename(pr_object[0] + "2," + pr_object[1] + self.base_dn,
+                            pr_object[0] + "," + pr_object[1] + self.base_dn)
+
+    def test_new_user_default_attributes(self):
+        """Test default attributes for new user objects"""
+        print("Test default attributes for new User objects\n")
+
+        user_name = "ldaptestuser"
+        user_dn = "CN=%s,CN=Users,%s" % (user_name, self.base_dn)
+        ldb.add({
+            "dn": user_dn,
+            "objectclass": "user",
+            "sAMAccountName": user_name})
+
+        res = ldb.search(user_dn, scope=SCOPE_BASE)
+        self.assertTrue(len(res) == 1)
+        user_obj = res[0]
+
+        expected_attrs = {"primaryGroupID": MessageElement(["513"]),
+                          "logonCount": MessageElement(["0"]),
+                          "cn": MessageElement([user_name]),
+                          "countryCode": MessageElement(["0"]),
+                          "objectClass": MessageElement(["top", "person", "organizationalPerson", "user"]),
+                          "instanceType": MessageElement(["4"]),
+                          "distinguishedName": MessageElement([user_dn]),
+                          "sAMAccountType": MessageElement(["805306368"]),
+                          "objectSid": "**SKIP**",
+                          "whenCreated": "**SKIP**",
+                          "uSNCreated": "**SKIP**",
+                          "badPasswordTime": MessageElement(["0"]),
+                          "dn": Dn(ldb, user_dn),
+                          "pwdLastSet": MessageElement(["0"]),
+                          "sAMAccountName": MessageElement([user_name]),
+                          "objectCategory": MessageElement(["CN=Person,%s" % ldb.get_schema_basedn().get_linearized()]),
+                          "objectGUID": "**SKIP**",
+                          "whenChanged": "**SKIP**",
+                          "badPwdCount": MessageElement(["0"]),
+                          "accountExpires": MessageElement(["9223372036854775807"]),
+                          "name": MessageElement([user_name]),
+                          "codePage": MessageElement(["0"]),
+                          "userAccountControl": MessageElement(["546"]),
+                          "lastLogon": MessageElement(["0"]),
+                          "uSNChanged": "**SKIP**",
+                          "lastLogoff": MessageElement(["0"])}
+        # assert we have expected attribute names
+        actual_names = set(user_obj.keys())
+        # Samba does not use 'dSCorePropagationData', so skip it
+        actual_names -= set(['dSCorePropagationData'])
+        self.assertEqual(set(expected_attrs.keys()), actual_names, "Actual object does not have expected attributes")
+        # check attribute values
+        for name in expected_attrs.keys():
+            actual_val = user_obj.get(name)
+            self.assertFalse(actual_val is None, "No value for attribute '%s'" % name)
+            expected_val = expected_attrs[name]
+            if expected_val == "**SKIP**":
+                # "**ANY**" values means "any"
+                continue
+            self.assertEqual(expected_val, actual_val,
+                             "Unexpected value[%r] for '%s' expected[%r]" %
+                             (actual_val, name, expected_val))
+        # clean up
+        delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn)
+
+
+if "://" not in host:
+    if os.path.isfile(host):
+        host = "tdb://%s" % host
+    else:
+        host = "ldap://%s" % host
+
+ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp)
 
-ldb = Ldb(host, credentials=creds, session_info=system_session(), lp=lp)
-if not "tdb://" in host:
-    gc_ldb = Ldb("%s:3268" % host, credentials=creds,
-                 session_info=system_session(), lp=lp)
-else:
-    gc_ldb = None
-
-runner = SubunitTestRunner()
-rc = 0
-if not runner.run(unittest.makeSuite(SamTests)).wasSuccessful():
-    rc = 1
-sys.exit(rc)
+TestProgram(module=__name__, opts=subunitopts)