From 3c3d94263f457eea03bc3ddbf1c510e748a75902 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 1 Jun 2022 16:07:17 +1200 Subject: [PATCH 1/2] s4-acl: Add tests for validated dNSHostName write BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833 Signed-off-by: Joseph Sutton --- selftest/knownfail.d/validated-dns-host-name | 11 + source4/dsdb/tests/python/acl.py | 522 +++++++++++++++++++ 2 files changed, 533 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 00000000000..3e22a57111d --- /dev/null +++ b/selftest/knownfail.d/validated-dns-host-name @@ -0,0 +1,11 @@ +^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_wrong_prefix\( +^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_wrong_suffix\( diff --git a/source4/dsdb/tests/python/acl.py b/source4/dsdb/tests/python/acl.py index 70dca9b7678..a778267e2d9 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,527 @@ 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 Validated Write and Write Property. + mod = (f'(OA;CI;SWWP;{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() + # enable these when we have search implemented -- 2.35.0 From 9476d6ad8e735144918c390a77dba2ebf2acaa53 Mon Sep 17 00:00:00 2001 From: Joseph Sutton Date: Wed, 1 Jun 2022 16:08:42 +1200 Subject: [PATCH 2/2] dsdb: Implement validated dNSHostName write BUG: https://bugzilla.samba.org/show_bug.cgi?id=14833 Signed-off-by: Joseph Sutton --- selftest/knownfail.d/validated-dns-host-name | 11 - source4/dsdb/samdb/ldb_modules/acl.c | 237 +++++++++++++++++++ 2 files changed, 237 insertions(+), 11 deletions(-) delete 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 deleted file mode 100644 index 3e22a57111d..00000000000 --- a/selftest/knownfail.d/validated-dns-host-name +++ /dev/null @@ -1,11 +0,0 @@ -^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_wrong_prefix\( -^samba4.ldap.acl.python.*__main__.AclModifyTests.test_modify_dns_host_name_wrong_suffix\( diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c index 289c885cdb1..61cd74f0510 100644 --- a/source4/dsdb/samdb/ldb_modules/acl.c +++ b/source4/dsdb/samdb/ldb_modules/acl.c @@ -801,6 +801,231 @@ static int acl_check_spn(TALLOC_CTX *mem_ctx, return LDB_SUCCESS; } +static int acl_check_dns_host_name(TALLOC_CTX *mem_ctx, + struct ldb_module *module, + struct ldb_request *req, + const struct ldb_message_element *el, + struct security_descriptor *sd, + struct dom_sid *sid, + const struct dsdb_attribute *attr, + const struct dsdb_class *objectclass) +{ + int ret; + unsigned i; + TALLOC_CTX *tmp_ctx = NULL; + struct ldb_context *ldb = ldb_module_get_ctx(module); + const struct ldb_message_element *oc_el = NULL; + const struct ldb_message_element *allowed_suffixes = NULL; + struct ldb_result *nc_res = NULL; + struct ldb_dn *nc_root = NULL; + const char *nc_dns_name = NULL; + const char *dns_host_name = NULL; + const char *samAccountName = NULL; + size_t account_name_len; + const struct ldb_message *search_res = NULL; + + static const char *nc_attrs[] = { + "msDS-AllowedDNSSuffixes", + NULL + }; + + if (el->num_values == 0) { + return LDB_SUCCESS; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return ldb_oom(ldb); + } + + /* if we have wp, we can do whatever we like */ + ret = acl_check_access_on_attribute(module, + tmp_ctx, + sd, + sid, + SEC_ADS_WRITE_PROP, + attr, objectclass); + if (ret == LDB_SUCCESS) { + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + ret = acl_check_extended_right(tmp_ctx, + module, + req, + objectclass, + sd, + acl_user_token(module), + GUID_DRS_DNS_HOST_NAME, + SEC_ADS_SELF_WRITE, + sid); + + if (ret != LDB_SUCCESS) { + dsdb_acl_debug(sd, acl_user_token(module), + req->op.mod.message->dn, + true, + 10); + talloc_free(tmp_ctx); + return ret; + } + + /* + * If we have "validated write spn", allow delete of any + * existing value (this keeps constrained delete to the same + * rules as unconstrained) + */ + if (req->operation == LDB_MODIFY) { + struct ldb_result *acl_res = NULL; + + static const char *acl_attrs[] = { + "sAMAccountName", + "objectClass", + NULL + }; + + /* + * If not add or replace (eg delete), + * return success + */ + if ((el->flags + & (LDB_FLAG_MOD_ADD|LDB_FLAG_MOD_REPLACE)) == 0) { + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + ret = dsdb_module_search_dn(module, tmp_ctx, + &acl_res, req->op.mod.message->dn, + acl_attrs, + DSDB_FLAG_NEXT_MODULE | + DSDB_FLAG_AS_SYSTEM | + DSDB_SEARCH_SHOW_RECYCLED, + req); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + search_res = acl_res->msgs[0]; + } else if (req->operation == LDB_ADD) { + search_res = req->op.add.message; + } else { + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } + + /* Check if the account has objectclass 'computer' or 'server'. */ + oc_el = samdb_find_attribute(ldb, + search_res, + "objectclass", + "computer"); + if (oc_el == NULL) { + oc_el = samdb_find_attribute(ldb, + search_res, + "objectclass", + "server"); + + if (oc_el == NULL) { + /* Not a computer or server, so return. */ + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + } + + samAccountName = ldb_msg_find_attr_as_string(search_res, "sAMAccountName", NULL); + if (samAccountName == NULL) { + talloc_free(tmp_ctx); + return ldb_operr(ldb); + } + account_name_len = strlen(samAccountName); + if (account_name_len && samAccountName[account_name_len - 1] == '$') { + /* Account for the '$' character. */ + --account_name_len; + } + + dns_host_name = (const char *)el->values[0].data; + /* TODO: Ensure string is zero-terminated */ + SMB_ASSERT(dns_host_name[el->values[0].length] == '\0'); + + /* Check that sAMAccountName matches the new dNSHostName. */ + if (strncasecmp(dns_host_name, samAccountName, + account_name_len) != 0) { + goto fail; + } + + dns_host_name += account_name_len; + + /* Check the '.' character */ + if (*dns_host_name++ != '.') { + goto fail; + } + + /* Now we check the suffix. */ + + ret = dsdb_find_nc_root(ldb, + tmp_ctx, + search_res->dn, + &nc_root); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + nc_dns_name = samdb_dn_to_dns_domain(tmp_ctx, nc_root); + if (nc_dns_name == NULL) { + talloc_free(tmp_ctx); + return ret; + } + + if (strcasecmp(dns_host_name, nc_dns_name) == 0) { + /* It matches - success. */ + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + /* We didn't get a match, so now try msDS-AllowedDNSSuffixes. */ + + ret = dsdb_module_search_dn(module, tmp_ctx, + &nc_res, nc_root, + nc_attrs, + DSDB_FLAG_NEXT_MODULE | + DSDB_FLAG_AS_SYSTEM | + DSDB_SEARCH_SHOW_RECYCLED, + req); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + allowed_suffixes = ldb_msg_find_element(nc_res->msgs[0], + "msDS-AllowedDNSSuffixes"); + if (allowed_suffixes == NULL) { + goto fail; + } + + for (i = 0; i < allowed_suffixes->num_values; ++i) { + const struct ldb_val *val = &allowed_suffixes->values[i]; + + /* TODO: Ensure string is zero-terminated */ + SMB_ASSERT(val->data[val->length] == '\0'); + + if (strcasecmp(dns_host_name, (const char *)val->data) == 0) { + /* It matches - success. */ + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + } + +fail: + // TODO: ensure that variables printed are non-NULL! + + talloc_free(tmp_ctx); + ldb_debug_set(ldb, LDB_DEBUG_WARNING, + "acl: hostname validation failed for " + "account[%s] hostname[%s]\n", + samAccountName, el->values[0].data); + return LDB_ERR_CONSTRAINT_VIOLATION; +} + static int acl_add(struct ldb_module *module, struct ldb_request *req) { int ret; @@ -1535,6 +1760,18 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req) if (ret != LDB_SUCCESS) { goto fail; } + } else if (ldb_attr_cmp("dnsHostName", el->name) == 0) { + ret = acl_check_dns_host_name(tmp_ctx, + module, + req, + el, + sd, + sid, + attr, + objectclass); + if (ret != LDB_SUCCESS) { + goto fail; + } } else if (is_undelete != NULL && (ldb_attr_cmp("isDeleted", el->name) == 0)) { /* * in case of undelete op permissions on -- 2.35.0