From d277700710dc118f61065ed9e16e08e76820b66a Mon Sep 17 00:00:00 2001
From: Joseph Sutton <josephsutton@catalyst.net.nz>
Date: Wed, 1 Jun 2022 16:07:17 +1200
Subject: [PATCH 01/15] CVE-2022-32743 s4-acl: Add tests for validated
 dNSHostName write

BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
---
 selftest/knownfail.d/validated-dns-host-name |  15 +
 source4/dsdb/tests/python/acl.py             | 757 +++++++++++++++++++++++++++
 2 files changed, 772 insertions(+)
 create mode 100644 selftest/knownfail.d/validated-dns-host-name

diff --git a/selftest/knownfail.d/validated-dns-host-name b/selftest/knownfail.d/validated-dns-host-name
new file mode 100644
index 0000000..ee51f44
--- /dev/null
+++ b/selftest/knownfail.d/validated-dns-host-name
@@ -0,0 +1,15 @@
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_account_no_dollar\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_allowed_suffixes\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_case\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_dollar\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_empty_string\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_invalid\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_no_suffix\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_no_value\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_spn\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_spn_matching_account_name_new\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_spn_matching_account_name_original\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_wrong_prefix\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_wrong_suffix\(
+^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_spn_matching_dns_host_name_invalid\(
diff --git a/source4/dsdb/tests/python/acl.py b/source4/dsdb/tests/python/acl.py
index 0061d0c..6751934 100755
--- a/source4/dsdb/tests/python/acl.py
+++ b/source4/dsdb/tests/python/acl.py
@@ -300,6 +300,7 @@ class AclModifyTests(AclTests):
         delete_force(self.ldb_admin, "CN=test_modify_group1,CN=Users," + self.base_dn)
         delete_force(self.ldb_admin, "CN=test_modify_group2,CN=Users," + self.base_dn)
         delete_force(self.ldb_admin, "CN=test_modify_group3,CN=Users," + self.base_dn)
+        delete_force(self.ldb_admin, "CN=test_mod_hostname,OU=test_modify_ou1," + self.base_dn)
         delete_force(self.ldb_admin, "OU=test_modify_ou1," + self.base_dn)
         delete_force(self.ldb_admin, self.get_user_dn(self.user_with_wp))
         delete_force(self.ldb_admin, self.get_user_dn(self.user_with_sm))
@@ -651,6 +652,762 @@ Member: CN=test_modify_user2,CN=Users,""" + self.base_dn
         else:
             self.fail()
 
+    def test_modify_dns_host_name(self):
+        '''Test modifying dNSHostName with validated write'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement(host_name,
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError:
+            self.fail()
+
+    def test_modify_dns_host_name_no_validated_write(self):
+        '''Test modifying dNSHostName without validated write'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement(host_name,
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError as err:
+            num, estr = err.args
+            self.assertEqual(ERR_INSUFFICIENT_ACCESS_RIGHTS, num)
+        else:
+            self.fail()
+
+    def test_modify_dns_host_name_invalid(self):
+        '''Test modifying dNSHostName to an invalid value'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        host_name = 'invalid'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement(host_name,
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError as err:
+            num, estr = err.args
+            self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+        else:
+            self.fail()
+
+    def test_modify_dns_host_name_invalid_wp(self):
+        '''Test modifying dNSHostName to an invalid value when we have WP'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Write Property.
+        mod = (f'(OA;CI;WP;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        host_name = 'invalid'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement(host_name,
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError:
+            self.fail()
+
+    def test_modify_dns_host_name_invalid_non_computer(self):
+        '''Test modifying dNSHostName to an invalid value on a non-computer'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'user',
+            'sAMAccountName': f'{account_name}',
+        })
+
+        host_name = 'invalid'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement(host_name,
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError as err:
+            num, estr = err.args
+            self.assertEqual(ERR_INSUFFICIENT_ACCESS_RIGHTS, num)
+        else:
+            self.fail()
+
+    def test_modify_dns_host_name_no_value(self):
+        '''Test modifying dNSHostName with validated write with no value'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement([],
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError as err:
+            num, estr = err.args
+            self.assertEqual(ERR_OPERATIONS_ERROR, num)
+        else:
+            # Windows accepts this.
+            pass
+
+    def test_modify_dns_host_name_empty_string(self):
+        '''Test modifying dNSHostName with validated write of an empty string'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement('\0',
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError as err:
+            num, estr = err.args
+            self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+        else:
+            self.fail()
+
+    def test_modify_dns_host_name_dollar(self):
+        '''Test modifying dNSHostName with validated write of a value including a dollar'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        host_name = f'{account_name}$.{self.ldb_user.domain_dns_name()}'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement(host_name,
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError as err:
+            num, estr = err.args
+            self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+        else:
+            self.fail()
+
+    def test_modify_dns_host_name_account_no_dollar(self):
+        '''Test modifying dNSHostName with validated write with no dollar in sAMAccountName'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}',
+        })
+
+        host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement(host_name,
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError:
+            self.fail()
+
+    def test_modify_dns_host_name_no_suffix(self):
+        '''Test modifying dNSHostName with validated write of a value missing the suffix'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        host_name = f'{account_name}'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement(host_name,
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError as err:
+            num, estr = err.args
+            self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+        else:
+            self.fail()
+
+    def test_modify_dns_host_name_wrong_prefix(self):
+        '''Test modifying dNSHostName with validated write of a value with the wrong prefix'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        host_name = f'invalid.{self.ldb_user.domain_dns_name()}'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement(host_name,
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError as err:
+            num, estr = err.args
+            self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+        else:
+            self.fail()
+
+    def test_modify_dns_host_name_wrong_suffix(self):
+        '''Test modifying dNSHostName with validated write of a value with the wrong suffix'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        host_name = f'{account_name}.invalid.example.com'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement(host_name,
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError as err:
+            num, estr = err.args
+            self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+        else:
+            self.fail()
+
+    def test_modify_dns_host_name_case(self):
+        '''Test modifying dNSHostName with validated write of a value with irregular case'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+        host_name = host_name.capitalize()
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement(host_name,
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError:
+            self.fail()
+
+    def test_modify_dns_host_name_allowed_suffixes(self):
+        '''Test modifying dNSHostName with validated write and an allowed suffix'''
+
+        allowed_suffix = 'suffix.that.is.allowed'
+
+        # Add the allowed suffix.
+
+        res = self.ldb_admin.search(self.base_dn,
+                                    scope=SCOPE_BASE,
+                                    attrs=['msDS-AllowedDNSSuffixes'])
+        self.assertEqual(1, len(res))
+        old_allowed_suffixes = res[0].get('msDS-AllowedDNSSuffixes')
+
+        def modify_allowed_suffixes(suffixes):
+            if suffixes is None:
+                suffixes = []
+                flag = FLAG_MOD_DELETE
+            else:
+                flag = FLAG_MOD_REPLACE
+
+            m = Message(Dn(self.ldb_admin, self.base_dn))
+            m['msDS-AllowedDNSSuffixes'] = MessageElement(
+                suffixes,
+                flag,
+                'msDS-AllowedDNSSuffixes')
+            self.ldb_admin.modify(m)
+
+        self.addCleanup(modify_allowed_suffixes, old_allowed_suffixes)
+
+        if old_allowed_suffixes is None:
+            allowed_suffixes = []
+        else:
+            allowed_suffixes = list(old_allowed_suffixes)
+
+        if (allowed_suffix not in allowed_suffixes and
+            allowed_suffix.encode('utf-8') not in allowed_suffixes):
+                allowed_suffixes.append(allowed_suffix)
+
+        modify_allowed_suffixes(allowed_suffixes)
+
+        # Create the account and run the test.
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        host_name = f'{account_name}.{allowed_suffix}'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['dNSHostName'] = MessageElement(host_name,
+                                          FLAG_MOD_REPLACE,
+                                          'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError:
+            self.fail()
+
+    def test_modify_dns_host_name_spn(self):
+        '''Test modifying dNSHostName and SPN with validated write'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_VALIDATE_SPN};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+        spn = f'host/{host_name}'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['0'] = MessageElement(host_name,
+                                FLAG_MOD_REPLACE,
+                                'dNSHostName')
+        m['1'] = MessageElement(spn,
+                                FLAG_MOD_ADD,
+                                'servicePrincipalName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError:
+            self.fail()
+
+    def test_modify_spn_matching_dns_host_name_invalid(self):
+        '''Test modifying SPN with validated write, matching a valid dNSHostName '''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Write Property.
+        mod = (f'(OA;CI;WP;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_VALIDATE_SPN};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        invalid_host_name = 'invalid'
+
+        host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+        spn = f'host/{host_name}'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['0'] = MessageElement(invalid_host_name,
+                                FLAG_MOD_REPLACE,
+                                'dNSHostName')
+        m['1'] = MessageElement(spn,
+                                FLAG_MOD_ADD,
+                                'servicePrincipalName')
+        m['2'] = MessageElement(host_name,
+                                FLAG_MOD_REPLACE,
+                                'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError:
+            self.fail()
+
+    def test_modify_spn_matching_dns_host_name_original(self):
+        '''Test modifying SPN with validated write, matching the original dNSHostName '''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_VALIDATE_SPN};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        original_host_name = 'invalid_host_name'
+        original_spn = 'host/{original_host_name}'
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+            'dNSHostName': original_host_name,
+        })
+
+        host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['0'] = MessageElement(original_spn,
+                                FLAG_MOD_ADD,
+                                'servicePrincipalName')
+        m['1'] = MessageElement(host_name,
+                                FLAG_MOD_REPLACE,
+                                'dNSHostName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError as err:
+            num, estr = err.args
+            self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+        else:
+            self.fail()
+
+    def test_modify_dns_host_name_spn_matching_account_name_original(self):
+        '''Test modifying dNSHostName and SPN with validated write, matching the original sAMAccountName'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        sam_account_name = '3e0abfd0-126a-11d0-a060-00aa006c33ed'
+
+        # Grant Write Property.
+        mod = (f'(OA;CI;WP;{sam_account_name};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_VALIDATE_SPN};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        new_account_name = 'test_mod_hostname2'
+        host_name = f'{account_name}.{self.ldb_user.domain_dns_name()}'
+        spn = f'host/{host_name}'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['0'] = MessageElement(host_name,
+                                FLAG_MOD_REPLACE,
+                                'dNSHostName')
+        m['1'] = MessageElement(spn,
+                                FLAG_MOD_ADD,
+                                'servicePrincipalName')
+        m['2'] = MessageElement(f'{new_account_name}$',
+                                FLAG_MOD_REPLACE,
+                                'sAMAccountName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError as err:
+            num, estr = err.args
+            self.assertEqual(ERR_CONSTRAINT_VIOLATION, num)
+        else:
+            self.fail()
+
+    def test_modify_dns_host_name_spn_matching_account_name_new(self):
+        '''Test modifying dNSHostName and SPN with validated write, matching the new sAMAccountName'''
+
+        ou_dn = f'OU=test_modify_ou1,{self.base_dn}'
+
+        account_name = 'test_mod_hostname'
+        dn = f'CN={account_name},{ou_dn}'
+
+        self.ldb_admin.create_ou(ou_dn)
+
+        sam_account_name = '3e0abfd0-126a-11d0-a060-00aa006c33ed'
+
+        # Grant Write Property.
+        mod = (f'(OA;CI;WP;{sam_account_name};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+        # Grant Validated Write.
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_DNS_HOST_NAME};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+        mod = (f'(OA;CI;SW;{security.GUID_DRS_VALIDATE_SPN};;'
+               f'{self.user_sid})')
+        self.sd_utils.dacl_add_ace(ou_dn, mod)
+
+        # Create the account.
+        self.ldb_admin.add({
+            'dn': dn,
+            'objectClass': 'computer',
+            'sAMAccountName': f'{account_name}$',
+        })
+
+        new_account_name = 'test_mod_hostname2'
+        new_host_name = f'{new_account_name}.{self.ldb_user.domain_dns_name()}'
+        new_spn = f'host/{new_host_name}'
+
+        m = Message(Dn(self.ldb_user, dn))
+        m['0'] = MessageElement(new_spn,
+                                FLAG_MOD_ADD,
+                                'servicePrincipalName')
+        m['1'] = MessageElement(new_host_name,
+                                FLAG_MOD_REPLACE,
+                                'dNSHostName')
+        m['2'] = MessageElement(f'{new_account_name}$',
+                                FLAG_MOD_REPLACE,
+                                'sAMAccountName')
+        try:
+            self.ldb_user.modify(m)
+        except LdbError:
+            self.fail()
+
 # enable these when we have search implemented
 
 
-- 
1.8.3.1

