From 773304ec8b52d718bd3ca9e1b2543a50d7f4843e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Matthias=20Dieter=20Walln=C3=B6fer?= Date: Sat, 21 Apr 2012 18:16:43 +0200 Subject: [PATCH] s4:samldb LDB module - implement "fSMORoleOwner" attribute protection This is a very essential attribute since it references to various domain master roles (PDC emulator, schema...) depending on which entry it has been set. Incautious modifications can cause severe problems. Autobuild-User: Andrew Bartlett Autobuild-Date: Mon Apr 30 02:04:24 CEST 2012 on sn-devel-104 --- source4/dsdb/samdb/ldb_modules/samldb.c | 76 ++++++++++++++++++++++++ source4/dsdb/tests/python/sam.py | 77 +++++++++++++++++++++++++ 2 files changed, 153 insertions(+) diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c index 390d9213f59..3aa0c239591 100644 --- a/source4/dsdb/samdb/ldb_modules/samldb.c +++ b/source4/dsdb/samdb/ldb_modules/samldb.c @@ -2010,12 +2010,72 @@ static int samldb_service_principal_names_change(struct samldb_ctx *ac) return LDB_SUCCESS; } +/* This checks the "fSMORoleOwner" attributes */ +static int samldb_fsmo_role_owner_check(struct samldb_ctx *ac) +{ + struct ldb_context *ldb = ldb_module_get_ctx(ac->module); + const char * const no_attrs[] = { NULL }; + struct ldb_message_element *el; + struct ldb_message *tmp_msg; + struct ldb_dn *res_dn; + struct ldb_result *res; + int ret; + + el = dsdb_get_single_valued_attr(ac->msg, "fSMORoleOwner", + ac->req->operation); + if (el == NULL) { + /* we are not affected */ + return LDB_SUCCESS; + } + + /* Create a temporary message for fetching the "fSMORoleOwner" */ + tmp_msg = ldb_msg_new(ac->msg); + if (tmp_msg == NULL) { + return ldb_module_oom(ac->module); + } + ret = ldb_msg_add(tmp_msg, el, 0); + if (ret != LDB_SUCCESS) { + return ret; + } + res_dn = ldb_msg_find_attr_as_dn(ldb, ac, tmp_msg, "fSMORoleOwner"); + talloc_free(tmp_msg); + + if (res_dn == NULL) { + ldb_set_errstring(ldb, + "samldb: 'fSMORoleOwner' attributes have to reference 'nTDSDSA' entries!"); + if (ac->req->operation == LDB_ADD) { + return LDB_ERR_CONSTRAINT_VIOLATION; + } else { + return LDB_ERR_UNWILLING_TO_PERFORM; + } + } + + /* Fetched DN has to reference a "nTDSDSA" entry */ + ret = dsdb_module_search(ac->module, ac, &res, res_dn, LDB_SCOPE_BASE, + no_attrs, + DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED, + ac->req, "(objectClass=nTDSDSA)"); + if (ret != LDB_SUCCESS) { + return ret; + } + if (res->count != 1) { + ldb_set_errstring(ldb, + "samldb: 'fSMORoleOwner' attributes have to reference 'nTDSDSA' entries!"); + return LDB_ERR_UNWILLING_TO_PERFORM; + } + + talloc_free(res); + + return LDB_SUCCESS; +} + /* add */ static int samldb_add(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb; struct samldb_ctx *ac; + struct ldb_message_element *el; int ret; ldb = ldb_module_get_ctx(module); @@ -2040,6 +2100,14 @@ static int samldb_add(struct ldb_module *module, struct ldb_request *req) return ldb_operr(ldb); } + el = ldb_msg_find_element(ac->msg, "fSMORoleOwner"); + if (el != NULL) { + ret = samldb_fsmo_role_owner_check(ac); + if (ret != LDB_SUCCESS) { + return ret; + } + } + if (samdb_find_attribute(ldb, ac->msg, "objectclass", "user") != NULL) { ac->type = SAMLDB_TYPE_USER; @@ -2231,6 +2299,14 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req) } } + el = ldb_msg_find_element(ac->msg, "fSMORoleOwner"); + if (el != NULL) { + ret = samldb_fsmo_role_owner_check(ac); + if (ret != LDB_SUCCESS) { + return ret; + } + } + if (modified) { struct ldb_request *child_req; diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index 7f5b74dd182..8417b26cb7e 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -2607,6 +2607,83 @@ class SamTests(samba.tests.TestCase): 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, (num, _): + self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) + + try: + self.ldb.add({ + "dn": "cn=ldaptestgroup,cn=users," + self.base_dn, + "objectclass": "group", + "fSMORoleOwner": [] }) + self.fail() + except LdbError, (num, _): + self.assertEquals(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, (num, _): + self.assertEquals(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, (num, _): + self.assertEquals(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) + + if not "://" in host: if os.path.isfile(host): host = "tdb://%s" % host -- 2.34.1