diff options
Diffstat (limited to '')
-rw-r--r-- | source4/dsdb/samdb/ldb_modules/schema_load.c | 657 |
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); +} |