diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c index 38ad7eaf1fd..050ec42ea8c 100644 --- a/source4/dsdb/common/util.c +++ b/source4/dsdb/common/util.c @@ -3994,6 +3994,123 @@ int dsdb_dc_functional_level(struct ldb_context *ldb) return *dcFunctionality; } +int dsdb_check_and_update_fl(struct ldb_context *ldb_ctx, struct loadparm_context *lp_ctx) +{ + TALLOC_CTX *frame = talloc_stackframe(); + int ret; + + int db_dc_functional_level; + int db_domain_functional_level; + int db_forest_functional_level; + int lp_dc_functional_level = lpcfg_ad_dc_functional_level(lp_ctx); + bool am_rodc; + struct ldb_message *msg = NULL; + struct ldb_dn *dc_ntds_settings_dn = NULL; + + + db_dc_functional_level = dsdb_dc_functional_level(ldb_ctx); + db_domain_functional_level = dsdb_functional_level(ldb_ctx); + db_forest_functional_level = dsdb_forest_functional_level(ldb_ctx); + + if (lp_dc_functional_level < db_domain_functional_level) { + DBG_ERR("Refusing to start as smb.conf 'ad dc functional level' maps to %d, " + "which is less than the domain functional level of %d\n", + lp_dc_functional_level, db_domain_functional_level); + TALLOC_FREE(frame); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + if (lp_dc_functional_level < db_forest_functional_level) { + DBG_ERR("Refusing to start as smb.conf 'ad dc functional level' maps to %d, " + "which is less than the forest functional level of %d\n", + lp_dc_functional_level, db_forest_functional_level); + TALLOC_FREE(frame); + return LDB_ERR_CONSTRAINT_VIOLATION; + } + + /* Check if we need to update the DB */ + if (db_dc_functional_level == lp_dc_functional_level) { + TALLOC_FREE(frame); + return LDB_SUCCESS; + } + + /* Confirm we are not an RODC before we try a modify */ + ret = samdb_rodc(ldb_ctx, &am_rodc); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to determine if this server is an RODC\n"); + TALLOC_FREE(frame); + return ret; + } + + if (am_rodc) { + DBG_WARNING("Unable to update DC's msDS-Behavior-Version " + "to correct value (%d from %d) as we are an RODC\n", + db_forest_functional_level, lp_dc_functional_level); + TALLOC_FREE(frame); + return LDB_SUCCESS; + } + + dc_ntds_settings_dn = samdb_ntds_settings_dn(ldb_ctx, frame); + + if (dc_ntds_settings_dn == NULL) { + DBG_ERR("Failed to find own NTDS Settings DN\n"); + TALLOC_FREE(frame); + return LDB_ERR_NO_SUCH_OBJECT; + } + + /* Now update our msDS-Behavior-Version */ + + msg = ldb_msg_new(frame); + if (msg == NULL) { + DBG_ERR("Failed to allocate message to update msDS-Behavior-Version\n"); + TALLOC_FREE(frame); + return LDB_ERR_OPERATIONS_ERROR; + } + + msg->dn = dc_ntds_settings_dn; + + ret = samdb_msg_add_int(ldb_ctx, frame, msg, "msDS-Behavior-Version", lp_dc_functional_level); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to set new msDS-Behavior-Version on message\n"); + TALLOC_FREE(frame); + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = dsdb_replace(ldb_ctx, msg, 0); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to update DB with new msDS-Behavior-Version on %s: %s\n", + ldb_dn_get_linearized(dc_ntds_settings_dn), + ldb_errstring(ldb_ctx)); + TALLOC_FREE(frame); + return ret; + } + + /* + * We have to update the opaque because this particular ldb_context + * will not re-read the DB + */ + { + int *val = talloc(ldb_ctx, int); + if (!val) { + TALLOC_FREE(frame); + return LDB_ERR_OPERATIONS_ERROR; + } + *val = lp_dc_functional_level; + ret = ldb_set_opaque(ldb_ctx, + "domainControllerFunctionality", val); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to re-set domainControllerFunctionality opaque\n"); + TALLOC_FREE(val); + TALLOC_FREE(frame); + return ret; + } + } + + TALLOC_FREE(frame); + return LDB_SUCCESS; +} + + /* set a GUID in an extended DN structure */ diff --git a/source4/dsdb/pydsdb.c b/source4/dsdb/pydsdb.c index 1f2fa7c4be7..22506423d49 100644 --- a/source4/dsdb/pydsdb.c +++ b/source4/dsdb/pydsdb.c @@ -1426,6 +1426,38 @@ static PyObject *py_dsdb_user_account_control_flag_bit_to_string(PyObject *self, return PyUnicode_FromString(str); } +static PyObject *py_dsdb_check_and_update_fl(PyObject *self, PyObject *args) +{ + TALLOC_CTX *frame = NULL; + + PyObject *py_ldb = NULL, *py_lp = NULL; + struct ldb_context *ldb = NULL; + struct loadparm_context *lp_ctx = NULL; + + int ret; + + if (!PyArg_ParseTuple(args, "OO", &py_ldb, &py_lp)) { + return NULL; + } + + PyErr_LDB_OR_RAISE(py_ldb, ldb); + + frame = talloc_stackframe(); + + lp_ctx = lpcfg_from_py_object(frame, py_lp); + if (lp_ctx == NULL) { + TALLOC_FREE(frame); + return NULL; + } + + ret = dsdb_check_and_update_fl(ldb, lp_ctx); + TALLOC_FREE(frame); + + PyErr_LDB_ERROR_IS_ERR_RAISE(py_ldb_get_exception(), ret, ldb); + + Py_RETURN_NONE; +} + static PyMethodDef py_dsdb_methods[] = { { "_samdb_server_site_name", (PyCFunction)py_samdb_server_site_name, METH_VARARGS, "Get the server site name as a string"}, @@ -1512,6 +1544,12 @@ static PyMethodDef py_dsdb_methods[] = { METH_VARARGS, "user_account_control_flag_bit_to_string(bit)" " -> string name" }, + { "check_and_update_fl", + (PyCFunction)py_dsdb_check_and_update_fl, + METH_VARARGS, + "check_and_update_fl(ldb, lp) -> None\n" + "Hook to run in testing the code run on samba server startup " + "to validate and update DC functional levels"}, {0} };