summaryrefslogtreecommitdiffstats
path: root/source4/dsdb/samdb/ldb_modules/schema_load.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/dsdb/samdb/ldb_modules/schema_load.c')
-rw-r--r--source4/dsdb/samdb/ldb_modules/schema_load.c657
1 files changed, 657 insertions, 0 deletions
diff --git a/source4/dsdb/samdb/ldb_modules/schema_load.c b/source4/dsdb/samdb/ldb_modules/schema_load.c
new file mode 100644
index 0000000..178a991
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/schema_load.c
@@ -0,0 +1,657 @@
+/*
+ Unix SMB/CIFS Implementation.
+
+ The module that handles the Schema FSMO Role Owner
+ checkings, it also loads the dsdb_schema.
+
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009-2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+#include <tdb.h>
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "lib/ldb-samba/ldb_wrap.h"
+#include "lib/util/smb_strtox.h"
+
+#include "system/filesys.h"
+struct schema_load_private_data {
+ struct ldb_module *module;
+ uint64_t in_transaction;
+ uint64_t in_read_transaction;
+ struct tdb_wrap *metadata;
+ uint64_t schema_seq_num_cache;
+ int tdb_seqnum;
+
+ /*
+ * Please write out the updated schema on the next transaction
+ * start
+ */
+ bool need_write;
+};
+
+static int dsdb_schema_from_db(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ uint64_t schema_seq_num,
+ struct dsdb_schema **schema);
+
+/*
+ * Open sam.ldb.d/metadata.tdb.
+ */
+static int schema_metadata_open(struct ldb_module *module)
+{
+ struct schema_load_private_data *data = talloc_get_type(ldb_module_get_private(module), struct schema_load_private_data);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx;
+ struct loadparm_context *lp_ctx;
+ char *filename;
+ int open_flags;
+ struct stat statbuf;
+
+ if (!data) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "schema_load: metadata not initialized");
+ }
+ data->metadata = NULL;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ filename = ldb_relative_path(ldb,
+ tmp_ctx,
+ "sam.ldb.d/metadata.tdb");
+ if (filename == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+
+ open_flags = O_RDWR;
+ if (stat(filename, &statbuf) != 0) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ lp_ctx = talloc_get_type_abort(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+
+ data->metadata = tdb_wrap_open(data, filename, 10,
+ lpcfg_tdb_flags(lp_ctx, TDB_DEFAULT|TDB_SEQNUM),
+ open_flags, 0660);
+ if (data->metadata == NULL) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+static int schema_metadata_get_uint64(struct schema_load_private_data *data,
+ const char *key, uint64_t *value,
+ uint64_t default_value)
+{
+ struct tdb_context *tdb;
+ TDB_DATA tdb_key, tdb_data;
+ char *value_str;
+ TALLOC_CTX *tmp_ctx;
+ int tdb_seqnum;
+ int error = 0;
+
+ if (!data) {
+ *value = default_value;
+ return LDB_SUCCESS;
+ }
+
+ if (!data->metadata) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ tdb_seqnum = tdb_get_seqnum(data->metadata->tdb);
+ if (tdb_seqnum == data->tdb_seqnum) {
+ *value = data->schema_seq_num_cache;
+ return LDB_SUCCESS;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ldb_module_oom(data->module);
+ }
+
+ tdb = data->metadata->tdb;
+
+ tdb_key.dptr = (uint8_t *)discard_const_p(char, key);
+ tdb_key.dsize = strlen(key);
+
+ tdb_data = tdb_fetch(tdb, tdb_key);
+ if (!tdb_data.dptr) {
+ if (tdb_error(tdb) == TDB_ERR_NOEXIST) {
+ *value = default_value;
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ } else {
+ talloc_free(tmp_ctx);
+ return ldb_module_error(data->module, LDB_ERR_OPERATIONS_ERROR,
+ tdb_errorstr(tdb));
+ }
+ }
+
+ value_str = talloc_strndup(tmp_ctx, (char *)tdb_data.dptr, tdb_data.dsize);
+ if (value_str == NULL) {
+ SAFE_FREE(tdb_data.dptr);
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(data->module);
+ }
+
+ /*
+ * Now store it in the cache. We don't mind that tdb_seqnum
+ * may be stale now, that just means the cache won't be used
+ * next time
+ */
+ data->tdb_seqnum = tdb_seqnum;
+ data->schema_seq_num_cache = smb_strtoull(value_str,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ talloc_free(tmp_ctx);
+ return ldb_module_error(data->module, LDB_ERR_OPERATIONS_ERROR,
+ "Failed to convert value");
+ }
+
+ *value = data->schema_seq_num_cache;
+
+ SAFE_FREE(tdb_data.dptr);
+ talloc_free(tmp_ctx);
+
+ return LDB_SUCCESS;
+}
+
+static struct dsdb_schema *dsdb_schema_refresh(struct ldb_module *module, struct tevent_context *ev,
+ struct dsdb_schema *schema, bool is_global_schema)
+{
+ TALLOC_CTX *mem_ctx;
+ uint64_t schema_seq_num = 0;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct dsdb_schema *new_schema;
+
+ struct schema_load_private_data *private_data = talloc_get_type(ldb_module_get_private(module), struct schema_load_private_data);
+ if (!private_data) {
+ /* We can't refresh until the init function has run */
+ return schema;
+ }
+
+ if (schema != NULL) {
+ /*
+ * If we have a schema already (not in the startup)
+ * and we are in a read or write transaction, then
+ * avoid a schema reload, it can't have changed
+ */
+ if (private_data->in_transaction > 0
+ || private_data->in_read_transaction > 0 ) {
+ /*
+ * If the refresh is not an expected part of a
+ * larger transaction, then we don't allow a
+ * schema reload during a transaction. This
+ * stops others from modifying our schema
+ * behind our backs
+ */
+ if (ldb_get_opaque(ldb,
+ "dsdb_schema_refresh_expected")
+ != (void *)1) {
+ return schema;
+ }
+ }
+ }
+
+ SMB_ASSERT(ev == ldb_get_event_context(ldb));
+
+ mem_ctx = talloc_new(module);
+ if (mem_ctx == NULL) {
+ return NULL;
+ }
+
+ /*
+ * We update right now the last refresh timestamp so that if
+ * the schema partition hasn't change we don't keep on retrying.
+ * Otherwise if the timestamp was update only when the schema has
+ * actually changed (and therefore completely reloaded) we would
+ * continue to hit the database to get the highest USN.
+ */
+
+ ret = schema_metadata_get_uint64(private_data,
+ DSDB_METADATA_SCHEMA_SEQ_NUM,
+ &schema_seq_num, 0);
+
+ if (schema != NULL) {
+ if (ret == LDB_SUCCESS) {
+ if (schema->metadata_usn == schema_seq_num) {
+ TALLOC_FREE(mem_ctx);
+ return schema;
+ } else {
+ DEBUG(3, ("Schema refresh needed %lld != %lld\n",
+ (unsigned long long)schema->metadata_usn,
+ (unsigned long long)schema_seq_num));
+ }
+ } else {
+ /* From an old provision it can happen that the tdb didn't exists yet */
+ DEBUG(0, ("Error while searching for the schema usn in the metadata ignoring: %d:%s:%s\n",
+ ret, ldb_strerror(ret), ldb_errstring(ldb)));
+ TALLOC_FREE(mem_ctx);
+ return schema;
+ }
+ } else {
+ DEBUG(10, ("Initial schema load needed, as we have no existing schema, seq_num: %lld\n",
+ (unsigned long long)schema_seq_num));
+ }
+
+ ret = dsdb_schema_from_db(module, mem_ctx, schema_seq_num, &new_schema);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "dsdb_schema_from_db() failed: %d:%s: %s",
+ ret, ldb_strerror(ret), ldb_errstring(ldb));
+ TALLOC_FREE(mem_ctx);
+ return schema;
+ }
+
+ ret = dsdb_set_schema(ldb, new_schema, SCHEMA_MEMORY_ONLY);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "dsdb_set_schema() failed: %d:%s: %s",
+ ret, ldb_strerror(ret), ldb_errstring(ldb));
+ TALLOC_FREE(mem_ctx);
+ return schema;
+ }
+ if (is_global_schema) {
+ dsdb_make_schema_global(ldb, new_schema);
+ }
+ TALLOC_FREE(mem_ctx);
+ return new_schema;
+}
+
+
+/*
+ Given an LDB module (pointing at the schema DB), and the DN, set the populated schema
+*/
+
+static int dsdb_schema_from_db(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ uint64_t schema_seq_num,
+ struct dsdb_schema **schema)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx;
+ char *error_string;
+ int ret, i;
+ struct ldb_dn *schema_dn = ldb_get_schema_basedn(ldb);
+ struct ldb_result *res;
+ struct ldb_message *schema_msg = NULL;
+ static const char *schema_attrs[] = {
+ DSDB_SCHEMA_COMMON_ATTRS,
+ DSDB_SCHEMA_ATTR_ATTRS,
+ DSDB_SCHEMA_CLASS_ATTRS,
+ "prefixMap",
+ "schemaInfo",
+ "fSMORoleOwner",
+ NULL
+ };
+ unsigned flags;
+
+ tmp_ctx = talloc_new(module);
+ if (!tmp_ctx) {
+ return ldb_oom(ldb);
+ }
+
+ /* we don't want to trace the schema load */
+ flags = ldb_get_flags(ldb);
+ ldb_set_flags(ldb, flags & ~LDB_FLG_ENABLE_TRACING);
+
+ /*
+ * Load the attribute and class definitions, as well as
+ * the schema object. We do this in one search and then
+ * split it so that there isn't a race condition when
+ * the schema is changed between two searches.
+ */
+ ret = dsdb_module_search(module, tmp_ctx, &res,
+ schema_dn, LDB_SCOPE_SUBTREE,
+ schema_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ NULL,
+ "(|(objectClass=attributeSchema)"
+ "(objectClass=classSchema)"
+ "(objectClass=dMD))");
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "dsdb_schema: failed to search attributeSchema and classSchema objects: %s",
+ ldb_errstring(ldb));
+ goto failed;
+ }
+
+ /*
+ * Separate the schema object from the attribute and
+ * class objects.
+ */
+ for (i = 0; i < res->count; i++) {
+ if (ldb_msg_find_element(res->msgs[i], "prefixMap")) {
+ schema_msg = res->msgs[i];
+ break;
+ }
+ }
+
+ if (schema_msg == NULL) {
+ ldb_asprintf_errstring(ldb,
+ "dsdb_schema load failed: failed to find prefixMap");
+ ret = LDB_ERR_NO_SUCH_ATTRIBUTE;
+ goto failed;
+ }
+
+ ret = dsdb_schema_from_ldb_results(tmp_ctx, ldb,
+ schema_msg, res, schema, &error_string);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "dsdb_schema load failed: %s",
+ error_string);
+ goto failed;
+ }
+
+ (*schema)->metadata_usn = schema_seq_num;
+
+ talloc_steal(mem_ctx, *schema);
+
+failed:
+ if (flags & LDB_FLG_ENABLE_TRACING) {
+ flags = ldb_get_flags(ldb);
+ ldb_set_flags(ldb, flags | LDB_FLG_ENABLE_TRACING);
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static int schema_load(struct ldb_context *ldb,
+ struct ldb_module *module,
+ bool *need_write)
+{
+ struct dsdb_schema *schema;
+ int ret, metadata_ret;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ schema = dsdb_get_schema(ldb, frame);
+
+ metadata_ret = schema_metadata_open(module);
+
+ /* We might already have a schema */
+ if (schema != NULL) {
+ /* If we have the metadata.tdb, then hook up the refresh function */
+ if (metadata_ret == LDB_SUCCESS && dsdb_uses_global_schema(ldb)) {
+ ret = dsdb_set_schema_refresh_function(ldb, dsdb_schema_refresh, module);
+
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_init: dsdb_set_schema_refresh_fns() failed: %d:%s: %s",
+ ret, ldb_strerror(ret), ldb_errstring(ldb));
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ }
+
+ TALLOC_FREE(frame);
+ return LDB_SUCCESS;
+ }
+
+ if (metadata_ret == LDB_SUCCESS) {
+ ret = dsdb_set_schema_refresh_function(ldb, dsdb_schema_refresh, module);
+
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_init: dsdb_set_schema_refresh_fns() failed: %d:%s: %s",
+ ret, ldb_strerror(ret), ldb_errstring(ldb));
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ } else {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_init: failed to open metadata.tdb");
+ TALLOC_FREE(frame);
+ return metadata_ret;
+ }
+
+ schema = dsdb_get_schema(ldb, frame);
+
+ /* We do this, invoking the refresh handler, so we know that it works */
+ if (schema == NULL) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_init: dsdb_get_schema failed");
+ TALLOC_FREE(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Now check the @INDEXLIST is correct, or fix it up */
+ ret = dsdb_schema_set_indices_and_attributes(ldb, schema,
+ SCHEMA_COMPARE);
+ if (ret == LDB_ERR_BUSY) {
+ *need_write = true;
+ ret = LDB_SUCCESS;
+ } else {
+ *need_write = false;
+ }
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to update "
+ "@INDEXLIST and @ATTRIBUTES "
+ "records to match database schema: %s",
+ ldb_errstring(ldb));
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ TALLOC_FREE(frame);
+ return LDB_SUCCESS;
+}
+
+static int schema_load_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct schema_load_private_data *private_data =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct schema_load_private_data);
+ int ret;
+
+ ret = ldb_next_init(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return schema_load(ldb, module, &private_data->need_write);
+}
+
+static int schema_load_start_transaction(struct ldb_module *module)
+{
+ struct schema_load_private_data *private_data =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct schema_load_private_data);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct dsdb_schema *schema;
+ int ret;
+
+ ret = ldb_next_start_trans(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Try the schema refresh now */
+ schema = dsdb_get_schema(ldb, NULL);
+ if (schema == NULL) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_init: dsdb_get_schema failed");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (private_data->need_write) {
+ ret = dsdb_schema_set_indices_and_attributes(ldb,
+ schema,
+ SCHEMA_WRITE);
+ private_data->need_write = false;
+ }
+
+ private_data->in_transaction++;
+
+ return ret;
+}
+
+static int schema_load_end_transaction(struct ldb_module *module)
+{
+ struct schema_load_private_data *private_data =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct schema_load_private_data);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ if (private_data->in_transaction == 0) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_end_transaction: transaction mismatch");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ private_data->in_transaction--;
+
+ return ldb_next_end_trans(module);
+}
+
+static int schema_load_del_transaction(struct ldb_module *module)
+{
+ struct schema_load_private_data *private_data =
+ talloc_get_type(ldb_module_get_private(module), struct schema_load_private_data);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ if (private_data->in_transaction == 0) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_del_transaction: transaction mismatch");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ private_data->in_transaction--;
+
+ return ldb_next_del_trans(module);
+}
+
+/* This is called in a transaction held by the callers */
+static int schema_load_extended(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct dsdb_schema *schema;
+ int ret;
+
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_SCHEMA_LOAD) == 0) {
+
+ ret = dsdb_schema_from_db(module, req, 0, &schema);
+ if (ret == LDB_SUCCESS) {
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
+ }
+ return ret;
+
+ } else if (strcmp(req->op.extended.oid, DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID) == 0) {
+ /* Force a refresh */
+ schema = dsdb_get_schema(ldb, NULL);
+
+ ret = dsdb_schema_set_indices_and_attributes(ldb,
+ schema,
+ SCHEMA_WRITE);
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to write new "
+ "@INDEXLIST and @ATTRIBUTES "
+ "records for updated schema: %s",
+ ldb_errstring(ldb));
+ return ret;
+ }
+
+ return ldb_next_request(module, req);
+ } else {
+ /* Pass to next module, the partition one should finish the chain */
+ return ldb_next_request(module, req);
+ }
+}
+
+static int schema_read_lock(struct ldb_module *module)
+{
+ struct schema_load_private_data *private_data =
+ talloc_get_type(ldb_module_get_private(module), struct schema_load_private_data);
+ int ret;
+
+ if (private_data == NULL) {
+ private_data = talloc_zero(module, struct schema_load_private_data);
+ if (private_data == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ private_data->module = module;
+
+ ldb_module_set_private(module, private_data);
+ }
+
+ ret = ldb_next_read_lock(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (private_data->in_transaction == 0 &&
+ private_data->in_read_transaction == 0) {
+ /* Try the schema refresh now */
+ dsdb_get_schema(ldb_module_get_ctx(module), NULL);
+ }
+
+ private_data->in_read_transaction++;
+
+ return LDB_SUCCESS;
+}
+
+static int schema_read_unlock(struct ldb_module *module)
+{
+ struct schema_load_private_data *private_data =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct schema_load_private_data);
+
+ private_data->in_read_transaction--;
+
+ return ldb_next_read_unlock(module);
+}
+
+
+static const struct ldb_module_ops ldb_schema_load_module_ops = {
+ .name = "schema_load",
+ .init_context = schema_load_init,
+ .extended = schema_load_extended,
+ .start_transaction = schema_load_start_transaction,
+ .end_transaction = schema_load_end_transaction,
+ .del_transaction = schema_load_del_transaction,
+ .read_lock = schema_read_lock,
+ .read_unlock = schema_read_unlock,
+};
+
+int ldb_schema_load_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_schema_load_module_ops);
+}