/* Unix SMB/CIFS Implementation. The module that handles the Schema FSMO Role Owner checkings, it also loads the dsdb_schema. Copyright (C) Stefan Metzmacher 2007 Copyright (C) Andrew Bartlett 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 . */ #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 #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); }