summaryrefslogtreecommitdiff
path: root/source4/dsdb/samdb/ldb_modules
diff options
context:
space:
mode:
Diffstat (limited to 'source4/dsdb/samdb/ldb_modules')
-rw-r--r--source4/dsdb/samdb/ldb_modules/anr.c307
-rw-r--r--source4/dsdb/samdb/ldb_modules/config.mk313
-rw-r--r--source4/dsdb/samdb/ldb_modules/dsdb_cache.c42
-rw-r--r--source4/dsdb/samdb/ldb_modules/extended_dn.c369
-rw-r--r--source4/dsdb/samdb/ldb_modules/instancetype.c124
-rw-r--r--source4/dsdb/samdb/ldb_modules/kludge_acl.c461
-rw-r--r--source4/dsdb/samdb/ldb_modules/linked_attributes.c953
-rw-r--r--source4/dsdb/samdb/ldb_modules/local_password.c852
-rw-r--r--source4/dsdb/samdb/ldb_modules/naming_fsmo.c123
-rw-r--r--source4/dsdb/samdb/ldb_modules/normalise.c162
-rw-r--r--source4/dsdb/samdb/ldb_modules/objectclass.c1207
-rw-r--r--source4/dsdb/samdb/ldb_modules/objectguid.c254
-rw-r--r--source4/dsdb/samdb/ldb_modules/partition.c1101
-rw-r--r--source4/dsdb/samdb/ldb_modules/password_hash.c2281
-rw-r--r--source4/dsdb/samdb/ldb_modules/password_modules.h3
-rw-r--r--source4/dsdb/samdb/ldb_modules/pdc_fsmo.c121
-rw-r--r--source4/dsdb/samdb/ldb_modules/proxy.c337
-rw-r--r--source4/dsdb/samdb/ldb_modules/ranged_results.c207
-rw-r--r--source4/dsdb/samdb/ldb_modules/repl_meta_data.c1588
-rw-r--r--source4/dsdb/samdb/ldb_modules/rootdse.c441
-rw-r--r--source4/dsdb/samdb/ldb_modules/samba3sam.c932
-rw-r--r--source4/dsdb/samdb/ldb_modules/samldb.c825
-rw-r--r--source4/dsdb/samdb/ldb_modules/schema_fsmo.c413
-rw-r--r--source4/dsdb/samdb/ldb_modules/show_deleted.c201
-rw-r--r--source4/dsdb/samdb/ldb_modules/simple_ldap_map.c716
-rw-r--r--source4/dsdb/samdb/ldb_modules/subtree_delete.c260
-rw-r--r--source4/dsdb/samdb/ldb_modules/subtree_rename.c285
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/samba3sam.py1046
-rw-r--r--source4/dsdb/samdb/ldb_modules/update_keytab.c211
29 files changed, 16135 insertions, 0 deletions
diff --git a/source4/dsdb/samdb/ldb_modules/anr.c b/source4/dsdb/samdb/ldb_modules/anr.c
new file mode 100644
index 0000000000..4e2c527fe9
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/anr.c
@@ -0,0 +1,307 @@
+/*
+ ldb database library
+
+ Copyright (C) Amdrew Bartlett <abartlet@samba.org> 2007
+ Copyright (C) Andrew Tridgell 2004
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb anr module
+ *
+ * Description: module to implement 'ambiguous name resolution'
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb_includes.h"
+#include "dsdb/samdb/samdb.h"
+
+/**
+ * Make a and 'and' or 'or' tree from the two supplied elements
+ */
+struct ldb_parse_tree *make_parse_list(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, enum ldb_parse_op op,
+ struct ldb_parse_tree *first_arm, struct ldb_parse_tree *second_arm)
+{
+ struct ldb_parse_tree *list;
+
+ list = talloc(mem_ctx, struct ldb_parse_tree);
+ if (list == NULL){
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+ list->operation = op;
+
+ list->u.list.num_elements = 2;
+ list->u.list.elements = talloc_array(list, struct ldb_parse_tree *, 2);
+ if (!list->u.list.elements) {
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+ list->u.list.elements[0] = talloc_steal(list, first_arm);
+ list->u.list.elements[1] = talloc_steal(list, second_arm);
+ return list;
+}
+
+/**
+ * Make an equality or prefix match tree, from the attribute, operation and matching value supplied
+ */
+struct ldb_parse_tree *make_match_tree(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, enum ldb_parse_op op,
+ const char *attr, const DATA_BLOB *match)
+{
+ struct ldb_parse_tree *match_tree;
+
+ match_tree = talloc(mem_ctx, struct ldb_parse_tree);
+
+ /* Depending on what type of match was selected, fill in the right part of the union */
+
+ match_tree->operation = op;
+ switch (op) {
+ case LDB_OP_SUBSTRING:
+ match_tree->u.substring.attr = attr;
+
+ match_tree->u.substring.start_with_wildcard = 0;
+ match_tree->u.substring.end_with_wildcard = 1;
+ match_tree->u.substring.chunks = talloc_array(match_tree, struct ldb_val *, 2);
+
+ if (match_tree->u.substring.chunks == NULL){
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+ match_tree->u.substring.chunks[0] = match;
+ match_tree->u.substring.chunks[1] = NULL;
+ break;
+ case LDB_OP_EQUALITY:
+ match_tree->u.equality.attr = attr;
+ match_tree->u.equality.value = *match;
+ break;
+ }
+ return match_tree;
+}
+
+struct anr_context {
+ bool found_anr;
+ struct ldb_module *module;
+};
+
+/**
+ * Given the match for an 'ambigious name resolution' query, create a
+ * parse tree with an 'or' of all the anr attributes in the schema.
+ */
+
+typedef struct ldb_parse_tree *(*anr_parse_tree_callback_t)(TALLOC_CTX *mem_ctx,
+ const struct ldb_val *match,
+ void *context);
+
+
+/**
+ * Callback function to do the heavy lifting for the for the parse tree walker
+ */
+struct ldb_parse_tree *anr_replace_callback(TALLOC_CTX *mem_ctx,
+ const struct ldb_val *match,
+ void *context)
+{
+ struct ldb_parse_tree *tree = NULL;
+ struct anr_context *anr_context = talloc_get_type(context, struct anr_context);
+ struct ldb_module *module = anr_context->module;
+ struct ldb_parse_tree *match_tree;
+ uint8_t *p;
+ enum ldb_parse_op op;
+ struct dsdb_attribute *cur;
+ const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ ldb_asprintf_errstring(module->ldb, "no schema with which to construct anr filter");
+ return NULL;
+ }
+
+ anr_context->found_anr = true;
+
+ if (match->length > 1 && match->data[0] == '=') {
+ DATA_BLOB *match2 = talloc(tree, DATA_BLOB);
+ *match2 = data_blob_const(match->data+1, match->length - 1);
+ if (match2 == NULL){
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+ match = match2;
+ op = LDB_OP_EQUALITY;
+ } else {
+ op = LDB_OP_SUBSTRING;
+ }
+ for (cur = schema->attributes; cur; cur = cur->next) {
+ if (!(cur->searchFlags & SEARCH_FLAG_ANR)) continue;
+ match_tree = make_match_tree(module, mem_ctx, op, cur->lDAPDisplayName, match);
+
+ if (tree) {
+ /* Inject an 'or' with the current tree */
+ tree = make_parse_list(module, mem_ctx, LDB_OP_OR, tree, match_tree);
+ if (tree == NULL) {
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+ } else {
+ tree = match_tree;
+ }
+ }
+
+
+ /* If the search term has a space in it,
+ split it up at the first space. */
+
+ p = memchr(match->data, ' ', match->length);
+
+ if (p) {
+ struct ldb_parse_tree *first_split_filter, *second_split_filter, *split_filters, *match_tree_1, *match_tree_2;
+ DATA_BLOB *first_match = talloc(tree, DATA_BLOB);
+ DATA_BLOB *second_match = talloc(tree, DATA_BLOB);
+ if (!first_match || !second_match) {
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+ *first_match = data_blob_const(match->data, p-match->data);
+ *second_match = data_blob_const(p+1, match->length - (p-match->data) - 1);
+
+ /* Add (|(&(givenname=first)(sn=second))(&(givenname=second)(sn=first))) */
+
+ match_tree_1 = make_match_tree(module, mem_ctx, op, "givenName", first_match);
+ match_tree_2 = make_match_tree(module, mem_ctx, op, "sn", second_match);
+
+ first_split_filter = make_parse_list(module, context, LDB_OP_AND, match_tree_1, match_tree_2);
+ if (first_split_filter == NULL){
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+
+ match_tree_1 = make_match_tree(module, mem_ctx, op, "sn", first_match);
+ match_tree_2 = make_match_tree(module, mem_ctx, op, "givenName", second_match);
+
+ second_split_filter = make_parse_list(module, context, LDB_OP_AND, match_tree_1, match_tree_2);
+ if (second_split_filter == NULL){
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+
+ split_filters = make_parse_list(module, mem_ctx, LDB_OP_OR,
+ first_split_filter, second_split_filter);
+ if (split_filters == NULL) {
+ ldb_oom(module->ldb);
+ return NULL;
+ }
+
+ if (tree) {
+ /* Inject an 'or' with the current tree */
+ tree = make_parse_list(module, mem_ctx, LDB_OP_OR, tree, split_filters);
+ } else {
+ tree = split_filters;
+ }
+ }
+ return tree;
+}
+
+/*
+ replace any occurances of an attribute with a new, generated attribute tree
+*/
+struct ldb_parse_tree *anr_replace_subtrees(struct ldb_parse_tree *tree,
+ const char *attr,
+ anr_parse_tree_callback_t callback,
+ void *context)
+{
+ int i;
+ struct ldb_parse_tree *tmp;
+
+ switch (tree->operation) {
+ case LDB_OP_AND:
+ case LDB_OP_OR:
+ for (i=0;i<tree->u.list.num_elements;i++) {
+ tmp = anr_replace_subtrees(tree->u.list.elements[i],
+ attr, callback, context);
+ if (tmp) tree->u.list.elements[i] = tmp;
+ }
+ break;
+ case LDB_OP_NOT:
+ tmp = anr_replace_subtrees(tree->u.isnot.child, attr, callback, context);
+ if (tmp) tree->u.isnot.child = tmp;
+ break;
+ case LDB_OP_EQUALITY:
+ if (ldb_attr_cmp(tree->u.equality.attr, attr) == 0) {
+ tmp = callback(tree, &tree->u.equality.value,
+ context);
+ if (tmp) tree = tmp;
+ }
+ break;
+ case LDB_OP_SUBSTRING:
+ if (ldb_attr_cmp(tree->u.substring.attr, attr) == 0) {
+ if (tree->u.substring.start_with_wildcard == 0 &&
+ tree->u.substring.end_with_wildcard == 1 &&
+ tree->u.substring.chunks[0] != NULL &&
+ tree->u.substring.chunks[1] == NULL) {
+ tmp = callback(tree, tree->u.substring.chunks[0], context);
+ if (tmp) tree = tmp;
+ }
+ }
+ break;
+ }
+ return tree;
+}
+
+/* search */
+static int anr_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_parse_tree *anr_tree;
+ struct anr_context *context = talloc(req, struct anr_context);
+ if (!context) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ context->module = module;
+ context->found_anr = false;
+
+#if 0
+ printf("oldanr : %s\n", ldb_filter_from_tree (0, req->op.search.tree));
+#endif
+
+ /* Yes, this is a problem with req->op.search.tree being const... */
+ anr_tree = anr_replace_subtrees(req->op.search.tree, "anr", anr_replace_callback, context);
+ if (!anr_tree) {
+ talloc_free(context);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (context->found_anr) {
+ /* The above function modifies the tree if it finds "anr", so no
+ * point just setting this on the down_req */
+#if 0
+ printf("newtree: %s\n", ldb_filter_from_tree (0, anr_tree));
+#endif
+ req->op.search.tree = talloc_steal(req, anr_tree);
+ } else {
+ if (anr_tree != req->op.search.tree) {
+ talloc_free(anr_tree);
+ }
+ talloc_free(context);
+ }
+ return ldb_next_request(module, req);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_anr_module_ops = {
+ .name = "anr",
+ .search = anr_search
+};
diff --git a/source4/dsdb/samdb/ldb_modules/config.mk b/source4/dsdb/samdb/ldb_modules/config.mk
new file mode 100644
index 0000000000..00e4f1af92
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/config.mk
@@ -0,0 +1,313 @@
+################################################
+# Start MODULE ldb_objectguid
+[MODULE::ldb_objectguid]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBNDR NDR_MISC
+INIT_FUNCTION = LDB_MODULE(objectguid)
+# End MODULE ldb_objectguid
+################################################
+
+ldb_objectguid_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/objectguid.o
+
+################################################
+# Start MODULE ldb_repl_meta_data
+[MODULE::ldb_repl_meta_data]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = SAMDB LIBTALLOC LIBEVENTS \
+ LIBNDR NDR_MISC NDR_DRSUAPI \
+ NDR_DRSBLOBS LIBNDR
+INIT_FUNCTION = LDB_MODULE(repl_meta_data)
+# End MODULE ldb_repl_meta_data
+################################################
+
+ldb_repl_meta_data_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/repl_meta_data.o
+
+################################################
+# Start MODULE ldb_dsdb_cache
+[MODULE::ldb_dsdb_cache]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = SAMDB LIBTALLOC LIBEVENTS
+INIT_FUNCTION = LDB_MODULE(dsdb_cache)
+# End MODULE ldb_dsdb_cache
+################################################
+
+ldb_dsdb_cache_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/dsdb_cache.o
+
+################################################
+# Start MODULE ldb_schema_fsmo
+[MODULE::ldb_schema_fsmo]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = SAMDB LIBTALLOC LIBEVENTS
+INIT_FUNCTION = LDB_MODULE(schema_fsmo)
+# End MODULE ldb_schema_fsmo
+################################################
+
+ldb_schema_fsmo_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/schema_fsmo.o
+
+################################################
+# Start MODULE ldb_naming_fsmo
+[MODULE::ldb_naming_fsmo]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = SAMDB LIBTALLOC LIBEVENTS
+INIT_FUNCTION = LDB_MODULE(naming_fsmo)
+# End MODULE ldb_naming_fsmo
+################################################
+
+ldb_naming_fsmo_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/naming_fsmo.o
+
+################################################
+# Start MODULE ldb_pdc_fsmo
+[MODULE::ldb_pdc_fsmo]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = SAMDB LIBTALLOC LIBEVENTS
+INIT_FUNCTION = LDB_MODULE(pdc_fsmo)
+# End MODULE ldb_pdc_fsmo
+################################################
+
+ldb_pdc_fsmo_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/pdc_fsmo.o
+
+################################################
+# Start MODULE ldb_samldb
+[MODULE::ldb_samldb]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LDAP_ENCODE NDR_MISC SAMDB
+INIT_FUNCTION = LDB_MODULE(samldb)
+#
+# End MODULE ldb_samldb
+################################################
+
+ldb_samldb_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/samldb.o
+
+################################################
+# Start MODULE ldb_samba3sam
+[MODULE::ldb_samba3sam]
+SUBSYSTEM = LIBLDB
+INIT_FUNCTION = LDB_MODULE(samba3sam)
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS SMBPASSWD \
+ NSS_WRAPPER LIBSECURITY NDR_SECURITY
+# End MODULE ldb_samldb
+################################################
+
+ldb_samba3sam_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/samba3sam.o
+
+################################################
+# Start MODULE ldb_simple_ldap_map
+[MODULE::ldb_simple_ldap_map]
+SUBSYSTEM = LIBLDB
+INIT_FUNCTION = LDB_MODULE(entryuuid),LDB_MODULE(nsuniqueid)
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBNDR NDR_MISC
+ENABLE = YES
+ALIASES = entryuuid nsuniqueid
+# End MODULE ldb_entryuuid
+################################################
+
+ldb_simple_ldap_map_OBJ_FILES = \
+ $(dsdbsrcdir)/samdb/ldb_modules/simple_ldap_map.o
+
+# ################################################
+# # Start MODULE ldb_proxy
+# [MODULE::ldb_proxy]
+# SUBSYSTEM = LIBLDB
+# INIT_FUNCTION = LDB_MODULE(proxy)
+# OBJ_FILES = \
+# proxy.o
+#
+# # End MODULE ldb_proxy
+# ################################################
+
+
+################################################
+# Start MODULE ldb_rootdse
+[MODULE::ldb_rootdse]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS SAMDB
+INIT_FUNCTION = LDB_MODULE(rootdse)
+# End MODULE ldb_rootdse
+################################################
+
+ldb_rootdse_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/rootdse.o
+
+################################################
+# Start MODULE ldb_password_hash
+[MODULE::ldb_password_hash]
+SUBSYSTEM = LIBLDB
+INIT_FUNCTION = LDB_MODULE(password_hash)
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS SAMDB LDAP_ENCODE \
+ LIBCLI_AUTH NDR_DRSBLOBS KERBEROS \
+ HEIMDAL_HDB_KEYS HEIMDAL_KRB5
+# End MODULE ldb_password_hash
+################################################
+
+ldb_password_hash_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/password_hash.o
+
+################################################
+# Start MODULE ldb_local_password
+[MODULE::ldb_local_password]
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBNDR SAMDB
+SUBSYSTEM = LIBLDB
+INIT_FUNCTION = LDB_MODULE(local_password)
+# End MODULE ldb_local_password
+################################################
+
+ldb_local_password_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/local_password.o
+
+################################################
+# Start MODULE ldb_kludge_acl
+[MODULE::ldb_kludge_acl]
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBSECURITY SAMDB
+SUBSYSTEM = LIBLDB
+INIT_FUNCTION = LDB_MODULE(kludge_acl)
+
+# End MODULE ldb_kludge_acl
+################################################
+
+ldb_kludge_acl_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/kludge_acl.o
+
+################################################
+# Start MODULE ldb_extended_dn
+[MODULE::ldb_extended_dn]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBNDR LIBSECURITY SAMDB
+INIT_FUNCTION = LDB_MODULE(extended_dn)
+# End MODULE ldb_extended_dn
+################################################
+
+ldb_extended_dn_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/extended_dn.o
+
+################################################
+# Start MODULE ldb_show_deleted
+[MODULE::ldb_show_deleted]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS
+INIT_FUNCTION = LDB_MODULE(show_deleted)
+# End MODULE ldb_show_deleted
+################################################
+
+ldb_show_deleted_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/show_deleted.o
+
+################################################
+# Start MODULE ldb_partition
+[MODULE::ldb_partition]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS SAMDB
+INIT_FUNCTION = LDB_MODULE(partition)
+# End MODULE ldb_partition
+################################################
+
+ldb_partition_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/partition.o
+
+################################################
+# Start MODULE ldb_update_kt
+[MODULE::ldb_update_keytab]
+SUBSYSTEM = LIBLDB
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS CREDENTIALS
+#Also depends on credentials, but that would loop
+INIT_FUNCTION = LDB_MODULE(update_keytab)
+# End MODULE ldb_update_kt
+################################################
+
+ldb_update_keytab_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/update_keytab.o
+
+################################################
+# Start MODULE ldb_objectclass
+[MODULE::ldb_objectclass]
+INIT_FUNCTION = LDB_MODULE(objectclass)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBSECURITY NDR_SECURITY SAMDB
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_objectclass
+################################################
+
+ldb_objectclass_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/objectclass.o
+
+################################################
+# Start MODULE ldb_subtree_rename
+[MODULE::ldb_subtree_rename]
+INIT_FUNCTION = LDB_MODULE(subtree_rename)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_subtree_rename
+################################################
+
+ldb_subtree_rename_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/subtree_rename.o
+
+################################################
+# Start MODULE ldb_subtree_rename
+[MODULE::ldb_subtree_delete]
+INIT_FUNCTION = LDB_MODULE(subtree_delete)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_subtree_rename
+################################################
+
+ldb_subtree_delete_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/subtree_delete.o
+
+################################################
+# Start MODULE ldb_linked_attributes
+[MODULE::ldb_linked_attributes]
+INIT_FUNCTION = LDB_MODULE(linked_attributes)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS SAMDB
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_linked_attributes
+################################################
+
+ldb_linked_attributes_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/linked_attributes.o
+
+################################################
+# Start MODULE ldb_ranged_results
+[MODULE::ldb_ranged_results]
+INIT_FUNCTION = LDB_MODULE(ranged_results)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_ranged_results
+################################################
+
+ldb_ranged_results_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/ranged_results.o
+
+################################################
+# Start MODULE ldb_anr
+[MODULE::ldb_anr]
+INIT_FUNCTION = LDB_MODULE(anr)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBSAMBA-UTIL SAMDB
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_anr
+################################################
+
+ldb_anr_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/anr.o
+
+################################################
+# Start MODULE ldb_normalise
+[MODULE::ldb_normalise]
+INIT_FUNCTION = LDB_MODULE(normalise)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBSAMBA-UTIL SAMDB
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_normalise
+################################################
+
+ldb_normalise_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/normalise.o
+
+################################################
+# Start MODULE ldb_instancetype
+[MODULE::ldb_instancetype]
+INIT_FUNCTION = LDB_MODULE(instancetype)
+CFLAGS = -Ilib/ldb/include
+PRIVATE_DEPENDENCIES = LIBTALLOC LIBEVENTS LIBSAMBA-UTIL SAMDB
+SUBSYSTEM = LIBLDB
+# End MODULE ldb_instancetype
+################################################
+
+ldb_instancetype_OBJ_FILES = $(dsdbsrcdir)/samdb/ldb_modules/instancetype.o
+
diff --git a/source4/dsdb/samdb/ldb_modules/dsdb_cache.c b/source4/dsdb/samdb/ldb_modules/dsdb_cache.c
new file mode 100644
index 0000000000..e60605dce1
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/dsdb_cache.c
@@ -0,0 +1,42 @@
+/*
+ Unix SMB/CIFS mplementation.
+
+ The Module that loads some DSDB related things
+ into memory. E.g. it loads the dsdb_schema struture
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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 "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb_private.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"
+
+static int dsdb_cache_init(struct ldb_module *module)
+{
+ /* TODO: load the schema */
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_dsdb_cache_module_ops = {
+ .name = "dsdb_cache",
+ .init_context = dsdb_cache_init
+};
diff --git a/source4/dsdb/samdb/ldb_modules/extended_dn.c b/source4/dsdb/samdb/ldb_modules/extended_dn.c
new file mode 100644
index 0000000000..84bf5e4843
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/extended_dn.c
@@ -0,0 +1,369 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2005
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb extended dn control module
+ *
+ * Description: this module builds a special dn
+ *
+ * Author: Simo Sorce
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "dsdb/samdb/samdb.h"
+#include "libcli/security/security.h"
+
+#include <time.h>
+
+static bool is_attr_in_list(const char * const * attrs, const char *attr)
+{
+ int i;
+
+ for (i = 0; attrs[i]; i++) {
+ if (strcasecmp(attrs[i], attr) == 0)
+ return true;
+ }
+
+ return false;
+}
+
+static char **copy_attrs(void *mem_ctx, const char * const * attrs)
+{
+ char **new;
+ int i, num;
+
+ for (num = 0; attrs[num]; num++);
+
+ new = talloc_array(mem_ctx, char *, num + 1);
+ if (!new) return NULL;
+
+ for(i = 0; i < num; i++) {
+ new[i] = talloc_strdup(new, attrs[i]);
+ if (!new[i]) {
+ talloc_free(new);
+ return NULL;
+ }
+ }
+ new[i] = NULL;
+
+ return new;
+}
+
+static bool add_attrs(void *mem_ctx, char ***attrs, const char *attr)
+{
+ char **new;
+ int num;
+
+ for (num = 0; (*attrs)[num]; num++);
+
+ new = talloc_realloc(mem_ctx, *attrs, char *, num + 2);
+ if (!new) return false;
+
+ *attrs = new;
+
+ new[num] = talloc_strdup(new, attr);
+ if (!new[num]) return false;
+
+ new[num + 1] = NULL;
+
+ return true;
+}
+
+static bool inject_extended_dn(struct ldb_message *msg,
+ struct ldb_context *ldb,
+ int type,
+ bool remove_guid,
+ bool remove_sid)
+{
+ const struct ldb_val *val;
+ struct GUID guid;
+ struct dom_sid *sid;
+ const DATA_BLOB *guid_blob;
+ const DATA_BLOB *sid_blob;
+ char *object_guid;
+ char *object_sid;
+ char *new_dn;
+
+ guid_blob = ldb_msg_find_ldb_val(msg, "objectGUID");
+ sid_blob = ldb_msg_find_ldb_val(msg, "objectSID");
+
+ if (!guid_blob)
+ return false;
+
+ switch (type) {
+ case 0:
+ /* return things in hexadecimal format */
+ if (sid_blob) {
+ const char *lower_guid_hex = strlower_talloc(msg, data_blob_hex_string(msg, guid_blob));
+ const char *lower_sid_hex = strlower_talloc(msg, data_blob_hex_string(msg, sid_blob));
+ if (!lower_guid_hex || !lower_sid_hex) {
+ return false;
+ }
+ new_dn = talloc_asprintf(msg, "<GUID=%s>;<SID=%s>;%s",
+ lower_guid_hex,
+ lower_sid_hex,
+ ldb_dn_get_linearized(msg->dn));
+ } else {
+ const char *lower_guid_hex = strlower_talloc(msg, data_blob_hex_string(msg, guid_blob));
+ if (!lower_guid_hex) {
+ return false;
+ }
+ new_dn = talloc_asprintf(msg, "<GUID=%s>;%s",
+ lower_guid_hex,
+ ldb_dn_get_linearized(msg->dn));
+ }
+
+ break;
+ case 1:
+ /* retrieve object_guid */
+ guid = samdb_result_guid(msg, "objectGUID");
+ object_guid = GUID_string(msg, &guid);
+
+ /* retrieve object_sid */
+ object_sid = NULL;
+ sid = samdb_result_dom_sid(msg, msg, "objectSID");
+ if (sid) {
+ object_sid = dom_sid_string(msg, sid);
+ if (!object_sid)
+ return false;
+
+ }
+
+ /* Normal, sane format */
+ if (object_sid) {
+ new_dn = talloc_asprintf(msg, "<GUID=%s>;<SID=%s>;%s",
+ object_guid, object_sid,
+ ldb_dn_get_linearized(msg->dn));
+ } else {
+ new_dn = talloc_asprintf(msg, "<GUID=%s>;%s",
+ object_guid,
+ ldb_dn_get_linearized(msg->dn));
+ }
+ break;
+ default:
+ return false;
+ }
+
+ if (!new_dn) {
+ return false;
+ }
+
+ if (remove_guid) {
+ ldb_msg_remove_attr(msg, "objectGUID");
+ }
+
+ if (sid_blob && remove_sid) {
+ ldb_msg_remove_attr(msg, "objectSID");
+ }
+
+ msg->dn = ldb_dn_new(msg, ldb, new_dn);
+ if (! ldb_dn_validate(msg->dn))
+ return false;
+
+ val = ldb_msg_find_ldb_val(msg, "distinguishedName");
+ if (val) {
+ ldb_msg_remove_attr(msg, "distinguishedName");
+ if (ldb_msg_add_steal_string(msg, "distinguishedName", new_dn))
+ return false;
+ }
+
+ return true;
+}
+
+/* search */
+struct extended_context {
+
+ struct ldb_module *module;
+ void *up_context;
+ int (*up_callback)(struct ldb_context *, void *, struct ldb_reply *);
+
+ const char * const *attrs;
+ bool remove_guid;
+ bool remove_sid;
+ int extended_type;
+};
+
+static int extended_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct extended_context *ac;
+
+ ac = talloc_get_type(context, struct extended_context);
+
+ if (ares->type == LDB_REPLY_ENTRY) {
+ /* for each record returned post-process to add any derived
+ attributes that have been asked for */
+ if (!inject_extended_dn(ares->message, ldb, ac->extended_type, ac->remove_guid, ac->remove_sid)) {
+ goto error;
+ }
+ }
+
+ return ac->up_callback(ldb, ac->up_context, ares);
+
+error:
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+}
+
+static int extended_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_control *control;
+ struct ldb_extended_dn_control *extended_ctrl = NULL;
+ struct ldb_control **saved_controls;
+ struct extended_context *ac;
+ struct ldb_request *down_req;
+ char **new_attrs;
+ int ret;
+
+ /* check if there's an extended dn control */
+ control = ldb_request_get_control(req, LDB_CONTROL_EXTENDED_DN_OID);
+ if (control == NULL) {
+ /* not found go on */
+ return ldb_next_request(module, req);
+ }
+
+ if (control->data) {
+ extended_ctrl = talloc_get_type(control->data, struct ldb_extended_dn_control);
+ if (!extended_ctrl) {
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+ }
+
+ ac = talloc(req, struct extended_context);
+ if (ac == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->module = module;
+ ac->up_context = req->context;
+ ac->up_callback = req->callback;
+ ac->attrs = req->op.search.attrs;
+ ac->remove_guid = false;
+ ac->remove_sid = false;
+ if (extended_ctrl) {
+ ac->extended_type = extended_ctrl->type;
+ } else {
+ ac->extended_type = 0;
+ }
+
+ down_req = talloc_zero(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ down_req->operation = req->operation;
+ down_req->op.search.base = req->op.search.base;
+ down_req->op.search.scope = req->op.search.scope;
+ down_req->op.search.tree = req->op.search.tree;
+
+ /* check if attrs only is specified, in that case check wether we need to modify them */
+ if (req->op.search.attrs) {
+ if (! is_attr_in_list(req->op.search.attrs, "objectGUID")) {
+ ac->remove_guid = true;
+ }
+ if (! is_attr_in_list(req->op.search.attrs, "objectSID")) {
+ ac->remove_sid = true;
+ }
+ if (ac->remove_guid || ac->remove_sid) {
+ new_attrs = copy_attrs(down_req, req->op.search.attrs);
+ if (new_attrs == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (ac->remove_guid) {
+ if (!add_attrs(down_req, &new_attrs, "objectGUID"))
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (ac->remove_sid) {
+ if (!add_attrs(down_req, &new_attrs, "objectSID"))
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ down_req->op.search.attrs = (const char * const *)new_attrs;
+ }
+ }
+
+ down_req->controls = req->controls;
+
+ /* save it locally and remove it from the list */
+ /* we do not need to replace them later as we
+ * are keeping the original req intact */
+ if (!save_controls(control, down_req, &saved_controls)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ down_req->context = ac;
+ down_req->callback = extended_callback;
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* perform the search */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+static int extended_init(struct ldb_module *module)
+{
+ struct ldb_request *req;
+ int ret;
+
+ req = talloc(module, struct ldb_request);
+ if (req == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ req->operation = LDB_REQ_REGISTER_CONTROL;
+ req->op.reg_control.oid = LDB_CONTROL_EXTENDED_DN_OID;
+ req->controls = NULL;
+
+ ret = ldb_request(module->ldb, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "extended_dn: Unable to register control with rootdse!\n");
+ talloc_free(req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_free(req);
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_extended_dn_module_ops = {
+ .name = "extended_dn",
+ .search = extended_search,
+ .init_context = extended_init
+};
diff --git a/source4/dsdb/samdb/ldb_modules/instancetype.c b/source4/dsdb/samdb/ldb_modules/instancetype.c
new file mode 100644
index 0000000000..fd5aa5e18a
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/instancetype.c
@@ -0,0 +1,124 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2004-2006
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb instancetype module
+ *
+ * Description: add an instanceType onto every new record
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb_includes.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "param/param.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/flags.h"
+
+/* add_record: add instancetype attribute */
+static int instancetype_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ uint32_t instance_type;
+ int ret;
+ const struct ldb_control *partition_ctrl;
+ const struct dsdb_control_current_partition *partition;
+
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "instancetype_add_record\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ partition_ctrl = ldb_request_get_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID);
+ if (!partition_ctrl) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "instancetype_add: no current partition control found");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ partition = talloc_get_type(partition_ctrl->data,
+ struct dsdb_control_current_partition);
+ SMB_ASSERT(partition && partition->version == DSDB_CONTROL_CURRENT_PARTITION_VERSION);
+
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+
+ /* we have to copy the message as the caller might have it as a const */
+ down_req->op.add.message = msg = ldb_msg_copy_shallow(down_req, req->op.add.message);
+ if (msg == NULL) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * TODO: calculate correct instance type
+ */
+ instance_type = INSTANCE_TYPE_WRITE;
+ if (ldb_dn_compare(partition->dn, msg->dn) == 0) {
+ instance_type |= INSTANCE_TYPE_IS_NC_HEAD;
+ if (ldb_dn_compare(msg->dn, samdb_base_dn(module->ldb)) != 0) {
+ instance_type |= INSTANCE_TYPE_NC_ABOVE;
+ }
+ }
+
+ ret = ldb_msg_add_fmt(msg, "instanceType", "%u", instance_type);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_instancetype_module_ops = {
+ .name = "instancetype",
+ .add = instancetype_add,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/kludge_acl.c b/source4/dsdb/samdb/ldb_modules/kludge_acl.c
new file mode 100644
index 0000000000..bc998a835a
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/kludge_acl.c
@@ -0,0 +1,461 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett 2005
+ Copyright (C) Simo Sorce 2006
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb kludge ACL module
+ *
+ * Description: Simple module to enforce a simple form of access
+ * control, sufficient for securing a default Samba4
+ * installation.
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "auth/auth.h"
+#include "libcli/security/security.h"
+#include "dsdb/samdb/samdb.h"
+
+/* Kludge ACL rules:
+ *
+ * - System can read passwords
+ * - Administrators can write anything
+ * - Users can read anything that is not a password
+ *
+ */
+
+struct kludge_private_data {
+ const char **password_attrs;
+};
+
+static enum security_user_level what_is_user(struct ldb_module *module)
+{
+ struct auth_session_info *session_info
+ = (struct auth_session_info *)ldb_get_opaque(module->ldb, "sessionInfo");
+ return security_session_user_level(session_info);
+}
+
+static const char *user_name(TALLOC_CTX *mem_ctx, struct ldb_module *module)
+{
+ struct auth_session_info *session_info
+ = (struct auth_session_info *)ldb_get_opaque(module->ldb, "sessionInfo");
+ if (!session_info) {
+ return "UNKNOWN (NULL)";
+ }
+
+ return talloc_asprintf(mem_ctx, "%s\\%s",
+ session_info->server_info->domain_name,
+ session_info->server_info->account_name);
+}
+
+/* search */
+struct kludge_acl_context {
+
+ struct ldb_module *module;
+ void *up_context;
+ int (*up_callback)(struct ldb_context *, void *, struct ldb_reply *);
+
+ enum security_user_level user_type;
+ bool allowedAttributes;
+ bool allowedAttributesEffective;
+ bool allowedChildClasses;
+ bool allowedChildClassesEffective;
+ const char **attrs;
+};
+
+/* read all objectClasses */
+
+static int kludge_acl_allowedAttributes(struct ldb_context *ldb, struct ldb_message *msg,
+ const char *attrName)
+{
+ struct ldb_message_element *oc_el;
+ struct ldb_message_element *allowedAttributes;
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb);
+ TALLOC_CTX *mem_ctx;
+ char **objectclass_list, **attr_list;
+ int i, ret;
+
+ /* If we don't have a schema yet, we can't do anything... */
+ if (schema == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ /* Must remove any existing attribute, or else confusion reins */
+ ldb_msg_remove_attr(msg, attrName);
+ ret = ldb_msg_add_empty(msg, attrName, 0, &allowedAttributes);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ mem_ctx = talloc_new(msg);
+ if (!mem_ctx) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* To ensure that oc_el is valid, we must look for it after
+ we alter the element array in ldb_msg_add_empty() */
+ oc_el = ldb_msg_find_element(msg, "objectClass");
+
+ objectclass_list = talloc_array(mem_ctx, char *, oc_el->num_values + 1);
+ if (!objectclass_list) {
+ ldb_oom(ldb);
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; oc_el && i < oc_el->num_values; i++) {
+ objectclass_list[i] = (char *)oc_el->values[i].data;
+ }
+ objectclass_list[i] = NULL;
+
+ attr_list = dsdb_full_attribute_list(mem_ctx, schema, (const char **)objectclass_list, DSDB_SCHEMA_ALL);
+ if (!attr_list) {
+ ldb_asprintf_errstring(ldb, "kludge_acl: Failed to get list of attributes create %s attribute", attrName);
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; attr_list && attr_list[i]; i++) {
+ ldb_msg_add_string(msg, attrName, attr_list[i]);
+ }
+ talloc_free(mem_ctx);
+ return 0;
+
+}
+/* read all objectClasses */
+
+static int kludge_acl_childClasses(struct ldb_context *ldb, struct ldb_message *msg,
+ const char *attrName)
+{
+ struct ldb_message_element *oc_el;
+ struct ldb_message_element *allowedClasses;
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb);
+ const struct dsdb_class *class;
+ int i, j, ret;
+
+ /* If we don't have a schema yet, we can't do anything... */
+ if (schema == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ /* Must remove any existing attribute, or else confusion reins */
+ ldb_msg_remove_attr(msg, attrName);
+ ret = ldb_msg_add_empty(msg, attrName, 0, &allowedClasses);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* To ensure that oc_el is valid, we must look for it after
+ we alter the element array in ldb_msg_add_empty() */
+ oc_el = ldb_msg_find_element(msg, "objectClass");
+
+ for (i=0; oc_el && i < oc_el->num_values; i++) {
+ class = dsdb_class_by_lDAPDisplayName(schema, (const char *)oc_el->values[i].data);
+ if (!class) {
+ /* We don't know this class? what is going on? */
+ continue;
+ }
+
+ for (j=0; class->possibleInferiors && class->possibleInferiors[j]; j++) {
+ ldb_msg_add_string(msg, attrName, class->possibleInferiors[j]);
+ }
+ }
+
+ if (allowedClasses->num_values > 1) {
+ qsort(allowedClasses->values,
+ allowedClasses->num_values,
+ sizeof(*allowedClasses->values),
+ (comparison_fn_t)data_blob_cmp);
+
+ for (i=1 ; i < allowedClasses->num_values; i++) {
+ struct ldb_val *val1 = &allowedClasses->values[i-1];
+ struct ldb_val *val2 = &allowedClasses->values[i];
+ if (data_blob_cmp(val1, val2) == 0) {
+ memmove(val1, val2, (allowedClasses->num_values - i) * sizeof( struct ldb_val));
+ allowedClasses->num_values--;
+ i--;
+ }
+ }
+ }
+
+ return 0;
+
+}
+
+/* find all attributes allowed by all these objectClasses */
+
+static int kludge_acl_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct kludge_acl_context *ac;
+ struct kludge_private_data *data;
+ int i, ret;
+
+ ac = talloc_get_type(context, struct kludge_acl_context);
+ data = talloc_get_type(ac->module->private_data, struct kludge_private_data);
+
+ if (ares->type != LDB_REPLY_ENTRY) {
+ return ac->up_callback(ldb, ac->up_context, ares);
+ }
+
+ if (ac->allowedAttributes) {
+ ret = kludge_acl_allowedAttributes(ldb, ares->message, "allowedAttributes");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+
+ }
+ }
+ if (ac->allowedChildClasses) {
+ ret = kludge_acl_childClasses(ldb, ares->message, "allowedChildClasses");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (data && data->password_attrs) /* if we are not initialized just get through */
+ {
+ switch (ac->user_type) {
+ case SECURITY_SYSTEM:
+ if (ac->allowedAttributesEffective) {
+ ret = kludge_acl_allowedAttributes(ldb, ares->message, "allowedAttributesEffective");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (ac->allowedChildClassesEffective) {
+ ret = kludge_acl_childClasses(ldb, ares->message, "allowedChildClassesEffective");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ break;
+ case SECURITY_ADMINISTRATOR:
+ if (ac->allowedAttributesEffective) {
+ ret = kludge_acl_allowedAttributes(ldb, ares->message, "allowedAttributesEffective");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (ac->allowedChildClassesEffective) {
+ ret = kludge_acl_childClasses(ldb, ares->message, "allowedChildClassesEffective");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ /* fall though */
+ default:
+ /* remove password attributes */
+ for (i = 0; data->password_attrs[i]; i++) {
+ ldb_msg_remove_attr(ares->message, data->password_attrs[i]);
+ }
+ }
+ }
+
+ if ((ac->allowedAttributes || ac->allowedAttributesEffective
+ || ac->allowedChildClasses || ac->allowedChildClassesEffective) &&
+ (!ldb_attr_in_list(ac->attrs, "objectClass") &&
+ !ldb_attr_in_list(ac->attrs, "*"))) {
+ ldb_msg_remove_attr(ares->message, "objectClass");
+ }
+
+ return ac->up_callback(ldb, ac->up_context, ares);
+}
+
+static int kludge_acl_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct kludge_acl_context *ac;
+ struct ldb_request *down_req;
+ struct kludge_private_data *data;
+ int ret, i;
+
+ req->handle = NULL;
+
+ ac = talloc(req, struct kludge_acl_context);
+ if (ac == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ data = talloc_get_type(module->private_data, struct kludge_private_data);
+
+ ac->module = module;
+ ac->up_context = req->context;
+ ac->up_callback = req->callback;
+ ac->user_type = what_is_user(module);
+ ac->attrs = req->op.search.attrs;
+
+ down_req = talloc_zero(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ down_req->operation = req->operation;
+ down_req->op.search.base = req->op.search.base;
+ down_req->op.search.scope = req->op.search.scope;
+ down_req->op.search.tree = req->op.search.tree;
+ down_req->op.search.attrs = req->op.search.attrs;
+
+ ac->allowedAttributes = ldb_attr_in_list(req->op.search.attrs, "allowedAttributes");
+
+ ac->allowedAttributesEffective = ldb_attr_in_list(req->op.search.attrs, "allowedAttributesEffective");
+
+ ac->allowedChildClasses = ldb_attr_in_list(req->op.search.attrs, "allowedChildClasses");
+
+ ac->allowedChildClassesEffective = ldb_attr_in_list(req->op.search.attrs, "allowedChildClassesEffective");
+
+ if (ac->allowedAttributes || ac->allowedAttributesEffective || ac->allowedChildClasses || ac->allowedChildClassesEffective) {
+ down_req->op.search.attrs
+ = ldb_attr_list_copy_add(down_req, down_req->op.search.attrs, "objectClass");
+ }
+
+ /* FIXME: I hink we should copy the tree and keep the original
+ * unmodified. SSS */
+ /* replace any attributes in the parse tree that are private,
+ so we don't allow a search for 'userPassword=penguin',
+ just as we would not allow that attribute to be returned */
+ switch (ac->user_type) {
+ case SECURITY_SYSTEM:
+ break;
+ default:
+ /* remove password attributes */
+ for (i = 0; data && data->password_attrs && data->password_attrs[i]; i++) {
+ ldb_parse_tree_attr_replace(down_req->op.search.tree,
+ data->password_attrs[i],
+ "kludgeACLredactedattribute");
+ }
+ }
+
+ down_req->controls = req->controls;
+
+ down_req->context = ac;
+ down_req->callback = kludge_acl_callback;
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* perform the search */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+/* ANY change type */
+static int kludge_acl_change(struct ldb_module *module, struct ldb_request *req)
+{
+ enum security_user_level user_type = what_is_user(module);
+ switch (user_type) {
+ case SECURITY_SYSTEM:
+ case SECURITY_ADMINISTRATOR:
+ return ldb_next_request(module, req);
+ default:
+ ldb_asprintf_errstring(module->ldb,
+ "kludge_acl_change: "
+ "attempted database modify not permitted. "
+ "User %s is not SYSTEM or an administrator",
+ user_name(req, module));
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+}
+
+static int kludge_acl_init(struct ldb_module *module)
+{
+ int ret, i;
+ TALLOC_CTX *mem_ctx = talloc_new(module);
+ static const char *attrs[] = { "passwordAttribute", NULL };
+ struct ldb_result *res;
+ struct ldb_message *msg;
+ struct ldb_message_element *password_attributes;
+
+ struct kludge_private_data *data;
+
+ data = talloc(module, struct kludge_private_data);
+ if (data == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ data->password_attrs = NULL;
+ module->private_data = data;
+
+ if (!mem_ctx) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_search(module->ldb, ldb_dn_new(mem_ctx, module->ldb, "@KLUDGEACL"),
+ LDB_SCOPE_BASE,
+ NULL, attrs,
+ &res);
+ if (ret != LDB_SUCCESS) {
+ goto done;
+ }
+ talloc_steal(mem_ctx, res);
+ if (res->count == 0) {
+ goto done;
+ }
+
+ if (res->count > 1) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ msg = res->msgs[0];
+
+ password_attributes = ldb_msg_find_element(msg, "passwordAttribute");
+ if (!password_attributes) {
+ goto done;
+ }
+ data->password_attrs = talloc_array(data, const char *, password_attributes->num_values + 1);
+ if (!data->password_attrs) {
+ talloc_free(mem_ctx);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ for (i=0; i < password_attributes->num_values; i++) {
+ data->password_attrs[i] = (const char *)password_attributes->values[i].data;
+ talloc_steal(data->password_attrs, password_attributes->values[i].data);
+ }
+ data->password_attrs[i] = NULL;
+
+done:
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_kludge_acl_module_ops = {
+ .name = "kludge_acl",
+ .search = kludge_acl_search,
+ .add = kludge_acl_change,
+ .modify = kludge_acl_change,
+ .del = kludge_acl_change,
+ .rename = kludge_acl_change,
+ .extended = kludge_acl_change,
+ .init_context = kludge_acl_init
+};
diff --git a/source4/dsdb/samdb/ldb_modules/linked_attributes.c b/source4/dsdb/samdb/ldb_modules/linked_attributes.c
new file mode 100644
index 0000000000..e64472432d
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/linked_attributes.c
@@ -0,0 +1,953 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb linked_attributes module
+ *
+ * Description: Module to ensure linked attribute pairs remain in sync
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+
+struct linked_attributes_context {
+ enum la_step {LA_SEARCH, LA_DO_OPS, LA_DO_ORIG} step;
+ struct ldb_module *module;
+ struct ldb_handle *handle;
+ struct ldb_request *orig_req;
+
+ struct ldb_request *search_req;
+ struct ldb_request **down_req;
+ struct ldb_request *orig_down_req;
+
+ int num_requests;
+ int finished_requests;
+
+ const char **linked_attrs;
+};
+
+struct replace_context {
+ struct linked_attributes_context *ac;
+ struct ldb_message_element *el;
+};
+
+static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares);
+
+static struct linked_attributes_context *linked_attributes_init_handle(struct ldb_request *req,
+ struct ldb_module *module)
+{
+ struct linked_attributes_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct linked_attributes_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = ac;
+
+ ac->module = module;
+ ac->handle = h;
+ ac->orig_req = req;
+
+ ac->orig_down_req = talloc(ac, struct ldb_request);
+ if (!ac->orig_down_req) {
+ ldb_oom(ac->module->ldb);
+ return NULL;
+ }
+
+ *ac->orig_down_req = *req;
+
+ req->handle = h;
+
+ return ac;
+}
+
+/* Common routine to handle reading the attributes and creating a
+ * series of modify requests */
+
+static int setup_modifies(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
+ struct linked_attributes_context *ac,
+ const struct ldb_message *msg,
+ struct ldb_dn *olddn, struct ldb_dn *newdn)
+{
+ int i, j, ret = LDB_SUCCESS;
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb);
+ /* Look up each of the returned attributes */
+ /* Find their schema */
+ /* And it is an actual entry: now create a series of modify requests */
+ for (i=0; i < msg->num_elements; i++) {
+ int otherid;
+ const struct dsdb_attribute *target_attr;
+ const struct ldb_message_element *el = &msg->elements[i];
+ const struct dsdb_attribute *schema_attr
+ = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (!schema_attr) {
+ ldb_asprintf_errstring(ldb,
+ "attribute %s is not a valid attribute in schema", el->name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ /* We have a valid attribute, but if it's not linked they maybe we just got an extra return on our search... */
+ if (schema_attr->linkID == 0) {
+ continue;
+ }
+
+ /* Depending on which direction this link is in, we need to find it's partner */
+ if ((schema_attr->linkID & 1) == 1) {
+ otherid = schema_attr->linkID - 1;
+ } else {
+ otherid = schema_attr->linkID + 1;
+ }
+
+ /* Now find the target attribute */
+ target_attr = dsdb_attribute_by_linkID(schema, otherid);
+ if (!target_attr) {
+ ldb_asprintf_errstring(ldb,
+ "attribute %s does not have valid link target", el->name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* For each value being moded, we need to setup the modify */
+ for (j=0; j < el->num_values; j++) {
+ struct ldb_message_element *ret_el;
+ struct ldb_request *new_req;
+ struct ldb_message *new_msg;
+
+ /* Create a spot in the list for the requests */
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Create the modify request */
+ new_msg = ldb_msg_new(ac->down_req);
+ if (!new_msg) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ new_msg->dn = ldb_dn_from_ldb_val(new_msg, ldb, &el->values[j]);
+ if (!new_msg->dn) {
+ ldb_asprintf_errstring(ldb,
+ "attribute %s value %s was not a valid DN", msg->elements[i].name,
+ el->values[j].data);
+ return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+ }
+
+ if (olddn) {
+ ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
+ LDB_FLAG_MOD_DELETE, &ret_el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
+ if (!ret_el->values) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret_el->values[0] = data_blob_string_const(ldb_dn_get_linearized(olddn));
+ ret_el->num_values = 1;
+ }
+
+ if (newdn) {
+ ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
+ LDB_FLAG_MOD_ADD, &ret_el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret_el->values = talloc_array(new_msg, struct ldb_val, 1);
+ if (!ret_el->values) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret_el->values[0] = data_blob_string_const(ldb_dn_get_linearized(newdn));
+ ret_el->num_values = 1;
+ }
+
+ ret = ldb_build_mod_req(&new_req, ldb, ac->down_req,
+ new_msg,
+ NULL,
+ NULL,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(new_req, new_msg);
+
+ ldb_set_timeout_from_prev_req(ldb, ac->orig_req, new_req);
+
+ ac->down_req[ac->num_requests] = new_req;
+ ac->num_requests++;
+
+
+ /* Run the new request */
+ ret = ldb_next_request(ac->module, new_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+ return ret;
+}
+
+/* add */
+static int linked_attributes_add(struct ldb_module *module, struct ldb_request *req)
+{
+ int i;
+ struct linked_attributes_context *ac;
+
+ const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ /* without schema, this doesn't make any sense */
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+
+ ac = linked_attributes_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->step = LA_DO_OPS;
+
+ /* Need to ensure we only have forward links being specified */
+ for (i=0; i < req->op.add.message->num_elements; i++) {
+ const struct ldb_message_element *el = &req->op.add.message->elements[i];
+ const struct dsdb_attribute *schema_attr
+ = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (!schema_attr) {
+ ldb_asprintf_errstring(module->ldb,
+ "attribute %s is not a valid attribute in schema", req->op.add.message->elements[i].name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ /* We have a valid attribute, not find out if it is linked */
+ if (schema_attr->linkID == 0) {
+ continue;
+ }
+
+ if ((schema_attr->linkID & 1) == 1) {
+ /* Odd is for the target. Illigal to modify */
+ ldb_asprintf_errstring(module->ldb,
+ "attribute %s must not be modified directly, it is a linked attribute", req->op.add.message->elements[i].name);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Even link IDs are for the originating attribute */
+ }
+
+ /* Now call the common routine to setup the modifies across all the attributes */
+ return setup_modifies(module->ldb, ac, ac, req->op.add.message, NULL, req->op.add.message->dn);
+}
+
+struct merge {
+ struct ldb_dn *dn;
+ bool add;
+ bool ignore;
+};
+
+static int merge_cmp(struct merge *merge1, struct merge *merge2) {
+ int ret;
+ ret = ldb_dn_compare(merge1->dn, merge2->dn);
+ if (ret == 0) {
+ if (merge1->add == merge2->add) {
+ return 0;
+ }
+ if (merge1->add == true) {
+ return 1;
+ }
+ return -1;
+ }
+ return ret;
+}
+
+static int linked_attributes_mod_replace_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct replace_context *ac2 = talloc_get_type(context, struct replace_context);
+ struct linked_attributes_context *ac = ac2->ac;
+
+ /* OK, we have one search result here: */
+
+ /* Only entries are interesting, and we only want the olddn */
+ if (ares->type == LDB_REPLY_ENTRY
+ && ldb_dn_compare(ares->message->dn, ac->orig_req->op.mod.message->dn) == 0) {
+ /* only bother at all if there were some linked attributes found */
+ struct ldb_message_element *search_el
+ = ldb_msg_find_element(ares->message,
+ ac2->el->name);
+
+ /* See if this element already exists */
+ if (search_el) {
+
+ struct merge *merged_list = NULL;
+
+ int ret, size = 0, i;
+ struct ldb_message *msg = ldb_msg_new(ac);
+ if (!msg) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Add all the existing elements, marking as 'proposed for delete' by setting .add = false */
+ for (i=0; i < search_el->num_values; i++) {
+ merged_list = talloc_realloc(ares, merged_list, struct merge, size + 1);
+ merged_list[size].dn = ldb_dn_from_ldb_val(merged_list, ldb, &search_el->values[i]);
+ merged_list[size].add = false;
+ merged_list[size].ignore = false;
+ size++;
+ }
+
+ /* Add all the new replacement elements, marking as 'proposed for add' by setting .add = true */
+ for (i=0; i < ac2->el->num_values; i++) {
+ merged_list = talloc_realloc(ares, merged_list, struct merge, size + 1);
+ merged_list[size].dn = ldb_dn_from_ldb_val(merged_list, ldb, &ac2->el->values[i]);
+ merged_list[size].add = true;
+ merged_list[size].ignore = false;
+ size++;
+ }
+
+ /* Sort the list, so we can pick out an add and delete for the same DN, and eliminate them */
+ qsort(merged_list, size,
+ sizeof(*merged_list),
+ (comparison_fn_t)merge_cmp);
+
+ /* Now things are sorted, it is trivial to mark pairs of DNs as 'ignore' */
+ for (i=0; i + 1 < size; i++) {
+ if (ldb_dn_compare(merged_list[i].dn,
+ merged_list[i+1].dn) == 0
+ /* Fortunetly the sort also sorts 'add == false' first */
+ && merged_list[i].add == false
+ && merged_list[i+1].add == true) {
+
+ /* Mark as ignore, so we include neither in the actual operations */
+ merged_list[i].ignore = true;
+ merged_list[i+1].ignore = true;
+ }
+ }
+
+ /* Arrange to delete anything the search found that we don't re-add */
+ for (i=0; i < size; i++) {
+ if (merged_list[i].ignore == false
+ && merged_list[i].add == false) {
+ ldb_msg_add_steal_string(msg, search_el->name,
+ ldb_dn_get_linearized(merged_list[i].dn));
+ }
+ }
+
+ /* The DN to set on the linked attributes is the original DN of the modify message */
+ msg->dn = ac->orig_req->op.mod.message->dn;
+
+ ret = setup_modifies(ac->module->ldb, ac2, ac, msg, ares->message->dn, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Now add links for all the actually new elements */
+ for (i=0; i < size; i++) {
+ if (merged_list[i].ignore == false && merged_list[i].add == true) {
+ ldb_msg_add_steal_string(msg, search_el->name,
+ ldb_dn_get_linearized(merged_list[i].dn));
+ }
+ }
+
+ ret = setup_modifies(ac->module->ldb, ac2, ac, msg, NULL, ares->message->dn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_free(merged_list);
+
+ } else {
+ /* Looks like it doesn't exist, process like an 'add' */
+ struct ldb_message *msg = ldb_msg_new(ac);
+ if (!msg) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ msg->num_elements = 1;
+ msg->elements = ac2->el;
+ msg->dn = ac->orig_req->op.mod.message->dn;
+
+ return setup_modifies(ac->module->ldb, ac2, ac, msg, NULL, ac->orig_req->op.mod.message->dn);
+ }
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ } else if (ares->type == LDB_REPLY_ENTRY) {
+ /* Guh? We only asked for this DN */
+ return LDB_ERR_OPERATIONS_ERROR;
+
+ } else {
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+
+
+}
+/* modify */
+static int linked_attributes_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ /* Look over list of modifications */
+ /* Find if any are for linked attributes */
+ /* Determine the effect of the modification */
+ /* Apply the modify to the linked entry */
+
+ int i, j;
+ struct linked_attributes_context *ac;
+
+ const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ /* without schema, this doesn't make any sense */
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+
+ ac = linked_attributes_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* prepare the first operation */
+ ac->step = LA_DO_OPS;
+
+ for (i=0; i < req->op.mod.message->num_elements; i++) {
+ int ret;
+ struct ldb_request *new_req;
+ const struct dsdb_attribute *target_attr;
+ const struct ldb_message_element *el = &req->op.mod.message->elements[i];
+ const struct dsdb_attribute *schema_attr
+ = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (!schema_attr) {
+ ldb_asprintf_errstring(module->ldb,
+ "attribute %s is not a valid attribute in schema", req->op.mod.message->elements[i].name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ /* We have a valid attribute, not find out if it is linked */
+ if (schema_attr->linkID == 0) {
+ continue;
+ }
+
+ if ((schema_attr->linkID & 1) == 1) {
+ /* Odd is for the target. Illigal to modify */
+ ldb_asprintf_errstring(module->ldb,
+ "attribute %s must not be modified directly, it is a linked attribute", req->op.mod.message->elements[i].name);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Even link IDs are for the originating attribute */
+
+ /* Now find the target attribute */
+ target_attr = dsdb_attribute_by_linkID(schema, schema_attr->linkID + 1);
+ if (!target_attr) {
+ ldb_asprintf_errstring(module->ldb,
+ "attribute %s does not have valid link target", req->op.mod.message->elements[i].name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* Replace with new set of values */
+ if (((el->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_REPLACE)
+ && el->num_values > 0) {
+ struct replace_context *ac2 = talloc(ac, struct replace_context);
+ const char **attrs = talloc_array(ac, const char *, 2);
+ if (!attrs || !ac2) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ attrs[0] = el->name;
+ attrs[1] = NULL;
+
+ ac2->ac = ac;
+ ac2->el = el;
+
+ /* We need to setup a search, compare with the list, and then setup add/del as required */
+
+ /* The callback does all the hard work here */
+ ret = ldb_build_search_req(&new_req, module->ldb, req,
+ req->op.mod.message->dn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs,
+ NULL,
+ ac2,
+ linked_attributes_mod_replace_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(new_req, attrs);
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Create a spot in the list for the requests */
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
+ ac->num_requests++;
+
+ ret = ldb_next_request(module, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ continue;
+
+ /* Delete all values case */
+ } else if (((el->flags & LDB_FLAG_MOD_MASK) & (LDB_FLAG_MOD_DELETE|LDB_FLAG_MOD_REPLACE))
+ && el->num_values == 0) {
+ const char **attrs = talloc_array(ac, const char *, 2);
+ if (!attrs) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ attrs[0] = el->name;
+ attrs[1] = NULL;
+
+ /* We need to setup a search, and then setup del as required */
+
+ /* The callback does all the hard work here, acting identically to if we had delted the whole entry */
+ ret = ldb_build_search_req(&new_req, module->ldb, req,
+ req->op.mod.message->dn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs,
+ NULL,
+ ac,
+ linked_attributes_rename_del_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(new_req, attrs);
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Create a spot in the list for the requests */
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
+ ac->num_requests++;
+
+ ret = ldb_next_request(module, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ continue;
+ }
+
+ /* Prepare the modify (mod element) on the targets, for a normal modify request */
+
+ /* For each value being moded, we need to setup the modify */
+ for (j=0; j < el->num_values; j++) {
+ /* Create the modify request */
+ struct ldb_message *new_msg = ldb_msg_new(ac);
+ if (!new_msg) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ new_msg->dn = ldb_dn_from_ldb_val(new_msg, module->ldb, &el->values[j]);
+ if (!new_msg->dn) {
+ ldb_asprintf_errstring(module->ldb,
+ "attribute %s value %s was not a valid DN", req->op.mod.message->elements[i].name,
+ el->values[j].data);
+ return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+ }
+
+ ret = ldb_msg_add_empty(new_msg, target_attr->lDAPDisplayName,
+ el->flags & LDB_FLAG_MOD_MASK, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_msg_add_string(new_msg, target_attr->lDAPDisplayName,
+ ldb_dn_get_linearized(ac->orig_req->op.add.message->dn));
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_build_mod_req(&new_req, module->ldb, ac,
+ new_msg,
+ NULL,
+ NULL,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(new_req, new_msg);
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Now add it to the list */
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->down_req[ac->num_requests] = talloc_steal(ac->down_req, new_req);
+ ac->num_requests++;
+
+ /* Run the new request */
+ ret = ldb_next_request(module, new_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+static int linked_attributes_rename_del_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct linked_attributes_context *ac = talloc_get_type(context, struct linked_attributes_context);
+ struct ldb_dn *olddn, *newdn;
+
+ switch (ac->orig_req->operation) {
+ case LDB_DELETE:
+ {
+ olddn = ac->orig_req->op.del.dn;
+ newdn = NULL;
+ break;
+ }
+ /* This isn't the general modify case, just the modify when we are asked to delete all values */
+ case LDB_MODIFY:
+ {
+ olddn = ac->orig_req->op.mod.message->dn;
+ newdn = NULL;
+ break;
+ }
+ case LDB_RENAME:
+ {
+ olddn = ac->orig_req->op.rename.olddn;
+ newdn = ac->orig_req->op.rename.newdn;
+ break;
+ }
+ default:
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+
+ /* OK, we have one search result here: */
+
+ /* Only entries are interesting, and we only want the olddn */
+ if (ares->type == LDB_REPLY_ENTRY
+ && ldb_dn_compare(ares->message->dn, olddn) == 0) {
+ /* only bother at all if there were some linked attributes found */
+ if (ares->message->num_elements > 0) {
+ return setup_modifies(ldb, ac, ac,
+ ares->message, olddn, newdn);
+ }
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ } else if (ares->type == LDB_REPLY_ENTRY) {
+ /* Guh? We only asked for this DN */
+ return LDB_ERR_OPERATIONS_ERROR;
+
+ } else {
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+
+
+}
+/* rename */
+static int linked_attributes_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ /* Look up list of linked attributes */
+ const char **attrs;
+ WERROR werr;
+ int ret;
+ struct linked_attributes_context *ac;
+ struct ldb_request *new_req;
+ const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ /* without schema, this doesn't make any sense */
+ return ldb_next_request(module, req);
+ }
+
+ /* This gets complex: We need to:
+ - Do a search for the entry
+ - Wait for these result to appear
+ - In the callback for the result, issue a modify request based on the linked attributes found
+ - Wait for each modify result
+ - Regain our sainity
+ */
+
+ ac = linked_attributes_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ werr = dsdb_linked_attribute_lDAPDisplayName_list(schema, ac, &attrs);
+ if (!W_ERROR_IS_OK(werr)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_search_req(&new_req, module->ldb, req,
+ req->op.rename.olddn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs,
+ NULL,
+ ac,
+ linked_attributes_rename_del_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(new_req, attrs);
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->search_req = new_req;
+ ac->step = LA_SEARCH;
+ return ldb_next_request(module, new_req);
+}
+
+/* delete */
+static int linked_attributes_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ /* Look up list of linked attributes */
+ const char **attrs;
+ WERROR werr;
+ int ret;
+ struct ldb_request *new_req;
+ struct linked_attributes_context *ac;
+ const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ /* without schema, this doesn't make any sense */
+ return ldb_next_request(module, req);
+ }
+
+ /* This gets complex: We need to:
+ - Do a search for the entry
+ - Wait for these result to appear
+ - In the callback for the result, issue a modify request based on the linked attributes found
+ - Wait for each modify result
+ - Regain our sainity
+ */
+
+ ac = linked_attributes_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ werr = dsdb_linked_attribute_lDAPDisplayName_list(schema, ac, &attrs);
+ if (!W_ERROR_IS_OK(werr)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ };
+
+ ret = ldb_build_search_req(&new_req, module->ldb, req,
+ req->op.del.dn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs,
+ NULL,
+ ac,
+ linked_attributes_rename_del_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(new_req, attrs);
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->search_req = new_req;
+ ac->step = LA_SEARCH;
+ return ldb_next_request(module, new_req);
+}
+
+
+static int linked_attributes_wait_none(struct ldb_handle *handle) {
+ struct linked_attributes_context *ac;
+ int i, ret = LDB_ERR_OPERATIONS_ERROR;
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct linked_attributes_context);
+
+ switch (ac->step) {
+ case LA_SEARCH:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+ ac->step = LA_DO_OPS;
+ return LDB_SUCCESS;
+
+ case LA_DO_OPS:
+ for (i=0; i < ac->num_requests; i++) {
+ ret = ldb_wait(ac->down_req[i]->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req[i]->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req[i]->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req[i]->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+ }
+
+ /* Now run the original request */
+ ac->step = LA_DO_ORIG;
+ return ldb_next_request(ac->module, ac->orig_down_req);
+
+ case LA_DO_ORIG:
+ ret = ldb_wait(ac->orig_down_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->orig_down_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->orig_down_req->handle->status;
+ goto done;
+ }
+
+ if (ac->orig_down_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+ ret = LDB_SUCCESS;
+ }
+
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+
+}
+
+static int linked_attributes_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = linked_attributes_wait_none(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int linked_attributes_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return linked_attributes_wait_all(handle);
+ } else {
+ return linked_attributes_wait_none(handle);
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_linked_attributes_module_ops = {
+ .name = "linked_attributes",
+ .add = linked_attributes_add,
+ .modify = linked_attributes_modify,
+ .del = linked_attributes_delete,
+ .rename = linked_attributes_rename,
+ .wait = linked_attributes_wait,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/local_password.c b/source4/dsdb/samdb/ldb_modules/local_password.c
new file mode 100644
index 0000000000..a411c01513
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/local_password.c
@@ -0,0 +1,852 @@
+/*
+ ldb database module
+
+ Copyright (C) Simo Sorce 2004-2006
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2006
+ Copyright (C) Andrew Tridgell 2004
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb local_password module
+ *
+ * Description: correctly update hash values based on changes to userPassword and friends
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "libcli/ldap/ldap.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/ndr/libndr.h"
+#include "dsdb/samdb/ldb_modules/password_modules.h"
+
+#define PASSWORD_GUID_ATTR "masterGUID"
+
+/* This module maintains a local password database, seperate from the main LDAP server.
+
+ This allows the password database to be syncronised in a multi-master
+ fashion, seperate to the more difficult concerns of the main
+ database. (With passwords, the last writer always wins)
+
+ Each incoming add/modify is split into a remote, and a local request, done in that order.
+
+ We maintain a list of attributes that are kept locally:
+ */
+
+static const char * const password_attrs[] = {
+ "supplementalCredentials",
+ "unicodePwd",
+ "dBCSPwd",
+ "lmPwdHistory",
+ "ntPwdHistory",
+ "msDS-KeyVersionNumber",
+ "pwdLastSet"
+};
+
+/* And we merge them back into search requests when asked to do so */
+
+struct lpdb_context {
+
+ enum lpdb_type {LPDB_ADD, LPDB_MOD, LPDB_SEARCH} type;
+ enum lpdb_step {LPDB_ADD_REMOTE, LPDB_MOD_REMOTE, LPDB_MOD_SEARCH_SELF, LPDB_LOCAL, LPDB_SEARCH_REMOTE} step;
+
+ struct ldb_module *module;
+ struct ldb_request *orig_req;
+ struct ldb_request *remote_req;
+ struct ldb_request *search_req;
+ struct ldb_request *local_req;
+
+ struct ldb_message *local_message;
+
+ bool added_objectGUID;
+ bool added_objectClass;
+
+ struct ldb_reply *search_res;
+};
+
+struct lpdb_local_search_context {
+ struct lpdb_context *ac;
+ struct ldb_reply *remote_res;
+ struct ldb_reply *local_res;
+};
+
+static struct ldb_handle *lpdb_init_handle(struct ldb_request *req, struct ldb_module *module, enum lpdb_type type)
+{
+ struct lpdb_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct lpdb_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = (void *)ac;
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->type = type;
+ ac->module = module;
+ ac->orig_req = req;
+
+ return h;
+}
+
+/* Add a record, splitting password attributes from the user's main
+ * record */
+
+static int local_password_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_handle *h;
+ struct lpdb_context *ac;
+ struct ldb_message *remote_message;
+ struct ldb_message *local_message;
+ struct GUID objectGUID;
+ int i;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "local_password_add\n");
+
+ if (ldb_dn_is_special(req->op.add.message->dn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* If the caller is manipulating the local passwords directly, let them pass */
+ if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE),
+ req->op.add.message->dn) == 0) {
+ return ldb_next_request(module, req);
+ }
+
+ for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
+ if (ldb_msg_find_element(req->op.add.message, password_attrs[i])) {
+ break;
+ }
+ }
+
+ /* It didn't match any of our password attributes, go on */
+ if (i == ARRAY_SIZE(password_attrs)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* TODO: remove this when userPassword will be in schema */
+ if (!ldb_msg_check_string_attribute(req->op.add.message, "objectClass", "person")) {
+ ldb_asprintf_errstring(module->ldb,
+ "Cannot relocate a password on entry: %s, does not have objectClass 'person'",
+ ldb_dn_get_linearized(req->op.add.message->dn));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* From here, we assume we have password attributes to split off */
+ h = lpdb_init_handle(req, module, LPDB_ADD);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct lpdb_context);
+
+ ac->orig_req = req;
+
+ ac->remote_req = talloc(ac, struct ldb_request);
+ if (ac->remote_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->remote_req) = *(ac->orig_req);
+
+ remote_message = ldb_msg_copy_shallow(ac->remote_req, ac->orig_req->op.add.message);
+ if (remote_message == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Remove any password attributes from the remote message */
+ for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
+ ldb_msg_remove_attr(remote_message, password_attrs[i]);
+ }
+
+ ac->remote_req->op.add.message = remote_message;
+
+ ac->remote_req->context = NULL;
+ ac->remote_req->callback = NULL;
+
+ ac->local_req = talloc(ac, struct ldb_request);
+ if (ac->local_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->local_req) = *(ac->orig_req);
+ local_message = ldb_msg_copy_shallow(ac->local_req, ac->orig_req->op.add.message);
+ if (local_message == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Remove anything seen in the remote message from the local
+ * message (leaving only password attributes) */
+ for (i=0;i<ac->remote_req->op.add.message->num_elements;i++) {
+ ldb_msg_remove_attr(local_message, ac->remote_req->op.add.message->elements[i].name);
+ }
+
+ /* We must have an objectGUID already, or we don't know where
+ * to add the password. This may be changed to an 'add and
+ * search', to allow the directory to create the objectGUID */
+ if (ldb_msg_find_ldb_val(ac->orig_req->op.add.message, "objectGUID") == NULL) {
+ ldb_set_errstring(module->ldb,
+ "no objectGUID found in search: local_password module must be configured below objectGUID module!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ /* Find the objectGUID to use as the key */
+ objectGUID = samdb_result_guid(ac->orig_req->op.add.message, "objectGUID");
+
+ local_message->dn = ldb_dn_new(local_message, module->ldb, LOCAL_BASE);
+ ldb_dn_add_child_fmt(local_message->dn, PASSWORD_GUID_ATTR "=%s", GUID_string(local_message, &objectGUID));
+
+ ac->local_req->op.add.message = local_message;
+
+ ac->local_req->context = NULL;
+ ac->local_req->callback = NULL;
+
+ ac->step = LPDB_ADD_REMOTE;
+
+ /* Return our own handle do deal with this call */
+ req->handle = h;
+
+ return ldb_next_request(module, ac->remote_req);
+}
+
+/* After adding the remote entry, add the local one */
+static int local_password_add_local(struct ldb_handle *h) {
+
+ struct lpdb_context *ac;
+ ac = talloc_get_type(h->private_data, struct lpdb_context);
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = LPDB_LOCAL;
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->local_req);
+
+ /* perform the local add */
+ return ldb_next_request(ac->module, ac->local_req);
+}
+
+static int local_password_mod_search_self(struct ldb_handle *h);
+
+static int local_password_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_handle *h;
+ struct lpdb_context *ac;
+ struct ldb_message *remote_message;
+ struct ldb_message *local_message;
+ int i;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "local_password_modify\n");
+
+ if (ldb_dn_is_special(req->op.mod.message->dn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* If the caller is manipulating the local passwords directly, let them pass */
+ if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE),
+ req->op.mod.message->dn) == 0) {
+ return ldb_next_request(module, req);
+ }
+
+ for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
+ if (ldb_msg_find_element(req->op.add.message, password_attrs[i])) {
+ break;
+ }
+ }
+
+ /* It didn't match any of our password attributes, then we have nothing to do here */
+ if (i == ARRAY_SIZE(password_attrs)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* From here, we assume we have password attributes to split off */
+ h = lpdb_init_handle(req, module, LPDB_MOD);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct lpdb_context);
+
+ ac->orig_req = req;
+
+ ac->remote_req = talloc(ac, struct ldb_request);
+ if (ac->remote_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->remote_req) = *(ac->orig_req);
+ remote_message = ldb_msg_copy_shallow(ac->remote_req, ac->orig_req->op.mod.message);
+ if (remote_message == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Remove any password attributes from the remote message */
+ for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
+ ldb_msg_remove_attr(remote_message, password_attrs[i]);
+ }
+
+ ac->remote_req->op.mod.message = remote_message;
+
+ ac->remote_req->context = NULL;
+ ac->remote_req->callback = NULL;
+
+ ac->local_req = talloc(ac, struct ldb_request);
+ if (ac->local_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->local_req) = *(ac->orig_req);
+ local_message = ldb_msg_copy_shallow(ac->local_req, ac->orig_req->op.mod.message);
+ if (local_message == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Remove anything seen in the remote message from the local
+ * message (leaving only password attributes) */
+ for (i=0;i<ac->remote_req->op.mod.message->num_elements;i++) {
+ ldb_msg_remove_attr(local_message, ac->remote_req->op.mod.message->elements[i].name);
+ }
+
+ ac->local_req->op.mod.message = local_message;
+ ac->local_message = local_message;
+
+ ac->local_req->context = NULL;
+ ac->local_req->callback = NULL;
+
+ ac->step = LPDB_MOD_REMOTE;
+
+ /* Return our own handle do deal with this call */
+ req->handle = h;
+
+ return ldb_next_request(module, ac->remote_req);
+}
+
+/* Called when we search for our oen entry. Stores the one entry we
+ * expect (as it is a base search) on the context pointer */
+static int get_self_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct lpdb_context *ac;
+
+ ac = talloc_get_type(context, struct lpdb_context);
+
+ /* we are interested only in the single reply (base search) we receive here */
+ if (ares->type == LDB_REPLY_ENTRY) {
+ if (ac->search_res != NULL) {
+ ldb_set_errstring(ldb, "Too many results");
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->search_res = talloc_steal(ac, ares);
+ } else {
+ talloc_free(ares);
+ }
+
+ return LDB_SUCCESS;
+}
+
+/* On a modify, we don't have the objectGUID handy, so we need to
+ * search our DN for it */
+static int local_password_mod_search_self(struct ldb_handle *h) {
+
+ struct lpdb_context *ac;
+ static const char * const attrs[] = { "objectGUID", "objectClass", NULL };
+
+ ac = talloc_get_type(h->private_data, struct lpdb_context);
+
+ /* prepare the search operation */
+ ac->search_req = talloc_zero(ac, struct ldb_request);
+ if (ac->search_req == NULL) {
+ ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->search_req->operation = LDB_SEARCH;
+ ac->search_req->op.search.base = ac->orig_req->op.mod.message->dn;
+ ac->search_req->op.search.scope = LDB_SCOPE_BASE;
+ ac->search_req->op.search.tree = ldb_parse_tree(ac->orig_req, NULL);
+ if (ac->search_req->op.search.tree == NULL) {
+ ldb_set_errstring(ac->module->ldb, "Invalid search filter");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->search_req->op.search.attrs = attrs;
+ ac->search_req->controls = NULL;
+ ac->search_req->context = ac;
+ ac->search_req->callback = get_self_callback;
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
+
+ ac->step = LPDB_MOD_SEARCH_SELF;
+
+ return ldb_next_request(ac->module, ac->search_req);
+}
+
+/* After we find out the objectGUID for the entry, modify the local
+ * password database as required */
+static int local_password_mod_local(struct ldb_handle *h) {
+
+ struct lpdb_context *ac;
+ struct GUID objectGUID;
+ ac = talloc_get_type(h->private_data, struct lpdb_context);
+
+ /* if it is not an entry of type person this is an error */
+ /* TODO: remove this when these things are checked in the schema */
+ if (!ac->search_res) {
+ ldb_asprintf_errstring(ac->module->ldb,
+ "entry just modified (%s) not found!",
+ ldb_dn_get_linearized(ac->remote_req->op.mod.message->dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (!ldb_msg_check_string_attribute(ac->search_res->message, "objectClass", "person")) {
+ /* Not relevent to us */
+ return LDB_SUCCESS;
+ }
+
+ if (ldb_msg_find_ldb_val(ac->search_res->message, "objectGUID") == NULL) {
+ ldb_set_errstring(ac->module->ldb,
+ "no objectGUID found in search: local_password module must be configured below objectGUID module!\n");
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ objectGUID = samdb_result_guid(ac->search_res->message, "objectGUID");
+
+ ac->local_message->dn = ldb_dn_new(ac, ac->module->ldb, LOCAL_BASE);
+ ldb_dn_add_child_fmt(ac->local_message->dn, PASSWORD_GUID_ATTR "=%s", GUID_string(ac, &objectGUID));
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = LPDB_LOCAL;
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->local_req);
+
+ /* perform the local update */
+ return ldb_next_request(ac->module, ac->local_req);
+}
+
+
+static int lpdb_local_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct lpdb_local_search_context *local_context;
+
+ local_context = talloc_get_type(context, struct lpdb_local_search_context);
+
+ /* we are interested only in the single reply (base search) we receive here */
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ {
+ int i;
+ if (local_context->local_res != NULL) {
+ ldb_set_errstring(ldb, "Too many results to base search for password entry!");
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ local_context->local_res = ares;
+
+ /* Make sure never to return the internal key attribute to the caller */
+ ldb_msg_remove_attr(ares->message, PASSWORD_GUID_ATTR);
+
+ talloc_steal(local_context->remote_res->message->elements, ares->message->elements);
+ for (i=0; i < ares->message->num_elements; i++) {
+ struct ldb_message_element *el;
+
+ el = ldb_msg_find_element(local_context->remote_res->message,
+ ares->message->elements[i].name);
+ if (!el) {
+ if (ldb_msg_add_empty(local_context->remote_res->message,
+ ares->message->elements[i].name, 0, &el) != LDB_SUCCESS) {
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *el = ares->message->elements[i];
+ }
+ }
+ return local_context->ac->orig_req->callback(ldb,
+ local_context->ac->orig_req->context,
+ local_context->remote_res);
+ }
+ case LDB_REPLY_DONE:
+ {
+ /* Fire off the callback if there was no local entry, so we get the rest returned */
+ if (local_context->local_res == NULL) {
+ return local_context->ac->orig_req->callback(ldb,
+ local_context->ac->orig_req->context,
+ local_context->remote_res);
+ }
+ return LDB_SUCCESS;
+ break;
+ }
+ default:
+ {
+ talloc_free(ares);
+ ldb_set_errstring(ldb, "Unexpected result type in base search for password entry!");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+}
+
+/* For each entry returned in a remote search, do a local base search,
+ * based on the objectGUID we asked for as an additional attribute */
+static int lpdb_remote_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct lpdb_context *ac;
+
+ ac = talloc_get_type(context, struct lpdb_context);
+
+ if (ares->type == LDB_REPLY_ENTRY) {
+ struct ldb_request *req;
+ struct lpdb_local_search_context *local_context;
+ struct GUID objectGUID;
+
+ /* No point searching further if it's not a 'person' entry */
+ if (!ldb_msg_check_string_attribute(ares->message, "objectClass", "person")) {
+
+ /* Make sure to remove anything we added */
+ if (ac->added_objectGUID) {
+ ldb_msg_remove_attr(ares->message, "objectGUID");
+ }
+
+ if (ac->added_objectClass) {
+ ldb_msg_remove_attr(ares->message, "objectClass");
+ }
+
+ return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
+ }
+
+ if (ldb_msg_find_ldb_val(ares->message, "objectGUID") == NULL) {
+ ldb_set_errstring(ac->module->ldb,
+ "no objectGUID found in search: local_password module must be configured below objectGUID module!\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ objectGUID = samdb_result_guid(ares->message, "objectGUID");
+
+ if (ac->added_objectGUID) {
+ ldb_msg_remove_attr(ares->message, "objectGUID");
+ }
+
+ if (ac->added_objectClass) {
+ ldb_msg_remove_attr(ares->message, "objectClass");
+ }
+
+ req = talloc_zero(ac, struct ldb_request);
+ if (!req) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ local_context = talloc(ac, struct lpdb_local_search_context);
+ if (!local_context) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ local_context->ac = ac;
+ local_context->remote_res = ares;
+ local_context->local_res = NULL;
+
+ req->op.search.base = ldb_dn_new(ac, ac->module->ldb, LOCAL_BASE);
+ if ( ! ldb_dn_add_child_fmt(req->op.search.base, PASSWORD_GUID_ATTR "=%s", GUID_string(ac, &objectGUID))) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ req->operation = LDB_SEARCH;
+ req->op.search.scope = LDB_SCOPE_BASE;
+ req->op.search.tree = ldb_parse_tree(req, NULL);
+ if (req->op.search.tree == NULL) {
+ ldb_set_errstring(ac->module->ldb, "Out of Memory");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ req->op.search.attrs = ac->orig_req->op.search.attrs;
+ req->controls = NULL;
+ req->context = ac;
+ req->callback = get_self_callback;
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, req);
+
+ req->context = local_context;
+ req->callback = lpdb_local_search_callback;
+
+ return ldb_next_request(ac->module, req);
+ } else {
+ return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
+ }
+}
+
+/* Search for passwords and other attributes. The passwords are
+ * local, but the other attributes are remote, and we need to glue the
+ * two search spaces back togeather */
+
+static int local_password_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_handle *h;
+ struct lpdb_context *ac;
+ int i;
+ int ret;
+ const char * const *search_attrs = NULL;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "local_password_search\n");
+
+ if (ldb_dn_is_special(req->op.search.base)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* If the caller is searching for the local passwords directly, let them pass */
+ if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE),
+ req->op.search.base) == 0) {
+ return ldb_next_request(module, req);
+ }
+
+ if (req->op.search.attrs && (!ldb_attr_in_list(req->op.search.attrs, "*"))) {
+ for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
+ if (ldb_attr_in_list(req->op.search.attrs, password_attrs[i])) {
+ break;
+ }
+ }
+
+ /* It didn't match any of our password attributes, go on */
+ if (i == ARRAY_SIZE(password_attrs)) {
+ return ldb_next_request(module, req);
+ }
+ }
+
+ h = lpdb_init_handle(req, module, LPDB_SEARCH);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac = talloc_get_type(h->private_data, struct lpdb_context);
+
+ ac->orig_req = req;
+
+ ac->remote_req = talloc(ac, struct ldb_request);
+ if (ac->remote_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Remote search is for all attributes: if the remote LDAP server has these attributes, then it overrides the local database */
+ *(ac->remote_req) = *(ac->orig_req);
+
+ /* Return our own handle do deal with this call */
+ ac->remote_req->handle = h;
+
+ ac->remote_req->context = ac;
+ ac->remote_req->callback = lpdb_remote_search_callback;
+
+ if (req->op.search.attrs && !ldb_attr_in_list(req->op.search.attrs, "*")) {
+ if (!ldb_attr_in_list(req->op.search.attrs, "objectGUID")) {
+ search_attrs = ldb_attr_list_copy_add(req, req->op.search.attrs, "objectGUID");
+ ac->added_objectGUID = true;
+ if (!search_attrs) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ } else {
+ search_attrs = req->op.search.attrs;
+ }
+ if (!ldb_attr_in_list(search_attrs, "objectClass")) {
+ search_attrs = ldb_attr_list_copy_add(req, search_attrs, "objectClass");
+ ac->added_objectClass = true;
+ if (!search_attrs) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+ } else {
+ search_attrs = req->op.search.attrs;
+ }
+
+ ac->remote_req->op.search.attrs = search_attrs;
+
+ ldb_set_timeout_from_prev_req(module->ldb, ac->orig_req, ac->remote_req);
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = LPDB_SEARCH_REMOTE;
+
+ /* perform the search */
+ ret = ldb_next_request(module, ac->remote_req);
+
+ if (ret == LDB_SUCCESS) {
+ req->handle = ac->remote_req->handle;
+ }
+
+ return ret;
+}
+
+static int lpdb_wait(struct ldb_handle *handle) {
+ struct lpdb_context *ac;
+ int ret;
+
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct lpdb_context);
+
+ switch (ac->step) {
+ case LPDB_ADD_REMOTE:
+ ret = ldb_wait(ac->remote_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->remote_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->remote_req->handle->status;
+ goto done;
+ }
+
+ if (ac->remote_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* original request done, go on */
+ return local_password_add_local(handle);
+
+ case LPDB_MOD_REMOTE:
+ ret = ldb_wait(ac->remote_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->remote_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->remote_req->handle->status;
+ goto done;
+ }
+
+ if (ac->remote_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* original request done, go on */
+ return local_password_mod_search_self(handle);
+
+ case LPDB_MOD_SEARCH_SELF:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* original request done, go on */
+ return local_password_mod_local(handle);
+
+ case LPDB_LOCAL:
+ ret = ldb_wait(ac->local_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->local_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->local_req->handle->status;
+ goto done;
+ }
+
+ if (ac->local_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ case LPDB_SEARCH_REMOTE:
+ ret = ldb_wait(ac->remote_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->remote_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->remote_req->handle->status;
+ goto done;
+ }
+
+ if (ac->remote_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ default:
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ ret = LDB_SUCCESS;
+
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+}
+
+static int lpdb_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = lpdb_wait(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int local_password_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return lpdb_wait_all(handle);
+ } else {
+ return lpdb_wait(handle);
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_local_password_module_ops = {
+ .name = "local_password",
+ .add = local_password_add,
+ .modify = local_password_modify,
+ .search = local_password_search,
+ .wait = local_password_wait
+};
diff --git a/source4/dsdb/samdb/ldb_modules/naming_fsmo.c b/source4/dsdb/samdb/ldb_modules/naming_fsmo.c
new file mode 100644
index 0000000000..084540f68d
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/naming_fsmo.c
@@ -0,0 +1,123 @@
+/*
+ Unix SMB/CIFS mplementation.
+
+ The module that handles the Domain Naming FSMO Role Owner
+ checkings
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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 "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb_private.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 "lib/util/dlinklist.h"
+
+static int naming_fsmo_init(struct ldb_module *module)
+{
+ TALLOC_CTX *mem_ctx;
+ struct ldb_dn *naming_dn;
+ struct dsdb_naming_fsmo *naming_fsmo;
+ struct ldb_result *naming_res;
+ int ret;
+ static const char *naming_attrs[] = {
+ "fSMORoleOwner",
+ NULL
+ };
+
+ mem_ctx = talloc_new(module);
+ if (!mem_ctx) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ naming_dn = samdb_partitions_dn(module->ldb, mem_ctx);
+ if (!naming_dn) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "naming_fsmo_init: no partitions dn present: (skip loading of naming contexts details)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ }
+
+ naming_fsmo = talloc_zero(mem_ctx, struct dsdb_naming_fsmo);
+ if (!naming_fsmo) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ module->private_data = naming_fsmo;
+
+ ret = ldb_search(module->ldb, naming_dn,
+ LDB_SCOPE_BASE,
+ NULL, naming_attrs,
+ &naming_res);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "naming_fsmo_init: no partitions dn present: (skip loading of naming contexts details)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ }
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "naming_fsmo_init: failed to search the cross-ref container: %s: %s",
+ ldb_strerror(ret), ldb_errstring(module->ldb));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ talloc_steal(mem_ctx, naming_res);
+ if (naming_res->count == 0) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "naming_fsmo_init: no cross-ref container present: (skip loading of naming contexts details)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ } else if (naming_res->count > 1) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "naming_fsmo_init: [%u] cross-ref containers found on a base search",
+ naming_res->count);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ naming_fsmo->master_dn = ldb_msg_find_attr_as_dn(module->ldb, naming_fsmo, naming_res->msgs[0], "fSMORoleOwner");
+ if (ldb_dn_compare(samdb_ntds_settings_dn(module->ldb), naming_fsmo->master_dn) == 0) {
+ naming_fsmo->we_are_master = true;
+ } else {
+ naming_fsmo->we_are_master = false;
+ }
+
+ if (ldb_set_opaque(module->ldb, "dsdb_naming_fsmo", naming_fsmo) != LDB_SUCCESS) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_steal(module, naming_fsmo);
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE,
+ "naming_fsmo_init: we are master: %s\n",
+ (naming_fsmo->we_are_master?"yes":"no"));
+
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_naming_fsmo_module_ops = {
+ .name = "naming_fsmo",
+ .init_context = naming_fsmo_init
+};
diff --git a/source4/dsdb/samdb/ldb_modules/normalise.c b/source4/dsdb/samdb/ldb_modules/normalise.c
new file mode 100644
index 0000000000..3306fd3c33
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/normalise.c
@@ -0,0 +1,162 @@
+/*
+ ldb database library
+
+ Copyright (C) Amdrew Bartlett <abartlet@samba.org> 2007-2008
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb normalisation module
+ *
+ * Description: module to ensure all DNs and attribute names are normalised
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+
+/* Fix up the DN to be in the standard form, taking particular care to match the parent DN
+
+ This should mean that if the parent is:
+ CN=Users,DC=samba,DC=example,DC=com
+ and a proposed child is
+ cn=Admins ,cn=USERS,dc=Samba,dc=example,dc=COM
+
+ The resulting DN should be:
+
+ CN=Admins,CN=Users,DC=samba,DC=example,DC=com
+
+ */
+static int fix_dn(struct ldb_dn *dn)
+{
+ int i, ret;
+ char *upper_rdn_attr;
+
+ for (i=0; i < ldb_dn_get_comp_num(dn); i++) {
+ /* We need the attribute name in upper case */
+ upper_rdn_attr = strupper_talloc(dn,
+ ldb_dn_get_component_name(dn, i));
+ if (!upper_rdn_attr) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* And replace it with CN=foo (we need the attribute in upper case */
+ ret = ldb_dn_set_component(dn, i, upper_rdn_attr,
+ *ldb_dn_get_component_val(dn, i));
+ talloc_free(upper_rdn_attr);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+static int normalise_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb);
+ struct ldb_request *orig_req = talloc_get_type(context, struct ldb_request);
+ TALLOC_CTX *mem_ctx;
+ int i, j, ret;
+
+ /* Only entries are interesting, and we handle the case of the parent seperatly */
+ if (ares->type != LDB_REPLY_ENTRY) {
+ return orig_req->callback(ldb, orig_req->context, ares);
+ }
+
+ if (!schema) {
+ return orig_req->callback(ldb, orig_req->context, ares);
+ }
+
+ mem_ctx = talloc_new(ares);
+ if (!mem_ctx) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* OK, we have one of *many* search results passing by here,
+ * but we should get them one at a time */
+
+ ret = fix_dn(ares->message->dn);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ for (i = 0; i < ares->message->num_elements; i++) {
+ const struct dsdb_attribute *attribute = dsdb_attribute_by_lDAPDisplayName(schema, ares->message->elements[i].name);
+ if (!attribute) {
+ continue;
+ }
+ /* Look to see if this attributeSyntax is a DN */
+ if (!((strcmp(attribute->attributeSyntax_oid, "2.5.5.1") == 0) ||
+ (strcmp(attribute->attributeSyntax_oid, "2.5.5.7") == 0))) {
+ continue;
+ }
+ for (j = 0; j < ares->message->elements[i].num_values; j++) {
+ const char *dn_str;
+ struct ldb_dn *dn = ldb_dn_from_ldb_val(mem_ctx, ldb, &ares->message->elements[i].values[j]);
+ if (!dn) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = fix_dn(dn);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ dn_str = talloc_steal(ares->message->elements[i].values, ldb_dn_get_linearized(dn));
+ ares->message->elements[i].values[j] = data_blob_string_const(dn_str);
+ talloc_free(dn);
+ }
+ }
+ talloc_free(mem_ctx);
+ return orig_req->callback(ldb, orig_req->context, ares);
+}
+
+/* search */
+static int normalise_search(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ struct ldb_request *down_req = talloc(req, struct ldb_request);
+ if (!down_req) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+ down_req->context = req;
+ down_req->callback = normalise_search_callback;
+
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+ return ret;
+}
+
+
+_PUBLIC_ const struct ldb_module_ops ldb_normalise_module_ops = {
+ .name = "normalise",
+ .search = normalise_search,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/objectclass.c b/source4/dsdb/samdb/ldb_modules/objectclass.c
new file mode 100644
index 0000000000..b048a8d8e1
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/objectclass.c
@@ -0,0 +1,1207 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2006
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: objectClass sorting module
+ *
+ * Description:
+ * - sort the objectClass attribute into the class
+ * hierarchy,
+ * - fix DNs and attributes into 'standard' case
+ * - Add objectCategory and ntSecurityDescriptor defaults
+ *
+ * Author: Andrew Bartlett
+ */
+
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+#include "lib/util/dlinklist.h"
+#include "librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "libcli/security/security.h"
+#include "auth/auth.h"
+#include "param/param.h"
+
+struct oc_context {
+
+ enum oc_step {OC_DO_REQ, OC_SEARCH_SELF, OC_DO_MOD,
+ OC_SEARCH_ADD_PARENT, OC_DO_ADD,
+ OC_SEARCH_RENAME_PARENT, OC_DO_RENAME} step;
+
+ struct ldb_module *module;
+ struct ldb_request *orig_req;
+
+ struct ldb_request *down_req;
+
+ struct ldb_request *search_req;
+ struct ldb_reply *search_res;
+
+ struct ldb_request *add_req;
+ struct ldb_request *mod_req;
+ struct ldb_request *rename_req;
+};
+
+struct class_list {
+ struct class_list *prev, *next;
+ const struct dsdb_class *objectclass;
+};
+
+static int objectclass_do_add(struct ldb_handle *h);
+
+static struct ldb_handle *oc_init_handle(struct ldb_request *req, struct ldb_module *module)
+{
+ struct oc_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct oc_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = (void *)ac;
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->module = module;
+ ac->orig_req = req;
+
+ return h;
+}
+
+/* Sort objectClasses into correct order, and validate that all
+ * objectClasses specified actually exist in the schema
+ */
+
+static int objectclass_sort(struct ldb_module *module,
+ const struct dsdb_schema *schema,
+ struct ldb_message *msg, /* so that when we create new elements, we put it on the right parent */
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *objectclass_element,
+ struct class_list **sorted_out)
+{
+ int i;
+ int layer;
+ struct class_list *sorted = NULL, *parent_class = NULL,
+ *subclass = NULL, *unsorted = NULL, *current, *poss_subclass, *poss_parent, *new_parent;
+ /* DESIGN:
+ *
+ * We work on 4 different 'bins' (implemented here as linked lists):
+ *
+ * * sorted: the eventual list, in the order we wish to push
+ * into the database. This is the only ordered list.
+ *
+ * * parent_class: The current parent class 'bin' we are
+ * trying to find subclasses for
+ *
+ * * subclass: The subclasses we have found so far
+ *
+ * * unsorted: The remaining objectClasses
+ *
+ * The process is a matter of filtering objectClasses up from
+ * unsorted into sorted. Order is irrelevent in the later 3 'bins'.
+ *
+ * We start with 'top' (found and promoted to parent_class
+ * initially). Then we find (in unsorted) all the direct
+ * subclasses of 'top'. parent_classes is concatenated onto
+ * the end of 'sorted', and subclass becomes the list in
+ * parent_class.
+ *
+ * We then repeat, until we find no more subclasses. Any left
+ * over classes are added to the end.
+ *
+ */
+
+ /* Firstly, dump all the objectClass elements into the
+ * unsorted bin, except for 'top', which is special */
+ for (i=0; i < objectclass_element->num_values; i++) {
+ current = talloc(mem_ctx, struct class_list);
+ if (!current) {
+ ldb_set_errstring(module->ldb, "objectclass: out of memory allocating objectclass list");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ current->objectclass = dsdb_class_by_lDAPDisplayName(schema, (const char *)objectclass_element->values[i].data);
+ if (!current->objectclass) {
+ ldb_asprintf_errstring(module->ldb, "objectclass %s is not a valid objectClass in schema", (const char *)objectclass_element->values[i].data);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* this is the root of the tree. We will start
+ * looking for subclasses from here */
+ if (ldb_attr_cmp("top", current->objectclass->lDAPDisplayName) == 0) {
+ DLIST_ADD_END(parent_class, current, struct class_list *);
+ } else {
+ DLIST_ADD_END(unsorted, current, struct class_list *);
+ }
+ }
+
+ if (parent_class == NULL) {
+ current = talloc(mem_ctx, struct class_list);
+ current->objectclass = dsdb_class_by_lDAPDisplayName(schema, "top");
+ DLIST_ADD_END(parent_class, current, struct class_list *);
+ }
+
+ /* For each object: find parent chain */
+ for (current = unsorted; schema && current; current = current->next) {
+ for (poss_parent = unsorted; poss_parent; poss_parent = poss_parent->next) {
+ if (ldb_attr_cmp(poss_parent->objectclass->lDAPDisplayName, current->objectclass->subClassOf) == 0) {
+ break;
+ }
+ }
+ /* If we didn't get to the end of the list, we need to add this parent */
+ if (poss_parent || (ldb_attr_cmp("top", current->objectclass->subClassOf) == 0)) {
+ continue;
+ }
+
+ new_parent = talloc(mem_ctx, struct class_list);
+ new_parent->objectclass = dsdb_class_by_lDAPDisplayName(schema, current->objectclass->subClassOf);
+ DLIST_ADD_END(unsorted, new_parent, struct class_list *);
+ }
+
+ /* DEBUGGING aid: how many layers are we down now? */
+ layer = 0;
+ do {
+ layer++;
+ /* Find all the subclasses of classes in the
+ * parent_classes. Push them onto the subclass list */
+
+ /* Ensure we don't bother if there are no unsorted entries left */
+ for (current = parent_class; schema && unsorted && current; current = current->next) {
+ /* Walk the list of possible subclasses in unsorted */
+ for (poss_subclass = unsorted; poss_subclass; ) {
+ struct class_list *next;
+
+ /* Save the next pointer, as the DLIST_ macros will change poss_subclass->next */
+ next = poss_subclass->next;
+
+ if (ldb_attr_cmp(poss_subclass->objectclass->subClassOf, current->objectclass->lDAPDisplayName) == 0) {
+ DLIST_REMOVE(unsorted, poss_subclass);
+ DLIST_ADD(subclass, poss_subclass);
+
+ break;
+ }
+ poss_subclass = next;
+ }
+ }
+
+ /* Now push the parent_classes as sorted, we are done with
+ these. Add to the END of the list by concatenation */
+ DLIST_CONCATENATE(sorted, parent_class, struct class_list *);
+
+ /* and now find subclasses of these */
+ parent_class = subclass;
+ subclass = NULL;
+
+ /* If we didn't find any subclasses we will fall out
+ * the bottom here */
+ } while (parent_class);
+
+ if (!unsorted) {
+ *sorted_out = sorted;
+ return LDB_SUCCESS;
+ }
+
+ if (!schema) {
+ /* If we don't have schema yet, then just merge the lists again */
+ DLIST_CONCATENATE(sorted, unsorted, struct class_list *);
+ *sorted_out = sorted;
+ return LDB_SUCCESS;
+ }
+
+ /* This shouldn't happen, and would break MMC, perhaps there
+ * was no 'top', a conflict in the objectClasses or some other
+ * schema error?
+ */
+ ldb_asprintf_errstring(module->ldb, "objectclass %s is not a valid objectClass in objectClass chain", unsorted->objectclass->lDAPDisplayName);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+}
+
+static DATA_BLOB *get_sd(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ const struct dsdb_class *objectclass)
+{
+ enum ndr_err_code ndr_err;
+ DATA_BLOB *linear_sd;
+ struct auth_session_info *session_info
+ = ldb_get_opaque(module->ldb, "sessionInfo");
+ struct security_descriptor *sd;
+ struct dom_sid *domain_sid = samdb_domain_sid(module->ldb);
+
+ if (!objectclass->defaultSecurityDescriptor || !domain_sid) {
+ return NULL;
+ }
+
+ sd = sddl_decode(mem_ctx,
+ objectclass->defaultSecurityDescriptor,
+ domain_sid);
+
+ if (!sd || !session_info || !session_info->security_token) {
+ return NULL;
+ }
+
+ sd->owner_sid = session_info->security_token->user_sid;
+ sd->group_sid = session_info->security_token->group_sid;
+
+ linear_sd = talloc(mem_ctx, DATA_BLOB);
+ if (!linear_sd) {
+ return NULL;
+ }
+
+ ndr_err = ndr_push_struct_blob(linear_sd, mem_ctx,
+ lp_iconv_convenience(ldb_get_opaque(module->ldb, "loadparm")),
+ sd,
+ (ndr_push_flags_fn_t)ndr_push_security_descriptor);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NULL;
+ }
+
+ return linear_sd;
+
+}
+
+static int get_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct oc_context *ac;
+
+ ac = talloc_get_type(context, struct oc_context);
+
+ /* we are interested only in the single reply (base search) we receive here */
+ if (ares->type == LDB_REPLY_ENTRY) {
+ if (ac->search_res != NULL) {
+ ldb_set_errstring(ldb, "Too many results");
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->search_res = talloc_move(ac, &ares);
+ } else {
+ talloc_free(ares);
+ }
+
+ return LDB_SUCCESS;
+}
+
+/* Fix up the DN to be in the standard form, taking particular care to match the parent DN
+
+ This should mean that if the parent is:
+ CN=Users,DC=samba,DC=example,DC=com
+ and a proposed child is
+ cn=Admins ,cn=USERS,dc=Samba,dc=example,dc=COM
+
+ The resulting DN should be:
+
+ CN=Admins,CN=Users,DC=samba,DC=example,DC=com
+
+ */
+static int fix_dn(TALLOC_CTX *mem_ctx,
+ struct ldb_dn *newdn, struct ldb_dn *parent_dn,
+ struct ldb_dn **fixed_dn)
+{
+ char *upper_rdn_attr;
+ /* Fix up the DN to be in the standard form, taking particular care to match the parent DN */
+ *fixed_dn = ldb_dn_copy(mem_ctx, parent_dn);
+
+ /* We need the attribute name in upper case */
+ upper_rdn_attr = strupper_talloc(*fixed_dn,
+ ldb_dn_get_rdn_name(newdn));
+ if (!upper_rdn_attr) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Create a new child */
+ if (ldb_dn_add_child_fmt(*fixed_dn, "X=X") == false) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* And replace it with CN=foo (we need the attribute in upper case */
+ return ldb_dn_set_component(*fixed_dn, 0, upper_rdn_attr,
+ *ldb_dn_get_rdn_val(newdn));
+}
+
+/* Fix all attribute names to be in the correct case, and check they are all valid per the schema */
+static int fix_attributes(struct ldb_context *ldb, const struct dsdb_schema *schema, struct ldb_message *msg)
+{
+ int i;
+ for (i=0; i < msg->num_elements; i++) {
+ const struct dsdb_attribute *attribute = dsdb_attribute_by_lDAPDisplayName(schema, msg->elements[i].name);
+ if (!attribute) {
+ ldb_asprintf_errstring(ldb, "attribute %s is not a valid attribute in schema", msg->elements[i].name);
+ return LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE;
+ }
+ msg->elements[i].name = attribute->lDAPDisplayName;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int objectclass_add(struct ldb_module *module, struct ldb_request *req)
+{
+
+ static const char * const attrs[] = { NULL };
+
+ struct ldb_handle *h;
+ struct oc_context *ac;
+ struct ldb_dn *parent_dn;
+ int ret;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_add\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* Need to object to this, but cn=rootdse doesn't hae an objectClass... */
+ if (ldb_msg_find_element(req->op.add.message,
+ "objectClass") == NULL) {
+ return ldb_next_request(module, req);
+ }
+
+ h = oc_init_handle(req, module);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct oc_context);
+
+ /* return or own handle to deal with this call */
+ req->handle = h;
+
+ /* If there isn't a parent, just go on to the add processing */
+ if (ldb_dn_get_comp_num(ac->orig_req->op.add.message->dn) == 1) {
+ return objectclass_do_add(h);
+ }
+
+ parent_dn = ldb_dn_get_parent(ac, ac->orig_req->op.add.message->dn);
+ if (parent_dn == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_search_req(&ac->search_req, module->ldb,
+ ac, parent_dn, LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs, NULL,
+ ac, get_search_callback);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(ac->search_req, parent_dn);
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
+
+ ac->step = OC_SEARCH_ADD_PARENT;
+
+ return ldb_next_request(ac->module, ac->search_req);
+}
+
+static int objectclass_do_add(struct ldb_handle *h)
+{
+ const struct dsdb_schema *schema;
+ struct oc_context *ac;
+ struct ldb_message_element *objectclass_element;
+ struct ldb_message *msg;
+ TALLOC_CTX *mem_ctx;
+ struct class_list *sorted, *current;
+ int ret;
+
+ ac = talloc_get_type(h->private_data, struct oc_context);
+ schema = dsdb_get_schema(ac->module->ldb);
+
+ mem_ctx = talloc_new(ac);
+ if (mem_ctx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->add_req = talloc(ac, struct ldb_request);
+ if (ac->add_req == NULL) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *ac->add_req = *ac->orig_req;
+
+ ac->add_req->op.add.message = msg = ldb_msg_copy_shallow(ac->add_req, ac->orig_req->op.add.message);
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->add_req);
+
+ /* Check we have a valid parent */
+ if (ac->search_res == NULL) {
+ if (ldb_dn_compare(ldb_get_root_basedn(ac->module->ldb), ac->orig_req->op.add.message->dn) == 0) {
+ /* Allow the tree to be started */
+
+ /* but don't keep any error string, it's meaningless */
+ ldb_set_errstring(ac->module->ldb, NULL);
+ } else {
+ ldb_asprintf_errstring(ac->module->ldb, "objectclass: Cannot add %s, parent does not exist!",
+ ldb_dn_get_linearized(ac->orig_req->op.add.message->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ } else {
+
+ /* Fix up the DN to be in the standard form, taking particular care to match the parent DN */
+ ret = fix_dn(msg,
+ ac->orig_req->op.add.message->dn,
+ ac->search_res->message->dn,
+ &msg->dn);
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ac->module->ldb, "Could not munge DN %s into normal form",
+ ldb_dn_get_linearized(ac->orig_req->op.add.message->dn));
+ return ret;
+ }
+
+ /* TODO: Check this is a valid child to this parent,
+ * by reading the allowedChildClasses and
+ * allowedChildClasssesEffective attributes */
+
+ }
+
+ if (schema) {
+ ret = fix_attributes(ac->module->ldb, schema, msg);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* This is now the objectClass list from the database */
+ objectclass_element = ldb_msg_find_element(msg, "objectClass");
+
+ if (!objectclass_element) {
+ /* Where did it go? bail now... */
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = objectclass_sort(ac->module, schema, msg, mem_ctx, objectclass_element, &sorted);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ ldb_msg_remove_attr(msg, "objectClass");
+ ret = ldb_msg_add_empty(msg, "objectClass", 0, NULL);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* We must completely replace the existing objectClass entry,
+ * because we need it sorted */
+
+ /* Move from the linked list back into an ldb msg */
+ for (current = sorted; current; current = current->next) {
+ ret = ldb_msg_add_string(msg, "objectClass", current->objectclass->lDAPDisplayName);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(ac->module->ldb,
+ "objectclass: could not re-add sorted "
+ "objectclass to modify msg");
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ /* Last one is the critical one */
+ if (!current->next) {
+ struct ldb_message_element *el;
+ int32_t systemFlags = 0;
+ if (!ldb_msg_find_element(msg, "objectCategory")) {
+ ldb_msg_add_string(msg, "objectCategory",
+ current->objectclass->defaultObjectCategory);
+ }
+ if (!ldb_msg_find_element(msg, "showInAdvancedViewOnly") && (current->objectclass->defaultHidingValue == true)) {
+ ldb_msg_add_string(msg, "showInAdvancedViewOnly",
+ "TRUE");
+ }
+ if (!ldb_msg_find_element(msg, "nTSecurityDescriptor")) {
+ DATA_BLOB *sd = get_sd(ac->module, mem_ctx, current->objectclass);
+ if (sd) {
+ ldb_msg_add_steal_value(msg, "nTSecurityDescriptor", sd);
+ }
+ }
+
+ /* There are very special rules for systemFlags, see MS-ADTS 3.1.1.5.2.4 */
+ el = ldb_msg_find_element(msg, "systemFlags");
+
+ systemFlags = ldb_msg_find_attr_as_int(msg, "systemFlags", 0);
+
+ if (el) {
+ /* Only these flags may be set by a client, but we can't tell between a client and our provision at this point */
+ /* systemFlags &= ( SYSTEM_FLAG_CONFIG_ALLOW_RENAME | SYSTEM_FLAG_CONFIG_ALLOW_MOVE | SYSTEM_FLAG_CONFIG_LIMITED_MOVE); */
+ ldb_msg_remove_element(msg, el);
+ }
+
+ /* This flag is only allowed on attributeSchema objects */
+ if (ldb_attr_cmp(current->objectclass->lDAPDisplayName, "attributeSchema") == 0) {
+ systemFlags &= ~SYSTEM_FLAG_ATTR_IS_RDN;
+ }
+
+ if (ldb_attr_cmp(current->objectclass->lDAPDisplayName, "server") == 0) {
+ systemFlags |= (int32_t)(SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE | SYSTEM_FLAG_CONFIG_ALLOW_RENAME | SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE);
+ } else if (ldb_attr_cmp(current->objectclass->lDAPDisplayName, "site") == 0
+ || ldb_attr_cmp(current->objectclass->lDAPDisplayName, "serverContainer") == 0
+ || ldb_attr_cmp(current->objectclass->lDAPDisplayName, "ntDSDSA") == 0) {
+ systemFlags |= (int32_t)(SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE);
+
+ } else if (ldb_attr_cmp(current->objectclass->lDAPDisplayName, "siteLink") == 0
+ || ldb_attr_cmp(current->objectclass->lDAPDisplayName, "siteLinkBridge") == 0
+ || ldb_attr_cmp(current->objectclass->lDAPDisplayName, "nTDSConnection") == 0) {
+ systemFlags |= (int32_t)(SYSTEM_FLAG_CONFIG_ALLOW_RENAME);
+ }
+
+ /* TODO: If parent object is site or subnet, also add (SYSTEM_FLAG_CONFIG_ALLOW_RENAME) */
+
+ if (el || systemFlags != 0) {
+ samdb_msg_add_int(ac->module->ldb, msg, msg, "systemFlags", systemFlags);
+ }
+ }
+ }
+ }
+
+ talloc_free(mem_ctx);
+ ret = ldb_msg_sanity_check(ac->module->ldb, msg);
+
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = OC_DO_ADD;
+
+ /* perform the add */
+ return ldb_next_request(ac->module, ac->add_req);
+}
+
+static int objectclass_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_message_element *objectclass_element;
+ struct ldb_message *msg;
+ const struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+ int ret;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_modify\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* Without schema, there isn't much to do here */
+ if (!schema) {
+ return ldb_next_request(module, req);
+ }
+ objectclass_element = ldb_msg_find_element(req->op.mod.message, "objectClass");
+
+ /* If no part of this touches the objectClass, then we don't
+ * need to make any changes. */
+
+ /* If the only operation is the deletion of the objectClass
+ * then go on with just fixing the attribute case */
+ if (!objectclass_element) {
+ struct ldb_request *down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_set_errstring(module->ldb, "Out of memory!");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req; /* copy the request */
+
+ down_req->op.mod.message = msg = ldb_msg_copy_shallow(down_req, req->op.mod.message);
+
+ if (down_req->op.mod.message == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = fix_attributes(module->ldb, schema, msg);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+ return ret;
+ }
+
+ switch (objectclass_element->flags & LDB_FLAG_MOD_MASK) {
+ case LDB_FLAG_MOD_DELETE:
+ if (objectclass_element->num_values == 0) {
+ return LDB_ERR_OBJECT_CLASS_MODS_PROHIBITED;
+ }
+ break;
+ case LDB_FLAG_MOD_REPLACE:
+ {
+ struct ldb_request *down_req;
+ struct class_list *sorted, *current;
+ TALLOC_CTX *mem_ctx;
+ mem_ctx = talloc_new(req);
+ if (mem_ctx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* prepare the first operation */
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_set_errstring(module->ldb, "Out of memory!");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req; /* copy the request */
+
+ down_req->op.mod.message = msg = ldb_msg_copy_shallow(down_req, req->op.mod.message);
+
+ if (down_req->op.mod.message == NULL) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = fix_attributes(module->ldb, schema, msg);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ ret = objectclass_sort(module, schema, msg, mem_ctx, objectclass_element, &sorted);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* We must completely replace the existing objectClass entry,
+ * because we need it sorted */
+
+ ldb_msg_remove_attr(msg, "objectClass");
+ ret = ldb_msg_add_empty(msg, "objectClass", LDB_FLAG_MOD_REPLACE, NULL);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* Move from the linked list back into an ldb msg */
+ for (current = sorted; current; current = current->next) {
+ ret = ldb_msg_add_string(msg, "objectClass", current->objectclass->lDAPDisplayName);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(module->ldb, "objectclass: could not re-add sorted objectclass to modify msg");
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+
+ talloc_free(mem_ctx);
+
+ ret = ldb_msg_sanity_check(module->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+ return ret;
+ }
+ }
+
+ /* This isn't the default branch of the switch, but a 'in any
+ * other case'. When a delete isn't for all objectClasses for
+ * example
+ */
+ {
+ struct ldb_handle *h;
+ struct oc_context *ac;
+
+ h = oc_init_handle(req, module);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct oc_context);
+
+ /* return or own handle to deal with this call */
+ req->handle = h;
+
+ /* prepare the first operation */
+ ac->down_req = talloc(ac, struct ldb_request);
+ if (ac->down_req == NULL) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->down_req) = *req; /* copy the request */
+
+ ac->down_req->op.mod.message = msg = ldb_msg_copy_shallow(ac->down_req, req->op.mod.message);
+
+ if (ac->down_req->op.mod.message == NULL) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = fix_attributes(ac->module->ldb, schema, msg);
+ if (ret != LDB_SUCCESS) {
+ ldb_oom(ac->module->ldb);
+ return ret;
+ }
+
+ ac->down_req->context = NULL;
+ ac->down_req->callback = NULL;
+ ldb_set_timeout_from_prev_req(module->ldb, req, ac->down_req);
+
+ ac->step = OC_DO_REQ;
+
+ return ldb_next_request(module, ac->down_req);
+ }
+}
+
+static int objectclass_search_self(struct ldb_handle *h)
+{
+ int ret;
+ struct oc_context *ac;
+ static const char * const attrs[] = { "objectClass", NULL };
+
+ ac = talloc_get_type(h->private_data, struct oc_context);
+
+ ret = ldb_build_search_req(&ac->search_req, ac->module->ldb,
+ ac, ac->orig_req->op.mod.message->dn, LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs, NULL,
+ ac, get_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
+
+ ac->step = OC_SEARCH_SELF;
+
+ return ldb_next_request(ac->module, ac->search_req);
+}
+
+static int objectclass_do_mod(struct ldb_handle *h) {
+
+ const struct dsdb_schema *schema;
+ struct oc_context *ac;
+ struct ldb_message_element *objectclass_element;
+ struct ldb_message *msg;
+ TALLOC_CTX *mem_ctx;
+ struct class_list *sorted, *current;
+ int ret;
+
+ ac = talloc_get_type(h->private_data, struct oc_context);
+ schema = dsdb_get_schema(ac->module->ldb);
+
+ mem_ctx = talloc_new(ac);
+ if (mem_ctx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->mod_req = talloc(ac, struct ldb_request);
+ if (ac->mod_req == NULL) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->mod_req->operation = LDB_MODIFY;
+ ac->mod_req->controls = NULL;
+ ac->mod_req->context = ac;
+ ac->mod_req->callback = NULL;
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->mod_req);
+
+ /* use a new message structure */
+ ac->mod_req->op.mod.message = msg = ldb_msg_new(ac->mod_req);
+ if (msg == NULL) {
+ ldb_set_errstring(ac->module->ldb, "objectclass: could not create new modify msg");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* This is now the objectClass list from the database */
+ objectclass_element = ldb_msg_find_element(ac->search_res->message,
+ "objectClass");
+ if (!objectclass_element) {
+ /* Where did it go? bail now... */
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* modify dn */
+ msg->dn = ac->orig_req->op.mod.message->dn;
+
+ ret = objectclass_sort(ac->module, schema, msg, mem_ctx, objectclass_element, &sorted);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* We must completely replace the existing objectClass entry.
+ * We could do a constrained add/del, but we are meant to be
+ * in a transaction... */
+
+ ret = ldb_msg_add_empty(msg, "objectClass", LDB_FLAG_MOD_REPLACE, NULL);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(ac->module->ldb, "objectclass: could not clear objectclass in modify msg");
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* Move from the linked list back into an ldb msg */
+ for (current = sorted; current; current = current->next) {
+ ret = ldb_msg_add_string(msg, "objectClass", current->objectclass->lDAPDisplayName);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(ac->module->ldb, "objectclass: could not re-add sorted objectclass to modify msg");
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+
+ ret = ldb_msg_sanity_check(ac->module->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = OC_DO_MOD;
+
+ talloc_free(mem_ctx);
+ /* perform the search */
+ return ldb_next_request(ac->module, ac->mod_req);
+}
+
+static int objectclass_rename(struct ldb_module *module, struct ldb_request *req)
+{
+
+ static const char * const attrs[] = { NULL };
+
+ struct ldb_handle *h;
+ struct oc_context *ac;
+ struct ldb_dn *parent_dn;
+ int ret;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectclass_rename\n");
+
+ if (ldb_dn_is_special(req->op.rename.newdn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* Firstly ensure we are not trying to rename it to be a child of itself */
+ if ((ldb_dn_compare_base(req->op.rename.olddn, req->op.rename.newdn) == 0)
+ && (ldb_dn_compare(req->op.rename.olddn, req->op.rename.newdn) != 0)) {
+ ldb_asprintf_errstring(module->ldb, "Cannot rename %s to be a child of itself",
+ ldb_dn_get_linearized(req->op.rename.olddn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ h = oc_init_handle(req, module);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct oc_context);
+
+ /* return or own handle to deal with this call */
+ req->handle = h;
+
+ parent_dn = ldb_dn_get_parent(ac, ac->orig_req->op.rename.newdn);
+ if (parent_dn == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = ldb_build_search_req(&ac->search_req, module->ldb,
+ ac, parent_dn, LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs, NULL,
+ ac, get_search_callback);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_steal(ac->search_req, parent_dn);
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
+
+ ac->step = OC_SEARCH_RENAME_PARENT;
+
+ return ldb_next_request(ac->module, ac->search_req);
+}
+
+static int objectclass_do_rename(struct ldb_handle *h)
+{
+ struct oc_context *ac;
+ int ret;
+
+ ac = talloc_get_type(h->private_data, struct oc_context);
+
+ ac->rename_req = talloc(ac, struct ldb_request);
+ if (ac->rename_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *ac->rename_req = *ac->orig_req;
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->rename_req);
+
+ /* Check we have a valid parent */
+ if (ac->search_res == NULL) {
+ ldb_asprintf_errstring(ac->module->ldb, "objectclass: Cannot rename %s, parent does not exist!",
+ ldb_dn_get_linearized(ac->orig_req->op.rename.newdn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Fix up the DN to be in the standard form, taking particular care to match the parent DN */
+ ret = fix_dn(ac->rename_req,
+ ac->orig_req->op.rename.newdn,
+ ac->search_res->message->dn,
+ &ac->rename_req->op.rename.newdn);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* TODO: Check this is a valid child to this parent,
+ * by reading the allowedChildClasses and
+ * allowedChildClasssesEffective attributes */
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = OC_DO_RENAME;
+
+ /* perform the rename */
+ return ldb_next_request(ac->module, ac->rename_req);
+}
+
+static int oc_wait(struct ldb_handle *handle) {
+ struct oc_context *ac;
+ int ret;
+
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct oc_context);
+
+ switch (ac->step) {
+ case OC_DO_REQ:
+ ret = ldb_wait(ac->down_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* mods done, go on */
+ return objectclass_search_self(handle);
+
+ case OC_SEARCH_SELF:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* self search done, go on */
+ return objectclass_do_mod(handle);
+
+ case OC_DO_MOD:
+ ret = ldb_wait(ac->mod_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->mod_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->mod_req->handle->status;
+ goto done;
+ }
+
+ if (ac->mod_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ case OC_SEARCH_ADD_PARENT:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_OBJECT) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS
+ && ac->search_req->handle->status != LDB_ERR_NO_SUCH_OBJECT) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* parent search done, go on */
+ return objectclass_do_add(handle);
+
+ case OC_DO_ADD:
+ ret = ldb_wait(ac->add_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->add_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->add_req->handle->status;
+ goto done;
+ }
+
+ if (ac->add_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ case OC_SEARCH_RENAME_PARENT:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_OBJECT) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS && ac->search_req->handle->status != LDB_ERR_NO_SUCH_OBJECT) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* parent search done, go on */
+ return objectclass_do_rename(handle);
+
+ case OC_DO_RENAME:
+ ret = ldb_wait(ac->rename_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->rename_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->rename_req->handle->status;
+ goto done;
+ }
+
+ if (ac->rename_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ default:
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ ret = LDB_SUCCESS;
+
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+}
+
+static int oc_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = oc_wait(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int objectclass_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return oc_wait_all(handle);
+ } else {
+ return oc_wait(handle);
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_objectclass_module_ops = {
+ .name = "objectclass",
+ .add = objectclass_add,
+ .modify = objectclass_modify,
+ .rename = objectclass_rename,
+ .wait = objectclass_wait
+};
diff --git a/source4/dsdb/samdb/ldb_modules/objectguid.c b/source4/dsdb/samdb/ldb_modules/objectguid.c
new file mode 100644
index 0000000000..f62839389d
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/objectguid.c
@@ -0,0 +1,254 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2004-2006
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 2005
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb objectguid module
+ *
+ * Description: add a unique objectGUID onto every new record
+ *
+ * Author: Simo Sorce
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb_includes.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "param/param.h"
+
+static struct ldb_message_element *objectguid_find_attribute(const struct ldb_message *msg, const char *name)
+{
+ int i;
+
+ for (i = 0; i < msg->num_elements; i++) {
+ if (ldb_attr_cmp(name, msg->elements[i].name) == 0) {
+ return &msg->elements[i];
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ add a time element to a record
+*/
+static int add_time_element(struct ldb_message *msg, const char *attr, time_t t)
+{
+ struct ldb_message_element *el;
+ char *s;
+
+ if (ldb_msg_find_element(msg, attr) != NULL) {
+ return 0;
+ }
+
+ s = ldb_timestring(msg, t);
+ if (s == NULL) {
+ return -1;
+ }
+
+ if (ldb_msg_add_string(msg, attr, s) != 0) {
+ return -1;
+ }
+
+ el = ldb_msg_find_element(msg, attr);
+ /* always set as replace. This works because on add ops, the flag
+ is ignored */
+ el->flags = LDB_FLAG_MOD_REPLACE;
+
+ return 0;
+}
+
+/*
+ add a uint64_t element to a record
+*/
+static int add_uint64_element(struct ldb_message *msg, const char *attr, uint64_t v)
+{
+ struct ldb_message_element *el;
+
+ if (ldb_msg_find_element(msg, attr) != NULL) {
+ return 0;
+ }
+
+ if (ldb_msg_add_fmt(msg, attr, "%llu", (unsigned long long)v) != 0) {
+ return -1;
+ }
+
+ el = ldb_msg_find_element(msg, attr);
+ /* always set as replace. This works because on add ops, the flag
+ is ignored */
+ el->flags = LDB_FLAG_MOD_REPLACE;
+
+ return 0;
+}
+
+/* add_record: add objectGUID attribute */
+static int objectguid_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_request *down_req;
+ struct ldb_message_element *attribute;
+ struct ldb_message *msg;
+ struct ldb_val v;
+ struct GUID guid;
+ uint64_t seq_num;
+ enum ndr_err_code ndr_err;
+ int ret;
+ time_t t = time(NULL);
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectguid_add_record\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ if ((attribute = objectguid_find_attribute(req->op.add.message, "objectGUID")) != NULL ) {
+ return ldb_next_request(module, req);
+ }
+
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+
+ /* we have to copy the message as the caller might have it as a const */
+ down_req->op.add.message = msg = ldb_msg_copy_shallow(down_req, req->op.add.message);
+ if (msg == NULL) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* a new GUID */
+ guid = GUID_random();
+
+ ndr_err = ndr_push_struct_blob(&v, msg,
+ lp_iconv_convenience(ldb_get_opaque(module->ldb, "loadparm")),
+ &guid,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_msg_add_value(msg, "objectGUID", &v, NULL);
+ if (ret) {
+ talloc_free(down_req);
+ return ret;
+ }
+
+ if (add_time_element(msg, "whenCreated", t) != 0 ||
+ add_time_element(msg, "whenChanged", t) != 0) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Get a sequence number from the backend */
+ ret = ldb_sequence_number(module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret == LDB_SUCCESS) {
+ if (add_uint64_element(msg, "uSNCreated", seq_num) != 0 ||
+ add_uint64_element(msg, "uSNChanged", seq_num) != 0) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+/* modify_record: update timestamps */
+static int objectguid_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ int ret;
+ time_t t = time(NULL);
+ uint64_t seq_num;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectguid_add_record\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+
+ /* we have to copy the message as the caller might have it as a const */
+ down_req->op.mod.message = msg = ldb_msg_copy_shallow(down_req, req->op.mod.message);
+ if (msg == NULL) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (add_time_element(msg, "whenChanged", t) != 0) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Get a sequence number from the backend */
+ ret = ldb_sequence_number(module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret == LDB_SUCCESS) {
+ if (add_uint64_element(msg, "uSNChanged", seq_num) != 0) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_objectguid_module_ops = {
+ .name = "objectguid",
+ .add = objectguid_add,
+ .modify = objectguid_modify,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/partition.c b/source4/dsdb/samdb/ldb_modules/partition.c
new file mode 100644
index 0000000000..9cae6ab7b5
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/partition.c
@@ -0,0 +1,1101 @@
+
+/*
+ Partitions ldb module
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+
+ * NOTICE: this module is NOT released under the GNU LGPL license as
+ * other ldb code. This module is release under the GNU GPL v3 or
+ * later license.
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb partitions module
+ *
+ * Description: Implement LDAP partitions
+ *
+ * Author: Andrew Bartlett
+ * Author: Stefan Metzmacher
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb_includes.h"
+#include "dsdb/samdb/samdb.h"
+
+struct partition_private_data {
+ struct dsdb_control_current_partition **partitions;
+ struct ldb_dn **replicate;
+};
+
+struct partition_context {
+ struct ldb_module *module;
+ struct ldb_request *orig_req;
+
+ struct ldb_request **down_req;
+ int num_requests;
+ int finished_requests;
+};
+
+static struct partition_context *partition_init_handle(struct ldb_request *req, struct ldb_module *module)
+{
+ struct partition_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct partition_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = ac;
+
+ ac->module = module;
+ ac->orig_req = req;
+
+ req->handle = h;
+
+ return ac;
+}
+
+static struct ldb_module *make_module_for_next_request(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ struct ldb_module *module)
+{
+ struct ldb_module *current;
+ static const struct ldb_module_ops ops; /* zero */
+ current = talloc_zero(mem_ctx, struct ldb_module);
+ if (current == NULL) {
+ return module;
+ }
+
+ current->ldb = ldb;
+ current->ops = &ops;
+ current->prev = NULL;
+ current->next = module;
+ return current;
+}
+
+static struct dsdb_control_current_partition *find_partition(struct partition_private_data *data,
+ struct ldb_dn *dn)
+{
+ int i;
+
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ if (ldb_dn_compare_base(data->partitions[i]->dn, dn) == 0) {
+ return data->partitions[i];
+ }
+ }
+
+ return NULL;
+};
+
+/**
+ * fire the caller's callback for every entry, but only send 'done' once.
+ */
+static int partition_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct partition_context *ac;
+
+ ac = talloc_get_type(context, struct partition_context);
+
+ if (ares->type == LDB_REPLY_ENTRY) {
+ return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
+ } else {
+ ac->finished_requests++;
+ if (ac->finished_requests == ac->num_requests) {
+ return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
+ } else {
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+ }
+}
+
+/**
+ * only fire the 'last' callback, and only for START-TLS for now
+ */
+static int partition_other_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct partition_context *ac;
+
+ ac = talloc_get_type(context, struct partition_context);
+
+ if (!ac->orig_req->callback) {
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+
+ if (!ares
+ || (ares->type == LDB_REPLY_EXTENDED
+ && strcmp(ares->response->oid, LDB_EXTENDED_START_TLS_OID))) {
+ ac->finished_requests++;
+ if (ac->finished_requests == ac->num_requests) {
+ return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
+ }
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+ ldb_set_errstring(ldb, "partition_other_callback: Unknown reply type, only supports START_TLS");
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+}
+
+
+static int partition_send_request(struct partition_context *ac,
+ struct dsdb_control_current_partition *partition)
+{
+ int ret;
+ struct ldb_module *backend;
+ struct ldb_request *req;
+
+ if (partition) {
+ backend = make_module_for_next_request(ac, ac->module->ldb, partition->module);
+ } else {
+ backend = ac->module;
+ }
+
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ req = ac->down_req[ac->num_requests] = talloc(ac, struct ldb_request);
+ if (req == NULL) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *req = *ac->orig_req; /* copy the request */
+
+ if (req->controls) {
+ req->controls
+ = talloc_memdup(req,
+ ac->orig_req->controls, talloc_get_size(ac->orig_req->controls));
+ if (req->controls == NULL) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ if (req->operation == LDB_SEARCH) {
+ /* If the search is for 'more' than this partition,
+ * then change the basedn, so a remote LDAP server
+ * doesn't object */
+ if (partition) {
+ if (ldb_dn_compare_base(partition->dn, req->op.search.base) != 0) {
+ req->op.search.base = partition->dn;
+ }
+ } else {
+ req->op.search.base = NULL;
+ }
+ req->callback = partition_search_callback;
+ req->context = ac;
+ } else {
+ req->callback = partition_other_callback;
+ req->context = ac;
+ }
+
+ if (partition) {
+ ret = ldb_request_add_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID, false, partition);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* Spray off search requests the backend */
+ ret = ldb_next_request(backend, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->num_requests++;
+ return LDB_SUCCESS;
+}
+
+/**
+ * Send a request down to all the partitions
+ */
+static int partition_send_all(struct ldb_module *module,
+ struct partition_context *ac,
+ struct ldb_request *req)
+{
+ int i;
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+ int ret = partition_send_request(ac, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ ret = partition_send_request(ac, data->partitions[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+/**
+ * Figure out which backend a request needs to be aimed at. Some
+ * requests must be replicated to all backends
+ */
+static int partition_replicate(struct ldb_module *module, struct ldb_request *req, struct ldb_dn *dn)
+{
+ unsigned i;
+ int ret;
+ struct dsdb_control_current_partition *partition;
+ struct ldb_module *backend;
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+
+ if (req->operation != LDB_SEARCH) {
+ /* Is this a special DN, we need to replicate to every backend? */
+ for (i=0; data->replicate && data->replicate[i]; i++) {
+ if (ldb_dn_compare(data->replicate[i],
+ dn) == 0) {
+ struct partition_context *ac;
+
+ ac = partition_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return partition_send_all(module, ac, req);
+ }
+ }
+ }
+
+ /* Otherwise, we need to find the partition to fire it to */
+
+ /* Find partition */
+ partition = find_partition(data, dn);
+ if (!partition) {
+ /*
+ * if we haven't found a matching partition
+ * pass the request to the main ldb
+ *
+ * TODO: we should maybe return an error here
+ * if it's not a special dn
+ */
+
+ return ldb_next_request(module, req);
+ }
+
+ backend = make_module_for_next_request(req, module->ldb, partition->module);
+ if (!backend) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_request_add_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID, false, partition);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* issue request */
+ return ldb_next_request(backend, req);
+}
+
+/* search */
+static int partition_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_control **saved_controls;
+
+ /* Find backend */
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+ /* issue request */
+
+ /* (later) consider if we should be searching multiple
+ * partitions (for 'invisible' partition behaviour */
+ struct ldb_control *search_control = ldb_request_get_control(req, LDB_CONTROL_SEARCH_OPTIONS_OID);
+ struct ldb_control *domain_scope_control = ldb_request_get_control(req, LDB_CONTROL_DOMAIN_SCOPE_OID);
+
+ struct ldb_search_options_control *search_options = NULL;
+ if (search_control) {
+ search_options = talloc_get_type(search_control->data, struct ldb_search_options_control);
+ }
+
+ /* Remove the domain_scope control, so we don't confuse a backend server */
+ if (domain_scope_control && !save_controls(domain_scope_control, req, &saved_controls)) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* TODO:
+ Generate referrals (look for a partition under this DN) if we don't have the above control specified
+ */
+
+ if (search_options && (search_options->search_options & LDB_SEARCH_OPTION_PHANTOM_ROOT)) {
+ int ret, i;
+ struct partition_context *ac;
+ if ((search_options->search_options & ~LDB_SEARCH_OPTION_PHANTOM_ROOT) == 0) {
+ /* We have processed this flag, so we are done with this control now */
+
+ /* Remove search control, so we don't confuse a backend server */
+ if (search_control && !save_controls(search_control, req, &saved_controls)) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+ ac = partition_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Search from the base DN */
+ if (!req->op.search.base || ldb_dn_is_null(req->op.search.base)) {
+ return partition_send_all(module, ac, req);
+ }
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ /* Find all partitions under the search base */
+ if (ldb_dn_compare_base(req->op.search.base, data->partitions[i]->dn) == 0) {
+ ret = partition_send_request(ac, data->partitions[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ /* Perhaps we didn't match any partitions. Try the main partition, only */
+ if (ac->num_requests == 0) {
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+
+ return LDB_SUCCESS;
+ } else {
+ /* Handle this like all other requests */
+ if (search_control && (search_options->search_options & ~LDB_SEARCH_OPTION_PHANTOM_ROOT) == 0) {
+ /* We have processed this flag, so we are done with this control now */
+
+ /* Remove search control, so we don't confuse a backend server */
+ if (search_control && !save_controls(search_control, req, &saved_controls)) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ return partition_replicate(module, req, req->op.search.base);
+ }
+}
+
+/* add */
+static int partition_add(struct ldb_module *module, struct ldb_request *req)
+{
+ return partition_replicate(module, req, req->op.add.message->dn);
+}
+
+/* modify */
+static int partition_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ return partition_replicate(module, req, req->op.mod.message->dn);
+}
+
+/* delete */
+static int partition_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ return partition_replicate(module, req, req->op.del.dn);
+}
+
+/* rename */
+static int partition_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ int i, matched = -1;
+ /* Find backend */
+ struct dsdb_control_current_partition *backend, *backend2;
+
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ if (!data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ backend = find_partition(data, req->op.rename.olddn);
+ backend2 = find_partition(data, req->op.rename.newdn);
+
+ if ((backend && !backend2) || (!backend && backend2)) {
+ return LDB_ERR_AFFECTS_MULTIPLE_DSAS;
+ }
+
+ if (backend != backend2) {
+ ldb_asprintf_errstring(module->ldb,
+ "Cannot rename from %s in %s to %s in %s: %s",
+ ldb_dn_get_linearized(req->op.rename.olddn),
+ ldb_dn_get_linearized(backend->dn),
+ ldb_dn_get_linearized(req->op.rename.newdn),
+ ldb_dn_get_linearized(backend2->dn),
+ ldb_strerror(LDB_ERR_AFFECTS_MULTIPLE_DSAS));
+ return LDB_ERR_AFFECTS_MULTIPLE_DSAS;
+ }
+
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ if (ldb_dn_compare_base(req->op.rename.olddn, data->partitions[i]->dn) == 0) {
+ matched = i;
+ }
+ }
+
+ if (matched > 0) {
+ ldb_asprintf_errstring(module->ldb,
+ "Cannot rename from %s to %s, subtree rename would cross partition %s: %s",
+ ldb_dn_get_linearized(req->op.rename.olddn),
+ ldb_dn_get_linearized(req->op.rename.newdn),
+ ldb_dn_get_linearized(data->partitions[matched]->dn),
+ ldb_strerror(LDB_ERR_AFFECTS_MULTIPLE_DSAS));
+ return LDB_ERR_AFFECTS_MULTIPLE_DSAS;
+ }
+
+ return partition_replicate(module, req, req->op.rename.olddn);
+}
+
+/* start a transaction */
+static int partition_start_trans(struct ldb_module *module)
+{
+ int i, ret;
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ ret = ldb_next_start_trans(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ struct ldb_module *next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
+
+ ret = ldb_next_start_trans(next);
+ talloc_free(next);
+ if (ret != LDB_SUCCESS) {
+ /* Back it out, if it fails on one */
+ for (i--; i >= 0; i--) {
+ next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
+ ldb_next_del_trans(next);
+ talloc_free(next);
+ }
+ return ret;
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+/* end a transaction */
+static int partition_end_trans(struct ldb_module *module)
+{
+ int i, ret, ret2 = LDB_SUCCESS;
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+ ret = ldb_next_end_trans(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ struct ldb_module *next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
+
+ ret = ldb_next_end_trans(next);
+ talloc_free(next);
+ if (ret != LDB_SUCCESS) {
+ ret2 = ret;
+ }
+ }
+
+ if (ret != LDB_SUCCESS) {
+ /* Back it out, if it fails on one */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ struct ldb_module *next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
+ ldb_next_del_trans(next);
+ talloc_free(next);
+ }
+ }
+ return ret;
+}
+
+/* delete a transaction */
+static int partition_del_trans(struct ldb_module *module)
+{
+ int i, ret, ret2 = LDB_SUCCESS;
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+ ret = ldb_next_del_trans(module);
+ if (ret != LDB_SUCCESS) {
+ ret2 = ret;
+ }
+
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ struct ldb_module *next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
+
+ ret = ldb_next_del_trans(next);
+ talloc_free(next);
+ if (ret != LDB_SUCCESS) {
+ ret2 = ret;
+ }
+ }
+ return ret2;
+}
+
+static int partition_sequence_number(struct ldb_module *module, struct ldb_request *req)
+{
+ int i, ret;
+ uint64_t seq_number = 0;
+ uint64_t timestamp_sequence = 0;
+ uint64_t timestamp = 0;
+ struct partition_private_data *data = talloc_get_type(module->private_data,
+ struct partition_private_data);
+
+ switch (req->op.seq_num.type) {
+ case LDB_SEQ_NEXT:
+ case LDB_SEQ_HIGHEST_SEQ:
+ ret = ldb_next_request(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (req->op.seq_num.flags & LDB_SEQ_TIMESTAMP_SEQUENCE) {
+ timestamp_sequence = req->op.seq_num.seq_num;
+ } else {
+ seq_number = seq_number + req->op.seq_num.seq_num;
+ }
+
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ struct ldb_module *next = make_module_for_next_request(req, module->ldb, data->partitions[i]->module);
+
+ ret = ldb_request_add_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID, false, data->partitions[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_next_request(next, req);
+ talloc_free(next);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (req->op.seq_num.flags & LDB_SEQ_TIMESTAMP_SEQUENCE) {
+ timestamp_sequence = MAX(timestamp_sequence, req->op.seq_num.seq_num);
+ } else {
+ seq_number = seq_number + req->op.seq_num.seq_num;
+ }
+ }
+ /* fall though */
+ case LDB_SEQ_HIGHEST_TIMESTAMP:
+ {
+ struct ldb_request *date_req = talloc(req, struct ldb_request);
+ if (!date_req) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *date_req = *req;
+ date_req->op.seq_num.flags = LDB_SEQ_HIGHEST_TIMESTAMP;
+
+ ret = ldb_next_request(module, date_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ timestamp = date_req->op.seq_num.seq_num;
+
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialistion) */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ struct ldb_module *next = make_module_for_next_request(req, module->ldb, data->partitions[i]->module);
+
+ ret = ldb_next_request(next, date_req);
+ talloc_free(next);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ timestamp = MAX(timestamp, date_req->op.seq_num.seq_num);
+ }
+ break;
+ }
+ }
+
+ switch (req->op.seq_num.flags) {
+ case LDB_SEQ_NEXT:
+ case LDB_SEQ_HIGHEST_SEQ:
+
+ req->op.seq_num.flags = 0;
+
+ /* Has someone above set a timebase sequence? */
+ if (timestamp_sequence) {
+ req->op.seq_num.seq_num = (((unsigned long long)timestamp << 24) | (seq_number & 0xFFFFFF));
+ } else {
+ req->op.seq_num.seq_num = seq_number;
+ }
+
+ if (timestamp_sequence > req->op.seq_num.seq_num) {
+ req->op.seq_num.seq_num = timestamp_sequence;
+ req->op.seq_num.flags |= LDB_SEQ_TIMESTAMP_SEQUENCE;
+ }
+
+ req->op.seq_num.flags |= LDB_SEQ_GLOBAL_SEQUENCE;
+ break;
+ case LDB_SEQ_HIGHEST_TIMESTAMP:
+ req->op.seq_num.seq_num = timestamp;
+ break;
+ }
+
+ switch (req->op.seq_num.flags) {
+ case LDB_SEQ_NEXT:
+ req->op.seq_num.seq_num++;
+ }
+ return LDB_SUCCESS;
+}
+
+static int partition_extended_replicated_objects(struct ldb_module *module, struct ldb_request *req)
+{
+ struct dsdb_extended_replicated_objects *ext;
+
+ ext = talloc_get_type(req->op.extended.data, struct dsdb_extended_replicated_objects);
+ if (!ext) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "partition_extended_replicated_objects: invalid extended data\n");
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ if (ext->version != DSDB_EXTENDED_REPLICATED_OBJECTS_VERSION) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "partition_extended_replicated_objects: extended data invalid version [%u != %u]\n",
+ ext->version, DSDB_EXTENDED_REPLICATED_OBJECTS_VERSION);
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ return partition_replicate(module, req, ext->partition_dn);
+}
+
+static int partition_extended_schema_update_now(struct ldb_module *module, struct ldb_request *req)
+{
+ struct dsdb_control_current_partition *partition;
+ struct partition_private_data *data;
+ struct ldb_dn *schema_dn;
+ struct partition_context *ac;
+ struct ldb_module *backend;
+ int ret;
+
+ schema_dn = talloc_get_type(req->op.extended.data, struct ldb_dn);
+ if (!schema_dn) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "partition_extended: invalid extended data\n");
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ data = talloc_get_type(module->private_data, struct partition_private_data);
+ if (!data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ partition = find_partition( data, schema_dn );
+ if (!partition) {
+ return ldb_next_request(module, req);
+ }
+
+ ac = partition_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ backend = make_module_for_next_request(req, module->ldb, partition->module);
+ if (!backend) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_request_add_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID, false, partition);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(backend, req);
+}
+
+
+/* extended */
+static int partition_extended(struct ldb_module *module, struct ldb_request *req)
+{
+ struct partition_context *ac;
+
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_REPLICATED_OBJECTS_OID) == 0) {
+ return partition_extended_replicated_objects(module, req);
+ }
+
+ /* forward schemaUpdateNow operation to schema_fsmo module*/
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID) == 0) {
+ return partition_extended_schema_update_now( module, req );
+ }
+
+ /*
+ * as the extended operation has no dn
+ * we need to send it to all partitions
+ */
+
+ ac = partition_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return partition_send_all(module, ac, req);
+}
+
+static int sort_compare(void *void1,
+ void *void2, void *opaque)
+{
+ struct dsdb_control_current_partition **pp1 =
+ (struct dsdb_control_current_partition **)void1;
+ struct dsdb_control_current_partition **pp2 =
+ (struct dsdb_control_current_partition **)void2;
+ struct dsdb_control_current_partition *partition1 = talloc_get_type(*pp1,
+ struct dsdb_control_current_partition);
+ struct dsdb_control_current_partition *partition2 = talloc_get_type(*pp2,
+ struct dsdb_control_current_partition);
+
+ return ldb_dn_compare(partition1->dn, partition2->dn);
+}
+
+static int partition_init(struct ldb_module *module)
+{
+ int ret, i;
+ TALLOC_CTX *mem_ctx = talloc_new(module);
+ const char *attrs[] = { "partition", "replicateEntries", "modules", NULL };
+ struct ldb_result *res;
+ struct ldb_message *msg;
+ struct ldb_message_element *partition_attributes;
+ struct ldb_message_element *replicate_attributes;
+ struct ldb_message_element *modules_attributes;
+
+ struct partition_private_data *data;
+
+ if (!mem_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ data = talloc(mem_ctx, struct partition_private_data);
+ if (data == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_search(module->ldb, ldb_dn_new(mem_ctx, module->ldb, "@PARTITION"),
+ LDB_SCOPE_BASE,
+ NULL, attrs,
+ &res);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ talloc_steal(mem_ctx, res);
+ if (res->count == 0) {
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ }
+
+ if (res->count > 1) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ msg = res->msgs[0];
+
+ partition_attributes = ldb_msg_find_element(msg, "partition");
+ if (!partition_attributes) {
+ ldb_set_errstring(module->ldb, "partition_init: no partitions specified");
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ data->partitions = talloc_array(data, struct dsdb_control_current_partition *, partition_attributes->num_values + 1);
+ if (!data->partitions) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ for (i=0; i < partition_attributes->num_values; i++) {
+ char *base = talloc_strdup(data->partitions, (char *)partition_attributes->values[i].data);
+ char *p = strchr(base, ':');
+ if (!p) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "invalid form for partition record (missing ':'): %s", base);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ p[0] = '\0';
+ p++;
+ if (!p[0]) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "invalid form for partition record (missing backend database): %s", base);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ data->partitions[i] = talloc(data->partitions, struct dsdb_control_current_partition);
+ if (!data->partitions[i]) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ data->partitions[i]->version = DSDB_CONTROL_CURRENT_PARTITION_VERSION;
+
+ data->partitions[i]->dn = ldb_dn_new(data->partitions[i], module->ldb, base);
+ if (!data->partitions[i]->dn) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: invalid DN in partition record: %s", base);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ data->partitions[i]->backend = samdb_relative_path(module->ldb,
+ data->partitions[i],
+ p);
+ if (!data->partitions[i]->backend) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: unable to determine an relative path for partition: %s", base);
+ talloc_free(mem_ctx);
+ }
+ ret = ldb_connect_backend(module->ldb, data->partitions[i]->backend, NULL, &data->partitions[i]->module);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+ data->partitions[i] = NULL;
+
+ /* sort these into order, most to least specific */
+ ldb_qsort(data->partitions, partition_attributes->num_values, sizeof(*data->partitions),
+ module->ldb, sort_compare);
+
+ for (i=0; data->partitions[i]; i++) {
+ struct ldb_request *req;
+ req = talloc_zero(mem_ctx, struct ldb_request);
+ if (req == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "partition: Out of memory!\n");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ req->operation = LDB_REQ_REGISTER_PARTITION;
+ req->op.reg_partition.dn = data->partitions[i]->dn;
+
+ ret = ldb_request(module->ldb, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "partition: Unable to register partition with rootdse!\n");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OTHER;
+ }
+ talloc_free(req);
+ }
+
+ replicate_attributes = ldb_msg_find_element(msg, "replicateEntries");
+ if (!replicate_attributes) {
+ data->replicate = NULL;
+ } else {
+ data->replicate = talloc_array(data, struct ldb_dn *, replicate_attributes->num_values + 1);
+ if (!data->replicate) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; i < replicate_attributes->num_values; i++) {
+ data->replicate[i] = ldb_dn_from_ldb_val(data->replicate, module->ldb, &replicate_attributes->values[i]);
+ if (!ldb_dn_validate(data->replicate[i])) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "invalid DN in partition replicate record: %s",
+ replicate_attributes->values[i].data);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+ data->replicate[i] = NULL;
+ }
+
+ /* Make the private data available to any searches the modules may trigger in initialisation */
+ module->private_data = data;
+ talloc_steal(module, data);
+
+ modules_attributes = ldb_msg_find_element(msg, "modules");
+ if (modules_attributes) {
+ for (i=0; i < modules_attributes->num_values; i++) {
+ struct ldb_dn *base_dn;
+ int partition_idx;
+ struct dsdb_control_current_partition *partition = NULL;
+ const char **modules = NULL;
+
+ char *base = talloc_strdup(data->partitions, (char *)modules_attributes->values[i].data);
+ char *p = strchr(base, ':');
+ if (!p) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "invalid form for partition module record (missing ':'): %s", base);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ p[0] = '\0';
+ p++;
+ if (!p[0]) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "invalid form for partition module record (missing backend database): %s", base);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ modules = ldb_modules_list_from_string(module->ldb, mem_ctx,
+ p);
+
+ base_dn = ldb_dn_new(mem_ctx, module->ldb, base);
+ if (!ldb_dn_validate(base_dn)) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (partition_idx = 0; data->partitions[partition_idx]; partition_idx++) {
+ if (ldb_dn_compare(data->partitions[partition_idx]->dn, base_dn) == 0) {
+ partition = data->partitions[partition_idx];
+ break;
+ }
+ }
+
+ if (!partition) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "invalid form for partition module record (no such partition): %s", base);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ ret = ldb_load_modules_list(module->ldb, modules, partition->module, &partition->module);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "loading backend for %s failed: %s",
+ base, ldb_errstring(module->ldb));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ ret = ldb_init_module_chain(module->ldb, partition->module);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "partition_init: "
+ "initialising backend for %s failed: %s",
+ base, ldb_errstring(module->ldb));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+ }
+
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+}
+
+static int partition_wait_none(struct ldb_handle *handle) {
+ struct partition_context *ac;
+ int ret;
+ int i;
+
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct partition_context);
+
+ for (i=0; i < ac->num_requests; i++) {
+ ret = ldb_wait(ac->down_req[i]->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req[i]->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req[i]->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req[i]->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+ }
+
+ ret = LDB_SUCCESS;
+
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+}
+
+
+static int partition_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = partition_wait_none(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int partition_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return partition_wait_all(handle);
+ } else {
+ return partition_wait_none(handle);
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_partition_module_ops = {
+ .name = "partition",
+ .init_context = partition_init,
+ .search = partition_search,
+ .add = partition_add,
+ .modify = partition_modify,
+ .del = partition_delete,
+ .rename = partition_rename,
+ .extended = partition_extended,
+ .sequence_number = partition_sequence_number,
+ .start_transaction = partition_start_trans,
+ .end_transaction = partition_end_trans,
+ .del_transaction = partition_del_trans,
+ .wait = partition_wait
+};
diff --git a/source4/dsdb/samdb/ldb_modules/password_hash.c b/source4/dsdb/samdb/ldb_modules/password_hash.c
new file mode 100644
index 0000000000..69783aefa8
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/password_hash.c
@@ -0,0 +1,2281 @@
+/*
+ ldb database module
+
+ Copyright (C) Simo Sorce 2004-2006
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2006
+ Copyright (C) Andrew Tridgell 2004
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb password_hash module
+ *
+ * Description: correctly update hash values based on changes to userPassword and friends
+ *
+ * Author: Andrew Bartlett
+ * Author: Stefan Metzmacher
+ */
+
+#include "includes.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_private.h"
+#include "librpc/gen_ndr/misc.h"
+#include "librpc/gen_ndr/samr.h"
+#include "libcli/auth/libcli_auth.h"
+#include "libcli/security/security.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "system/time.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/flags.h"
+#include "dsdb/samdb/ldb_modules/password_modules.h"
+#include "librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "lib/crypto/crypto.h"
+#include "param/param.h"
+
+/* If we have decided there is reason to work on this request, then
+ * setup all the password hash types correctly.
+ *
+ * If the administrator doesn't want the userPassword stored (set in the
+ * domain and per-account policies) then we must strip that out before
+ * we do the first operation.
+ *
+ * Once this is done (which could update anything at all), we
+ * calculate the password hashes.
+ *
+ * This function must not only update the unicodePwd, dBCSPwd and
+ * supplementalCredentials fields, it must also atomicly increment the
+ * msDS-KeyVersionNumber. We should be in a transaction, so all this
+ * should be quite safe...
+ *
+ * Finally, if the administrator has requested that a password history
+ * be maintained, then this should also be written out.
+ *
+ */
+
+struct ph_context {
+
+ enum ph_type {PH_ADD, PH_MOD} type;
+ enum ph_step {PH_ADD_SEARCH_DOM, PH_ADD_DO_ADD, PH_MOD_DO_REQ, PH_MOD_SEARCH_SELF, PH_MOD_SEARCH_DOM, PH_MOD_DO_MOD} step;
+
+ struct ldb_module *module;
+ struct ldb_request *orig_req;
+
+ struct ldb_request *dom_req;
+ struct ldb_reply *dom_res;
+
+ struct ldb_request *down_req;
+
+ struct ldb_request *search_req;
+ struct ldb_reply *search_res;
+
+ struct ldb_request *mod_req;
+
+ struct dom_sid *domain_sid;
+};
+
+struct domain_data {
+ bool store_cleartext;
+ uint_t pwdProperties;
+ uint_t pwdHistoryLength;
+ char *netbios_domain;
+ char *dns_domain;
+ char *realm;
+};
+
+struct setup_password_fields_io {
+ struct ph_context *ac;
+ struct domain_data *domain;
+ struct smb_krb5_context *smb_krb5_context;
+
+ /* infos about the user account */
+ struct {
+ uint32_t user_account_control;
+ const char *sAMAccountName;
+ const char *user_principal_name;
+ bool is_computer;
+ } u;
+
+ /* new credentials */
+ struct {
+ const char *cleartext;
+ struct samr_Password *nt_hash;
+ struct samr_Password *lm_hash;
+ } n;
+
+ /* old credentials */
+ struct {
+ uint32_t nt_history_len;
+ struct samr_Password *nt_history;
+ uint32_t lm_history_len;
+ struct samr_Password *lm_history;
+ const struct ldb_val *supplemental;
+ struct supplementalCredentialsBlob scb;
+ uint32_t kvno;
+ } o;
+
+ /* generated credentials */
+ struct {
+ struct samr_Password *nt_hash;
+ struct samr_Password *lm_hash;
+ uint32_t nt_history_len;
+ struct samr_Password *nt_history;
+ uint32_t lm_history_len;
+ struct samr_Password *lm_history;
+ const char *salt;
+ DATA_BLOB aes_256;
+ DATA_BLOB aes_128;
+ DATA_BLOB des_md5;
+ DATA_BLOB des_crc;
+ struct ldb_val supplemental;
+ NTTIME last_set;
+ uint32_t kvno;
+ } g;
+};
+
+static int setup_nt_fields(struct setup_password_fields_io *io)
+{
+ uint32_t i;
+
+ io->g.nt_hash = io->n.nt_hash;
+
+ if (io->domain->pwdHistoryLength == 0) {
+ return LDB_SUCCESS;
+ }
+
+ /* We might not have an old NT password */
+ io->g.nt_history = talloc_array(io->ac,
+ struct samr_Password,
+ io->domain->pwdHistoryLength);
+ if (!io->g.nt_history) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i = 0; i < MIN(io->domain->pwdHistoryLength-1, io->o.nt_history_len); i++) {
+ io->g.nt_history[i+1] = io->o.nt_history[i];
+ }
+ io->g.nt_history_len = i + 1;
+
+ if (io->g.nt_hash) {
+ io->g.nt_history[0] = *io->g.nt_hash;
+ } else {
+ /*
+ * TODO: is this correct?
+ * the simular behavior is correct for the lm history case
+ */
+ E_md4hash("", io->g.nt_history[0].hash);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_lm_fields(struct setup_password_fields_io *io)
+{
+ uint32_t i;
+
+ io->g.lm_hash = io->n.lm_hash;
+
+ if (io->domain->pwdHistoryLength == 0) {
+ return LDB_SUCCESS;
+ }
+
+ /* We might not have an old NT password */
+ io->g.lm_history = talloc_array(io->ac,
+ struct samr_Password,
+ io->domain->pwdHistoryLength);
+ if (!io->g.lm_history) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i = 0; i < MIN(io->domain->pwdHistoryLength-1, io->o.lm_history_len); i++) {
+ io->g.lm_history[i+1] = io->o.lm_history[i];
+ }
+ io->g.lm_history_len = i + 1;
+
+ if (io->g.lm_hash) {
+ io->g.lm_history[0] = *io->g.lm_hash;
+ } else {
+ E_deshash("", io->g.lm_history[0].hash);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_kerberos_keys(struct setup_password_fields_io *io)
+{
+ krb5_error_code krb5_ret;
+ Principal *salt_principal;
+ krb5_salt salt;
+ krb5_keyblock key;
+
+ /* Many, many thanks to lukeh@padl.com for this
+ * algorithm, described in his Nov 10 2004 mail to
+ * samba-technical@samba.org */
+
+ /*
+ * Determine a salting principal
+ */
+ if (io->u.is_computer) {
+ char *name;
+ char *saltbody;
+
+ name = talloc_strdup(io->ac, io->u.sAMAccountName);
+ if (!name) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (name[strlen(name)-1] == '$') {
+ name[strlen(name)-1] = '\0';
+ }
+
+ saltbody = talloc_asprintf(io->ac, "%s.%s", name, io->domain->dns_domain);
+ if (!saltbody) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ krb5_ret = krb5_make_principal(io->smb_krb5_context->krb5_context,
+ &salt_principal,
+ io->domain->realm, "host",
+ saltbody, NULL);
+ } else if (io->u.user_principal_name) {
+ char *user_principal_name;
+ char *p;
+
+ user_principal_name = talloc_strdup(io->ac, io->u.user_principal_name);
+ if (!user_principal_name) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ p = strchr(user_principal_name, '@');
+ if (p) {
+ p[0] = '\0';
+ }
+
+ krb5_ret = krb5_make_principal(io->smb_krb5_context->krb5_context,
+ &salt_principal,
+ io->domain->realm, user_principal_name,
+ NULL);
+ } else {
+ krb5_ret = krb5_make_principal(io->smb_krb5_context->krb5_context,
+ &salt_principal,
+ io->domain->realm, io->u.sAMAccountName,
+ NULL);
+ }
+ if (krb5_ret) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_kerberos_keys: "
+ "generation of a salting principal failed: %s",
+ smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * create salt from salt_principal
+ */
+ krb5_ret = krb5_get_pw_salt(io->smb_krb5_context->krb5_context,
+ salt_principal, &salt);
+ krb5_free_principal(io->smb_krb5_context->krb5_context, salt_principal);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_kerberos_keys: "
+ "generation of krb5_salt failed: %s",
+ smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* create a talloc copy */
+ io->g.salt = talloc_strndup(io->ac,
+ salt.saltvalue.data,
+ salt.saltvalue.length);
+ krb5_free_salt(io->smb_krb5_context->krb5_context, salt);
+ if (!io->g.salt) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ salt.saltvalue.data = discard_const(io->g.salt);
+ salt.saltvalue.length = strlen(io->g.salt);
+
+ /*
+ * create ENCTYPE_AES256_CTS_HMAC_SHA1_96 key out of
+ * the salt and the cleartext password
+ */
+ krb5_ret = krb5_string_to_key_salt(io->smb_krb5_context->krb5_context,
+ ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+ io->n.cleartext,
+ salt,
+ &key);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_kerberos_keys: "
+ "generation of a aes256-cts-hmac-sha1-96 key failed: %s",
+ smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ io->g.aes_256 = data_blob_talloc(io->ac,
+ key.keyvalue.data,
+ key.keyvalue.length);
+ krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key);
+ if (!io->g.aes_256.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * create ENCTYPE_AES128_CTS_HMAC_SHA1_96 key out of
+ * the salt and the cleartext password
+ */
+ krb5_ret = krb5_string_to_key_salt(io->smb_krb5_context->krb5_context,
+ ENCTYPE_AES128_CTS_HMAC_SHA1_96,
+ io->n.cleartext,
+ salt,
+ &key);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_kerberos_keys: "
+ "generation of a aes128-cts-hmac-sha1-96 key failed: %s",
+ smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ io->g.aes_128 = data_blob_talloc(io->ac,
+ key.keyvalue.data,
+ key.keyvalue.length);
+ krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key);
+ if (!io->g.aes_128.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * create ENCTYPE_DES_CBC_MD5 key out of
+ * the salt and the cleartext password
+ */
+ krb5_ret = krb5_string_to_key_salt(io->smb_krb5_context->krb5_context,
+ ENCTYPE_DES_CBC_MD5,
+ io->n.cleartext,
+ salt,
+ &key);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_kerberos_keys: "
+ "generation of a des-cbc-md5 key failed: %s",
+ smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ io->g.des_md5 = data_blob_talloc(io->ac,
+ key.keyvalue.data,
+ key.keyvalue.length);
+ krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key);
+ if (!io->g.des_md5.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * create ENCTYPE_DES_CBC_CRC key out of
+ * the salt and the cleartext password
+ */
+ krb5_ret = krb5_string_to_key_salt(io->smb_krb5_context->krb5_context,
+ ENCTYPE_DES_CBC_CRC,
+ io->n.cleartext,
+ salt,
+ &key);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_kerberos_keys: "
+ "generation of a des-cbc-crc key failed: %s",
+ smb_get_krb5_error_message(io->smb_krb5_context->krb5_context, krb5_ret, io->ac));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ io->g.des_crc = data_blob_talloc(io->ac,
+ key.keyvalue.data,
+ key.keyvalue.length);
+ krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key);
+ if (!io->g.des_crc.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_primary_kerberos(struct setup_password_fields_io *io,
+ const struct supplementalCredentialsBlob *old_scb,
+ struct package_PrimaryKerberosBlob *pkb)
+{
+ struct package_PrimaryKerberosCtr3 *pkb3 = &pkb->ctr.ctr3;
+ struct supplementalCredentialsPackage *old_scp = NULL;
+ struct package_PrimaryKerberosBlob _old_pkb;
+ struct package_PrimaryKerberosCtr3 *old_pkb3 = NULL;
+ uint32_t i;
+ enum ndr_err_code ndr_err;
+
+ /*
+ * prepare generation of keys
+ *
+ * ENCTYPE_DES_CBC_MD5
+ * ENCTYPE_DES_CBC_CRC
+ */
+ pkb->version = 3;
+ pkb3->salt.string = io->g.salt;
+ pkb3->num_keys = 2;
+ pkb3->keys = talloc_array(io->ac,
+ struct package_PrimaryKerberosKey3,
+ pkb3->num_keys);
+ if (!pkb3->keys) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ pkb3->keys[0].keytype = ENCTYPE_DES_CBC_MD5;
+ pkb3->keys[0].value = &io->g.des_md5;
+ pkb3->keys[1].keytype = ENCTYPE_DES_CBC_CRC;
+ pkb3->keys[1].value = &io->g.des_crc;
+
+ /* initialize the old keys to zero */
+ pkb3->num_old_keys = 0;
+ pkb3->old_keys = NULL;
+
+ /* if there're no old keys, then we're done */
+ if (!old_scb) {
+ return LDB_SUCCESS;
+ }
+
+ for (i=0; i < old_scb->sub.num_packages; i++) {
+ if (strcmp("Primary:Kerberos", old_scb->sub.packages[i].name) != 0) {
+ continue;
+ }
+
+ if (!old_scb->sub.packages[i].data || !old_scb->sub.packages[i].data[0]) {
+ continue;
+ }
+
+ old_scp = &old_scb->sub.packages[i];
+ break;
+ }
+ /* Primary:Kerberos element of supplementalCredentials */
+ if (old_scp) {
+ DATA_BLOB blob;
+
+ blob = strhex_to_data_blob(old_scp->data);
+ if (!blob.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ talloc_steal(io->ac, blob.data);
+
+ /* TODO: use ndr_pull_struct_blob_all(), when the ndr layer handles it correct with relative pointers */
+ ndr_err = ndr_pull_struct_blob(&blob, io->ac, lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")), &_old_pkb,
+ (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_primary_kerberos: "
+ "failed to pull old package_PrimaryKerberosBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (_old_pkb.version != 3) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_primary_kerberos: "
+ "package_PrimaryKerberosBlob version[%u] expected[3]",
+ _old_pkb.version);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ old_pkb3 = &_old_pkb.ctr.ctr3;
+ }
+
+ /* if we didn't found the old keys we're done */
+ if (!old_pkb3) {
+ return LDB_SUCCESS;
+ }
+
+ /* fill in the old keys */
+ pkb3->num_old_keys = old_pkb3->num_keys;
+ pkb3->old_keys = old_pkb3->keys;
+
+ return LDB_SUCCESS;
+}
+
+static int setup_primary_kerberos_newer(struct setup_password_fields_io *io,
+ const struct supplementalCredentialsBlob *old_scb,
+ struct package_PrimaryKerberosBlob *pkb)
+{
+ struct package_PrimaryKerberosCtr4 *pkb4 = &pkb->ctr.ctr4;
+ struct supplementalCredentialsPackage *old_scp = NULL;
+ struct package_PrimaryKerberosBlob _old_pkb;
+ struct package_PrimaryKerberosCtr4 *old_pkb4 = NULL;
+ uint32_t i;
+ enum ndr_err_code ndr_err;
+
+ /*
+ * prepare generation of keys
+ *
+ * ENCTYPE_AES256_CTS_HMAC_SHA1_96
+ * ENCTYPE_AES128_CTS_HMAC_SHA1_96
+ * ENCTYPE_DES_CBC_MD5
+ * ENCTYPE_DES_CBC_CRC
+ */
+ pkb->version = 4;
+ pkb4->salt.string = io->g.salt;
+ pkb4->default_iteration_count = 4096;
+ pkb4->num_keys = 4;
+
+ pkb4->keys = talloc_array(io->ac,
+ struct package_PrimaryKerberosKey4,
+ pkb4->num_keys);
+ if (!pkb4->keys) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ pkb4->keys[0].iteration_count = 4096;
+ pkb4->keys[0].keytype = ENCTYPE_AES256_CTS_HMAC_SHA1_96;
+ pkb4->keys[0].value = &io->g.aes_256;
+ pkb4->keys[1].iteration_count = 4096;
+ pkb4->keys[1].keytype = ENCTYPE_AES128_CTS_HMAC_SHA1_96;
+ pkb4->keys[1].value = &io->g.aes_128;
+ pkb4->keys[2].iteration_count = 4096;
+ pkb4->keys[2].keytype = ENCTYPE_DES_CBC_MD5;
+ pkb4->keys[2].value = &io->g.des_md5;
+ pkb4->keys[3].iteration_count = 4096;
+ pkb4->keys[3].keytype = ENCTYPE_DES_CBC_CRC;
+ pkb4->keys[3].value = &io->g.des_crc;
+
+ /* initialize the old keys to zero */
+ pkb4->num_old_keys = 0;
+ pkb4->old_keys = NULL;
+ pkb4->num_older_keys = 0;
+ pkb4->older_keys = NULL;
+
+ /* if there're no old keys, then we're done */
+ if (!old_scb) {
+ return LDB_SUCCESS;
+ }
+
+ for (i=0; i < old_scb->sub.num_packages; i++) {
+ if (strcmp("Primary:Kerberos-Newer-Keys", old_scb->sub.packages[i].name) != 0) {
+ continue;
+ }
+
+ if (!old_scb->sub.packages[i].data || !old_scb->sub.packages[i].data[0]) {
+ continue;
+ }
+
+ old_scp = &old_scb->sub.packages[i];
+ break;
+ }
+ /* Primary:Kerberos-Newer-Keys element of supplementalCredentials */
+ if (old_scp) {
+ DATA_BLOB blob;
+
+ blob = strhex_to_data_blob(old_scp->data);
+ if (!blob.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ talloc_steal(io->ac, blob.data);
+
+ /* TODO: use ndr_pull_struct_blob_all(), when the ndr layer handles it correct with relative pointers */
+ ndr_err = ndr_pull_struct_blob(&blob, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &_old_pkb,
+ (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_primary_kerberos_newer: "
+ "failed to pull old package_PrimaryKerberosBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (_old_pkb.version != 4) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_primary_kerberos_newer: "
+ "package_PrimaryKerberosBlob version[%u] expected[4]",
+ _old_pkb.version);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ old_pkb4 = &_old_pkb.ctr.ctr4;
+ }
+
+ /* if we didn't found the old keys we're done */
+ if (!old_pkb4) {
+ return LDB_SUCCESS;
+ }
+
+ /* fill in the old keys */
+ pkb4->num_old_keys = old_pkb4->num_keys;
+ pkb4->old_keys = old_pkb4->keys;
+ pkb4->num_older_keys = old_pkb4->num_old_keys;
+ pkb4->older_keys = old_pkb4->old_keys;
+
+ return LDB_SUCCESS;
+}
+
+static int setup_primary_wdigest(struct setup_password_fields_io *io,
+ const struct supplementalCredentialsBlob *old_scb,
+ struct package_PrimaryWDigestBlob *pdb)
+{
+ DATA_BLOB sAMAccountName;
+ DATA_BLOB sAMAccountName_l;
+ DATA_BLOB sAMAccountName_u;
+ const char *user_principal_name = io->u.user_principal_name;
+ DATA_BLOB userPrincipalName;
+ DATA_BLOB userPrincipalName_l;
+ DATA_BLOB userPrincipalName_u;
+ DATA_BLOB netbios_domain;
+ DATA_BLOB netbios_domain_l;
+ DATA_BLOB netbios_domain_u;
+ DATA_BLOB dns_domain;
+ DATA_BLOB dns_domain_l;
+ DATA_BLOB dns_domain_u;
+ DATA_BLOB cleartext;
+ DATA_BLOB digest;
+ DATA_BLOB delim;
+ DATA_BLOB backslash;
+ uint8_t i;
+ struct {
+ DATA_BLOB *user;
+ DATA_BLOB *realm;
+ DATA_BLOB *nt4dom;
+ } wdigest[] = {
+ /*
+ * See
+ * http://technet2.microsoft.com/WindowsServer/en/library/717b450c-f4a0-4cc9-86f4-cc0633aae5f91033.mspx?mfr=true
+ * for what precalculated hashes are supposed to be stored...
+ *
+ * I can't reproduce all values which should contain "Digest" as realm,
+ * am I doing something wrong or is w2k3 just broken...?
+ *
+ * W2K3 fills in following for a user:
+ *
+ * dn: CN=NewUser,OU=newtop,DC=sub1,DC=w2k3,DC=vmnet1,DC=vm,DC=base
+ * sAMAccountName: NewUser2Sam
+ * userPrincipalName: NewUser2Princ@sub1.w2k3.vmnet1.vm.base
+ *
+ * 4279815024bda54fc074a5f8bd0a6e6f => NewUser2Sam:SUB1:TestPwd2007
+ * b7ec9da91062199aee7d121e6710fe23 => newuser2sam:sub1:TestPwd2007
+ * 17d290bc5c9f463fac54c37a8cea134d => NEWUSER2SAM:SUB1:TestPwd2007
+ * 4279815024bda54fc074a5f8bd0a6e6f => NewUser2Sam:SUB1:TestPwd2007
+ * 5d57e7823938348127322e08cd81bcb5 => NewUser2Sam:sub1:TestPwd2007
+ * 07dd701bf8a011ece585de3d47237140 => NEWUSER2SAM:sub1:TestPwd2007
+ * e14fb0eb401498d2cb33c9aae1cc7f37 => newuser2sam:SUB1:TestPwd2007
+ * 8dadc90250f873d8b883f79d890bef82 => NewUser2Sam:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * f52da1266a6bdd290ffd48b2c823dda7 => newuser2sam:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * d2b42f171248cec37a3c5c6b55404062 => NEWUSER2SAM:SUB1.W2K3.VMNET1.VM.BASE:TestPwd2007
+ * fff8d790ff6c152aaeb6ebe17b4021de => NewUser2Sam:SUB1.W2K3.VMNET1.VM.BASE:TestPwd2007
+ * 8dadc90250f873d8b883f79d890bef82 => NewUser2Sam:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * 2a7563c3715bc418d626dabef378c008 => NEWUSER2SAM:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * c8e9557a87cd4200fda0c11d2fa03f96 => newuser2sam:SUB1.W2K3.VMNET1.VM.BASE:TestPwd2007
+ * 221c55284451ae9b3aacaa2a3c86f10f => NewUser2Princ@sub1.w2k3.vmnet1.vm.base::TestPwd2007
+ * 74e1be668853d4324d38c07e2acfb8ea => (w2k3 has a bug here!) newuser2princ@sub1.w2k3.vmnet1.vm.base::TestPwd2007
+ * e1e244ab7f098e3ae1761be7f9229bbb => NEWUSER2PRINC@SUB1.W2K3.VMNET1.VM.BASE::TestPwd2007
+ * 86db637df42513039920e605499c3af6 => SUB1\NewUser2Sam::TestPwd2007
+ * f5e43474dfaf067fee8197a253debaa2 => sub1\newuser2sam::TestPwd2007
+ * 2ecaa8382e2518e4b77a52422b279467 => SUB1\NEWUSER2SAM::TestPwd2007
+ * 31dc704d3640335b2123d4ee28aa1f11 => ??? changes with NewUser2Sam => NewUser1Sam
+ * 36349f5cecd07320fb3bb0e119230c43 => ??? changes with NewUser2Sam => NewUser1Sam
+ * 12adf019d037fb535c01fd0608e78d9d => ??? changes with NewUser2Sam => NewUser1Sam
+ * 6feecf8e724906f3ee1105819c5105a1 => ??? changes with NewUser2Princ => NewUser1Princ
+ * 6c6911f3de6333422640221b9c51ff1f => ??? changes with NewUser2Princ => NewUser1Princ
+ * 4b279877e742895f9348ac67a8de2f69 => ??? changes with NewUser2Princ => NewUser1Princ
+ * db0c6bff069513e3ebb9870d29b57490 => ??? changes with NewUser2Sam => NewUser1Sam
+ * 45072621e56b1c113a4e04a8ff68cd0e => ??? changes with NewUser2Sam => NewUser1Sam
+ * 11d1220abc44a9c10cf91ef4a9c1de02 => ??? changes with NewUser2Sam => NewUser1Sam
+ *
+ * dn: CN=NewUser,OU=newtop,DC=sub1,DC=w2k3,DC=vmnet1,DC=vm,DC=base
+ * sAMAccountName: NewUser2Sam
+ *
+ * 4279815024bda54fc074a5f8bd0a6e6f => NewUser2Sam:SUB1:TestPwd2007
+ * b7ec9da91062199aee7d121e6710fe23 => newuser2sam:sub1:TestPwd2007
+ * 17d290bc5c9f463fac54c37a8cea134d => NEWUSER2SAM:SUB1:TestPwd2007
+ * 4279815024bda54fc074a5f8bd0a6e6f => NewUser2Sam:SUB1:TestPwd2007
+ * 5d57e7823938348127322e08cd81bcb5 => NewUser2Sam:sub1:TestPwd2007
+ * 07dd701bf8a011ece585de3d47237140 => NEWUSER2SAM:sub1:TestPwd2007
+ * e14fb0eb401498d2cb33c9aae1cc7f37 => newuser2sam:SUB1:TestPwd2007
+ * 8dadc90250f873d8b883f79d890bef82 => NewUser2Sam:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * f52da1266a6bdd290ffd48b2c823dda7 => newuser2sam:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * d2b42f171248cec37a3c5c6b55404062 => NEWUSER2SAM:SUB1.W2K3.VMNET1.VM.BASE:TestPwd2007
+ * fff8d790ff6c152aaeb6ebe17b4021de => NewUser2Sam:SUB1.W2K3.VMNET1.VM.BASE:TestPwd2007
+ * 8dadc90250f873d8b883f79d890bef82 => NewUser2Sam:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * 2a7563c3715bc418d626dabef378c008 => NEWUSER2SAM:sub1.w2k3.vmnet1.vm.base:TestPwd2007
+ * c8e9557a87cd4200fda0c11d2fa03f96 => newuser2sam:SUB1.W2K3.VMNET1.VM.BASE:TestPwd2007
+ * 8a140d30b6f0a5912735dc1e3bc993b4 => NewUser2Sam@sub1.w2k3.vmnet1.vm.base::TestPwd2007
+ * 86d95b2faae6cae4ec261e7fbaccf093 => (here w2k3 is correct) newuser2sam@sub1.w2k3.vmnet1.vm.base::TestPwd2007
+ * dfeff1493110220efcdfc6362e5f5450 => NEWUSER2SAM@SUB1.W2K3.VMNET1.VM.BASE::TestPwd2007
+ * 86db637df42513039920e605499c3af6 => SUB1\NewUser2Sam::TestPwd2007
+ * f5e43474dfaf067fee8197a253debaa2 => sub1\newuser2sam::TestPwd2007
+ * 2ecaa8382e2518e4b77a52422b279467 => SUB1\NEWUSER2SAM::TestPwd2007
+ * 31dc704d3640335b2123d4ee28aa1f11 => ???M1 changes with NewUser2Sam => NewUser1Sam
+ * 36349f5cecd07320fb3bb0e119230c43 => ???M1.L changes with newuser2sam => newuser1sam
+ * 12adf019d037fb535c01fd0608e78d9d => ???M1.U changes with NEWUSER2SAM => NEWUSER1SAM
+ * 569b4533f2d9e580211dd040e5e360a8 => ???M2 changes with NewUser2Princ => NewUser1Princ
+ * 52528bddf310a587c5d7e6a9ae2cbb20 => ???M2.L changes with newuser2princ => newuser1princ
+ * 4f629a4f0361289ca4255ab0f658fcd5 => ???M3 changes with NewUser2Princ => NewUser1Princ (doesn't depend on case of userPrincipal )
+ * db0c6bff069513e3ebb9870d29b57490 => ???M4 changes with NewUser2Sam => NewUser1Sam
+ * 45072621e56b1c113a4e04a8ff68cd0e => ???M5 changes with NewUser2Sam => NewUser1Sam (doesn't depend on case of sAMAccountName)
+ * 11d1220abc44a9c10cf91ef4a9c1de02 => ???M4.U changes with NEWUSER2SAM => NEWUSER1SAM
+ */
+
+ /*
+ * sAMAccountName, netbios_domain
+ */
+ {
+ .user = &sAMAccountName,
+ .realm = &netbios_domain,
+ },
+ {
+ .user = &sAMAccountName_l,
+ .realm = &netbios_domain_l,
+ },
+ {
+ .user = &sAMAccountName_u,
+ .realm = &netbios_domain_u,
+ },
+ {
+ .user = &sAMAccountName,
+ .realm = &netbios_domain_u,
+ },
+ {
+ .user = &sAMAccountName,
+ .realm = &netbios_domain_l,
+ },
+ {
+ .user = &sAMAccountName_u,
+ .realm = &netbios_domain_l,
+ },
+ {
+ .user = &sAMAccountName_l,
+ .realm = &netbios_domain_u,
+ },
+ /*
+ * sAMAccountName, dns_domain
+ */
+ {
+ .user = &sAMAccountName,
+ .realm = &dns_domain,
+ },
+ {
+ .user = &sAMAccountName_l,
+ .realm = &dns_domain_l,
+ },
+ {
+ .user = &sAMAccountName_u,
+ .realm = &dns_domain_u,
+ },
+ {
+ .user = &sAMAccountName,
+ .realm = &dns_domain_u,
+ },
+ {
+ .user = &sAMAccountName,
+ .realm = &dns_domain_l,
+ },
+ {
+ .user = &sAMAccountName_u,
+ .realm = &dns_domain_l,
+ },
+ {
+ .user = &sAMAccountName_l,
+ .realm = &dns_domain_u,
+ },
+ /*
+ * userPrincipalName, no realm
+ */
+ {
+ .user = &userPrincipalName,
+ },
+ {
+ /*
+ * NOTE: w2k3 messes this up, if the user has a real userPrincipalName,
+ * the fallback to the sAMAccountName based userPrincipalName is correct
+ */
+ .user = &userPrincipalName_l,
+ },
+ {
+ .user = &userPrincipalName_u,
+ },
+ /*
+ * nt4dom\sAMAccountName, no realm
+ */
+ {
+ .user = &sAMAccountName,
+ .nt4dom = &netbios_domain
+ },
+ {
+ .user = &sAMAccountName_l,
+ .nt4dom = &netbios_domain_l
+ },
+ {
+ .user = &sAMAccountName_u,
+ .nt4dom = &netbios_domain_u
+ },
+
+ /*
+ * the following ones are guessed depending on the technet2 article
+ * but not reproducable on a w2k3 server
+ */
+ /* sAMAccountName with "Digest" realm */
+ {
+ .user = &sAMAccountName,
+ .realm = &digest
+ },
+ {
+ .user = &sAMAccountName_l,
+ .realm = &digest
+ },
+ {
+ .user = &sAMAccountName_u,
+ .realm = &digest
+ },
+ /* userPrincipalName with "Digest" realm */
+ {
+ .user = &userPrincipalName,
+ .realm = &digest
+ },
+ {
+ .user = &userPrincipalName_l,
+ .realm = &digest
+ },
+ {
+ .user = &userPrincipalName_u,
+ .realm = &digest
+ },
+ /* nt4dom\\sAMAccountName with "Digest" realm */
+ {
+ .user = &sAMAccountName,
+ .nt4dom = &netbios_domain,
+ .realm = &digest
+ },
+ {
+ .user = &sAMAccountName_l,
+ .nt4dom = &netbios_domain_l,
+ .realm = &digest
+ },
+ {
+ .user = &sAMAccountName_u,
+ .nt4dom = &netbios_domain_u,
+ .realm = &digest
+ },
+ };
+
+ /* prepare DATA_BLOB's used in the combinations array */
+ sAMAccountName = data_blob_string_const(io->u.sAMAccountName);
+ sAMAccountName_l = data_blob_string_const(strlower_talloc(io->ac, io->u.sAMAccountName));
+ if (!sAMAccountName_l.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ sAMAccountName_u = data_blob_string_const(strupper_talloc(io->ac, io->u.sAMAccountName));
+ if (!sAMAccountName_u.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* if the user doesn't have a userPrincipalName, create one (with lower case realm) */
+ if (!user_principal_name) {
+ user_principal_name = talloc_asprintf(io->ac, "%s@%s",
+ io->u.sAMAccountName,
+ io->domain->dns_domain);
+ if (!user_principal_name) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+ userPrincipalName = data_blob_string_const(user_principal_name);
+ userPrincipalName_l = data_blob_string_const(strlower_talloc(io->ac, user_principal_name));
+ if (!userPrincipalName_l.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ userPrincipalName_u = data_blob_string_const(strupper_talloc(io->ac, user_principal_name));
+ if (!userPrincipalName_u.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ netbios_domain = data_blob_string_const(io->domain->netbios_domain);
+ netbios_domain_l = data_blob_string_const(strlower_talloc(io->ac, io->domain->netbios_domain));
+ if (!netbios_domain_l.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ netbios_domain_u = data_blob_string_const(strupper_talloc(io->ac, io->domain->netbios_domain));
+ if (!netbios_domain_u.data) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ dns_domain = data_blob_string_const(io->domain->dns_domain);
+ dns_domain_l = data_blob_string_const(io->domain->dns_domain);
+ dns_domain_u = data_blob_string_const(io->domain->realm);
+
+ cleartext = data_blob_string_const(io->n.cleartext);
+
+ digest = data_blob_string_const("Digest");
+
+ delim = data_blob_string_const(":");
+ backslash = data_blob_string_const("\\");
+
+ pdb->num_hashes = ARRAY_SIZE(wdigest);
+ pdb->hashes = talloc_array(io->ac, struct package_PrimaryWDigestHash, pdb->num_hashes);
+ if (!pdb->hashes) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; i < ARRAY_SIZE(wdigest); i++) {
+ struct MD5Context md5;
+ MD5Init(&md5);
+ if (wdigest[i].nt4dom) {
+ MD5Update(&md5, wdigest[i].nt4dom->data, wdigest[i].nt4dom->length);
+ MD5Update(&md5, backslash.data, backslash.length);
+ }
+ MD5Update(&md5, wdigest[i].user->data, wdigest[i].user->length);
+ MD5Update(&md5, delim.data, delim.length);
+ if (wdigest[i].realm) {
+ MD5Update(&md5, wdigest[i].realm->data, wdigest[i].realm->length);
+ }
+ MD5Update(&md5, delim.data, delim.length);
+ MD5Update(&md5, cleartext.data, cleartext.length);
+ MD5Final(pdb->hashes[i].hash, &md5);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_supplemental_field(struct setup_password_fields_io *io)
+{
+ struct supplementalCredentialsBlob scb;
+ struct supplementalCredentialsBlob _old_scb;
+ struct supplementalCredentialsBlob *old_scb = NULL;
+ /* Packages + (Kerberos-Newer-Keys, Kerberos, WDigest and CLEARTEXT) */
+ uint32_t num_names = 0;
+ const char *names[1+4];
+ uint32_t num_packages = 0;
+ struct supplementalCredentialsPackage packages[1+4];
+ /* Packages */
+ struct supplementalCredentialsPackage *pp = NULL;
+ struct package_PackagesBlob pb;
+ DATA_BLOB pb_blob;
+ char *pb_hexstr;
+ /* Primary:Kerberos-Newer-Keys */
+ const char **nkn = NULL;
+ struct supplementalCredentialsPackage *pkn = NULL;
+ struct package_PrimaryKerberosBlob pknb;
+ DATA_BLOB pknb_blob;
+ char *pknb_hexstr;
+ /* Primary:Kerberos */
+ const char **nk = NULL;
+ struct supplementalCredentialsPackage *pk = NULL;
+ struct package_PrimaryKerberosBlob pkb;
+ DATA_BLOB pkb_blob;
+ char *pkb_hexstr;
+ /* Primary:WDigest */
+ const char **nd = NULL;
+ struct supplementalCredentialsPackage *pd = NULL;
+ struct package_PrimaryWDigestBlob pdb;
+ DATA_BLOB pdb_blob;
+ char *pdb_hexstr;
+ /* Primary:CLEARTEXT */
+ const char **nc = NULL;
+ struct supplementalCredentialsPackage *pc = NULL;
+ struct package_PrimaryCLEARTEXTBlob pcb;
+ DATA_BLOB pcb_blob;
+ char *pcb_hexstr;
+ int ret;
+ enum ndr_err_code ndr_err;
+ uint8_t zero16[16];
+ bool do_newer_keys = false;
+ bool do_cleartext = false;
+
+ ZERO_STRUCT(zero16);
+ ZERO_STRUCT(names);
+
+ if (!io->n.cleartext) {
+ /*
+ * when we don't have a cleartext password
+ * we can't setup a supplementalCredential value
+ */
+ return LDB_SUCCESS;
+ }
+
+ /* if there's an old supplementaCredentials blob then parse it */
+ if (io->o.supplemental) {
+ ndr_err = ndr_pull_struct_blob_all(io->o.supplemental, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &_old_scb,
+ (ndr_pull_flags_fn_t)ndr_pull_supplementalCredentialsBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to pull old supplementalCredentialsBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (_old_scb.sub.signature == SUPPLEMENTAL_CREDENTIALS_SIGNATURE) {
+ old_scb = &_old_scb;
+ } else {
+ ldb_debug(io->ac->module->ldb, LDB_DEBUG_ERROR,
+ "setup_supplemental_field: "
+ "supplementalCredentialsBlob signature[0x%04X] expected[0x%04X]",
+ _old_scb.sub.signature, SUPPLEMENTAL_CREDENTIALS_SIGNATURE);
+ }
+ }
+
+ /* TODO: do the correct check for this, it maybe depends on the functional level? */
+ do_newer_keys = lp_parm_bool(ldb_get_opaque(io->ac->module->ldb, "loadparm"),
+ NULL, "password_hash", "create_aes_key", false);
+
+ if (io->domain->store_cleartext &&
+ (io->u.user_account_control & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED)) {
+ do_cleartext = true;
+ }
+
+ /*
+ * The ordering is this
+ *
+ * Primary:Kerberos-Newer-Keys (optional)
+ * Primary:Kerberos
+ * Primary:WDigest
+ * Primary:CLEARTEXT (optional)
+ *
+ * And the 'Packages' package is insert before the last
+ * other package.
+ */
+ if (do_newer_keys) {
+ /* Primary:Kerberos-Newer-Keys */
+ nkn = &names[num_names++];
+ pkn = &packages[num_packages++];
+ }
+
+ /* Primary:Kerberos */
+ nk = &names[num_names++];
+ pk = &packages[num_packages++];
+
+ if (!do_cleartext) {
+ /* Packages */
+ pp = &packages[num_packages++];
+ }
+
+ /* Primary:WDigest */
+ nd = &names[num_names++];
+ pd = &packages[num_packages++];
+
+ if (do_cleartext) {
+ /* Packages */
+ pp = &packages[num_packages++];
+
+ /* Primary:CLEARTEXT */
+ nc = &names[num_names++];
+ pc = &packages[num_packages++];
+ }
+
+ if (pkn) {
+ /*
+ * setup 'Primary:Kerberos-Newer-Keys' element
+ */
+ *nkn = "Kerberos-Newer-Keys";
+
+ ret = setup_primary_kerberos_newer(io, old_scb, &pknb);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ndr_err = ndr_push_struct_blob(&pknb_blob, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &pknb,
+ (ndr_push_flags_fn_t)ndr_push_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PrimaryKerberosNeverBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pknb_hexstr = data_blob_hex_string(io->ac, &pknb_blob);
+ if (!pknb_hexstr) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pkn->name = "Primary:Kerberos-Newer-Keys";
+ pkn->reserved = 1;
+ pkn->data = pknb_hexstr;
+ }
+
+ /*
+ * setup 'Primary:Kerberos' element
+ */
+ *nk = "Kerberos";
+
+ ret = setup_primary_kerberos(io, old_scb, &pkb);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ndr_err = ndr_push_struct_blob(&pkb_blob, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &pkb,
+ (ndr_push_flags_fn_t)ndr_push_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PrimaryKerberosBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pkb_hexstr = data_blob_hex_string(io->ac, &pkb_blob);
+ if (!pkb_hexstr) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pk->name = "Primary:Kerberos";
+ pk->reserved = 1;
+ pk->data = pkb_hexstr;
+
+ /*
+ * setup 'Primary:WDigest' element
+ */
+ *nd = "WDigest";
+
+ ret = setup_primary_wdigest(io, old_scb, &pdb);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ndr_err = ndr_push_struct_blob(&pdb_blob, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &pdb,
+ (ndr_push_flags_fn_t)ndr_push_package_PrimaryWDigestBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PrimaryWDigestBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pdb_hexstr = data_blob_hex_string(io->ac, &pdb_blob);
+ if (!pdb_hexstr) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pd->name = "Primary:WDigest";
+ pd->reserved = 1;
+ pd->data = pdb_hexstr;
+
+ /*
+ * setup 'Primary:CLEARTEXT' element
+ */
+ if (pc) {
+ *nc = "CLEARTEXT";
+
+ pcb.cleartext = io->n.cleartext;
+
+ ndr_err = ndr_push_struct_blob(&pcb_blob, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &pcb,
+ (ndr_push_flags_fn_t)ndr_push_package_PrimaryCLEARTEXTBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PrimaryCLEARTEXTBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pcb_hexstr = data_blob_hex_string(io->ac, &pcb_blob);
+ if (!pcb_hexstr) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pc->name = "Primary:CLEARTEXT";
+ pc->reserved = 1;
+ pc->data = pcb_hexstr;
+ }
+
+ /*
+ * setup 'Packages' element
+ */
+ pb.names = names;
+ ndr_err = ndr_push_struct_blob(&pb_blob, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &pb,
+ (ndr_push_flags_fn_t)ndr_push_package_PackagesBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PackagesBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pb_hexstr = data_blob_hex_string(io->ac, &pb_blob);
+ if (!pb_hexstr) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pp->name = "Packages";
+ pp->reserved = 2;
+ pp->data = pb_hexstr;
+
+ /*
+ * setup 'supplementalCredentials' value
+ */
+ ZERO_STRUCT(scb);
+ scb.sub.num_packages = num_packages;
+ scb.sub.packages = packages;
+
+ ndr_err = ndr_push_struct_blob(&io->g.supplemental, io->ac,
+ lp_iconv_convenience(ldb_get_opaque(io->ac->module->ldb, "loadparm")),
+ &scb,
+ (ndr_push_flags_fn_t)ndr_push_supplementalCredentialsBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_supplemental_field: "
+ "failed to push supplementalCredentialsBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_last_set_field(struct setup_password_fields_io *io)
+{
+ /* set it as now */
+ unix_to_nt_time(&io->g.last_set, time(NULL));
+
+ return LDB_SUCCESS;
+}
+
+static int setup_kvno_field(struct setup_password_fields_io *io)
+{
+ /* increment by one */
+ io->g.kvno = io->o.kvno + 1;
+
+ return LDB_SUCCESS;
+}
+
+static int setup_password_fields(struct setup_password_fields_io *io)
+{
+ bool ok;
+ int ret;
+
+ /*
+ * refuse the change if someone want to change the cleartext
+ * and supply his own hashes at the same time...
+ */
+ if (io->n.cleartext && (io->n.nt_hash || io->n.lm_hash)) {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_password_fields: "
+ "it's only allowed to set the cleartext password or the password hashes");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ if (io->n.cleartext) {
+ struct samr_Password *hash;
+
+ hash = talloc(io->ac, struct samr_Password);
+ if (!hash) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* compute the new nt hash */
+ ok = E_md4hash(io->n.cleartext, hash->hash);
+ if (ok) {
+ io->n.nt_hash = hash;
+ } else {
+ ldb_asprintf_errstring(io->ac->module->ldb,
+ "setup_password_fields: "
+ "failed to generate nthash from cleartext password");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ if (io->n.cleartext) {
+ struct samr_Password *hash;
+
+ hash = talloc(io->ac, struct samr_Password);
+ if (!hash) {
+ ldb_oom(io->ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* compute the new lm hash */
+ ok = E_deshash(io->n.cleartext, hash->hash);
+ if (ok) {
+ io->n.lm_hash = hash;
+ } else {
+ talloc_free(hash->hash);
+ }
+ }
+
+ if (io->n.cleartext) {
+ ret = setup_kerberos_keys(io);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ ret = setup_nt_fields(io);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = setup_lm_fields(io);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = setup_supplemental_field(io);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = setup_last_set_field(io);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = setup_kvno_field(io);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static struct ldb_handle *ph_init_handle(struct ldb_request *req, struct ldb_module *module, enum ph_type type)
+{
+ struct ph_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct ph_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = (void *)ac;
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->type = type;
+ ac->module = module;
+ ac->orig_req = req;
+
+ return h;
+}
+
+static int get_domain_data_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct ph_context *ac;
+
+ ac = talloc_get_type(context, struct ph_context);
+
+ /* we are interested only in the single reply (base search) we receive here */
+ if (ares->type == LDB_REPLY_ENTRY) {
+ if (ac->dom_res != NULL) {
+ ldb_set_errstring(ldb, "Too many results");
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->dom_res = talloc_steal(ac, ares);
+ } else {
+ talloc_free(ares);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int build_domain_data_request(struct ph_context *ac)
+{
+ /* attrs[] is returned from this function in
+ ac->dom_req->op.search.attrs, so it must be static, as
+ otherwise the compiler can put it on the stack */
+ static const char * const attrs[] = { "pwdProperties", "pwdHistoryLength", NULL };
+ char *filter;
+
+ ac->dom_req = talloc_zero(ac, struct ldb_request);
+ if (ac->dom_req == NULL) {
+ ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->dom_req->operation = LDB_SEARCH;
+ ac->dom_req->op.search.base = ldb_get_default_basedn(ac->module->ldb);
+ ac->dom_req->op.search.scope = LDB_SCOPE_SUBTREE;
+
+ filter = talloc_asprintf(ac->dom_req,
+ "(&(objectSid=%s)(|(|(objectClass=domain)(objectClass=builtinDomain))(objectClass=samba4LocalDomain)))",
+ ldap_encode_ndr_dom_sid(ac->dom_req, ac->domain_sid));
+ if (filter == NULL) {
+ ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
+ talloc_free(ac->dom_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->dom_req->op.search.tree = ldb_parse_tree(ac->dom_req, filter);
+ if (ac->dom_req->op.search.tree == NULL) {
+ ldb_set_errstring(ac->module->ldb, "Invalid search filter");
+ talloc_free(ac->dom_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->dom_req->op.search.attrs = attrs;
+ ac->dom_req->controls = NULL;
+ ac->dom_req->context = ac;
+ ac->dom_req->callback = get_domain_data_callback;
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->dom_req);
+
+ return LDB_SUCCESS;
+}
+
+static struct domain_data *get_domain_data(struct ldb_module *module, void *ctx, struct ldb_reply *res)
+{
+ struct domain_data *data;
+ const char *tmp;
+ struct ph_context *ac;
+ char *p;
+
+ ac = talloc_get_type(ctx, struct ph_context);
+
+ data = talloc_zero(ac, struct domain_data);
+ if (data == NULL) {
+ return NULL;
+ }
+
+ if (res == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Could not find this user's domain: %s!\n", dom_sid_string(data, ac->domain_sid));
+ talloc_free(data);
+ return NULL;
+ }
+
+ data->pwdProperties= samdb_result_uint(res->message, "pwdProperties", 0);
+ data->store_cleartext = data->pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT;
+ data->pwdHistoryLength = samdb_result_uint(res->message, "pwdHistoryLength", 0);
+
+ /* For a domain DN, this puts things in dotted notation */
+ /* For builtin domains, this will give details for the host,
+ * but that doesn't really matter, as it's just used for salt
+ * and kerberos principals, which don't exist here */
+
+ tmp = ldb_dn_canonical_string(ctx, res->message->dn);
+ if (!tmp) {
+ return NULL;
+ }
+
+ /* But it puts a trailing (or just before 'builtin') / on things, so kill that */
+ p = strchr(tmp, '/');
+ if (p) {
+ p[0] = '\0';
+ }
+
+ if (tmp != NULL) {
+ data->dns_domain = strlower_talloc(data, tmp);
+ if (data->dns_domain == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Out of memory!\n");
+ return NULL;
+ }
+ data->realm = strupper_talloc(data, tmp);
+ if (data->realm == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Out of memory!\n");
+ return NULL;
+ }
+ p = strchr(tmp, '.');
+ if (p) {
+ p[0] = '\0';
+ }
+ data->netbios_domain = strupper_talloc(data, tmp);
+ if (data->netbios_domain == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Out of memory!\n");
+ return NULL;
+ }
+ }
+
+ return data;
+}
+
+static int password_hash_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_handle *h;
+ struct ph_context *ac;
+ struct ldb_message_element *sambaAttr;
+ struct ldb_message_element *ntAttr;
+ struct ldb_message_element *lmAttr;
+ int ret;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_add\n");
+
+ if (ldb_dn_is_special(req->op.add.message->dn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* If the caller is manipulating the local passwords directly, let them pass */
+ if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE),
+ req->op.add.message->dn) == 0) {
+ return ldb_next_request(module, req);
+ }
+
+ /* nobody must touch this fields */
+ if (ldb_msg_find_element(req->op.add.message, "ntPwdHistory")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (ldb_msg_find_element(req->op.add.message, "lmPwdHistory")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (ldb_msg_find_element(req->op.add.message, "supplementalCredentials")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* If no part of this ADD touches the userPassword, or the NT
+ * or LM hashes, then we don't need to make any changes. */
+
+ sambaAttr = ldb_msg_find_element(req->op.mod.message, "userPassword");
+ ntAttr = ldb_msg_find_element(req->op.mod.message, "unicodePwd");
+ lmAttr = ldb_msg_find_element(req->op.mod.message, "dBCSPwd");
+
+ if ((!sambaAttr) && (!ntAttr) && (!lmAttr)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* if it is not an entry of type person its an error */
+ /* TODO: remove this when userPassword will be in schema */
+ if (!ldb_msg_check_string_attribute(req->op.add.message, "objectClass", "person")) {
+ ldb_set_errstring(module->ldb, "Cannot set a password on entry that does not have objectClass 'person'");
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* check userPassword is single valued here */
+ /* TODO: remove this when userPassword will be single valued in schema */
+ if (sambaAttr && sambaAttr->num_values > 1) {
+ ldb_set_errstring(module->ldb, "mupltiple values for userPassword not allowed!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ if (ntAttr && (ntAttr->num_values > 1)) {
+ ldb_set_errstring(module->ldb, "mupltiple values for unicodePwd not allowed!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if (lmAttr && (lmAttr->num_values > 1)) {
+ ldb_set_errstring(module->ldb, "mupltiple values for dBCSPwd not allowed!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ if (sambaAttr && sambaAttr->num_values == 0) {
+ ldb_set_errstring(module->ldb, "userPassword must have a value!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ if (ntAttr && (ntAttr->num_values == 0)) {
+ ldb_set_errstring(module->ldb, "unicodePwd must have a value!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if (lmAttr && (lmAttr->num_values == 0)) {
+ ldb_set_errstring(module->ldb, "dBCSPwd must have a value!\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ h = ph_init_handle(req, module, PH_ADD);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct ph_context);
+
+ /* get user domain data */
+ ac->domain_sid = samdb_result_sid_prefix(ac, req->op.add.message, "objectSid");
+ if (ac->domain_sid == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "can't handle entry with missing objectSid!\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = build_domain_data_request(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->step = PH_ADD_SEARCH_DOM;
+
+ req->handle = h;
+
+ return ldb_next_request(module, ac->dom_req);
+}
+
+static int password_hash_add_do_add(struct ldb_handle *h) {
+
+ struct ph_context *ac;
+ struct domain_data *domain;
+ struct smb_krb5_context *smb_krb5_context;
+ struct ldb_message *msg;
+ struct setup_password_fields_io io;
+ int ret;
+
+ ac = talloc_get_type(h->private_data, struct ph_context);
+
+ domain = get_domain_data(ac->module, ac, ac->dom_res);
+ if (domain == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->down_req = talloc(ac, struct ldb_request);
+ if (ac->down_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->down_req) = *(ac->orig_req);
+ ac->down_req->op.add.message = msg = ldb_msg_copy_shallow(ac->down_req, ac->orig_req->op.add.message);
+ if (ac->down_req->op.add.message == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Some operations below require kerberos contexts */
+ if (smb_krb5_init_context(ac->down_req,
+ ldb_get_opaque(h->module->ldb, "EventContext"),
+ (struct loadparm_context *)ldb_get_opaque(h->module->ldb, "loadparm"),
+ &smb_krb5_context) != 0) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ZERO_STRUCT(io);
+ io.ac = ac;
+ io.domain = domain;
+ io.smb_krb5_context = smb_krb5_context;
+
+ io.u.user_account_control = samdb_result_uint(msg, "userAccountControl", 0);
+ io.u.sAMAccountName = samdb_result_string(msg, "samAccountName", NULL);
+ io.u.user_principal_name = samdb_result_string(msg, "userPrincipalName", NULL);
+ io.u.is_computer = ldb_msg_check_string_attribute(msg, "objectClass", "computer");
+
+ io.n.cleartext = samdb_result_string(msg, "userPassword", NULL);
+ io.n.nt_hash = samdb_result_hash(io.ac, msg, "unicodePwd");
+ io.n.lm_hash = samdb_result_hash(io.ac, msg, "dBCSPwd");
+
+ /* remove attributes */
+ if (io.n.cleartext) ldb_msg_remove_attr(msg, "userPassword");
+ if (io.n.nt_hash) ldb_msg_remove_attr(msg, "unicodePwd");
+ if (io.n.lm_hash) ldb_msg_remove_attr(msg, "dBCSPwd");
+ ldb_msg_remove_attr(msg, "pwdLastSet");
+ io.o.kvno = samdb_result_uint(msg, "msDs-KeyVersionNumber", 1) - 1;
+ ldb_msg_remove_attr(msg, "msDs-KeyVersionNumber");
+
+ ret = setup_password_fields(&io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (io.g.nt_hash) {
+ ret = samdb_msg_add_hash(ac->module->ldb, ac, msg,
+ "unicodePwd", io.g.nt_hash);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.lm_hash) {
+ ret = samdb_msg_add_hash(ac->module->ldb, ac, msg,
+ "dBCSPwd", io.g.lm_hash);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.nt_history_len > 0) {
+ ret = samdb_msg_add_hashes(ac, msg,
+ "ntPwdHistory",
+ io.g.nt_history,
+ io.g.nt_history_len);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.lm_history_len > 0) {
+ ret = samdb_msg_add_hashes(ac, msg,
+ "lmPwdHistory",
+ io.g.lm_history,
+ io.g.lm_history_len);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.supplemental.length > 0) {
+ ret = ldb_msg_add_value(msg, "supplementalCredentials",
+ &io.g.supplemental, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ ret = samdb_msg_add_uint64(ac->module->ldb, ac, msg,
+ "pwdLastSet",
+ io.g.last_set);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = samdb_msg_add_uint(ac->module->ldb, ac, msg,
+ "msDs-KeyVersionNumber",
+ io.g.kvno);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = PH_ADD_DO_ADD;
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->down_req);
+
+ /* perform the operation */
+ return ldb_next_request(ac->module, ac->down_req);
+}
+
+static int password_hash_mod_search_self(struct ldb_handle *h);
+
+static int password_hash_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_handle *h;
+ struct ph_context *ac;
+ struct ldb_message_element *sambaAttr;
+ struct ldb_message_element *ntAttr;
+ struct ldb_message_element *lmAttr;
+ struct ldb_message *msg;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_modify\n");
+
+ if (ldb_dn_is_special(req->op.mod.message->dn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* If the caller is manipulating the local passwords directly, let them pass */
+ if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE),
+ req->op.mod.message->dn) == 0) {
+ return ldb_next_request(module, req);
+ }
+
+ /* nobody must touch password Histories */
+ if (ldb_msg_find_element(req->op.add.message, "ntPwdHistory")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (ldb_msg_find_element(req->op.add.message, "lmPwdHistory")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (ldb_msg_find_element(req->op.add.message, "supplementalCredentials")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ sambaAttr = ldb_msg_find_element(req->op.mod.message, "userPassword");
+ ntAttr = ldb_msg_find_element(req->op.mod.message, "unicodePwd");
+ lmAttr = ldb_msg_find_element(req->op.mod.message, "dBCSPwd");
+
+ /* If no part of this touches the userPassword OR unicodePwd and/or dBCSPwd, then we don't
+ * need to make any changes. For password changes/set there should
+ * be a 'delete' or a 'modify' on this attribute. */
+ if ((!sambaAttr) && (!ntAttr) && (!lmAttr)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* check passwords are single valued here */
+ /* TODO: remove this when passwords will be single valued in schema */
+ if (sambaAttr && (sambaAttr->num_values > 1)) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if (ntAttr && (ntAttr->num_values > 1)) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if (lmAttr && (lmAttr->num_values > 1)) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ h = ph_init_handle(req, module, PH_MOD);
+ if (!h) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac = talloc_get_type(h->private_data, struct ph_context);
+
+ /* return or own handle to deal with this call */
+ req->handle = h;
+
+ /* prepare the first operation */
+ ac->down_req = talloc_zero(ac, struct ldb_request);
+ if (ac->down_req == NULL) {
+ ldb_set_errstring(module->ldb, "Out of memory!");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->down_req) = *req; /* copy the request */
+
+ /* use a new message structure so that we can modify it */
+ ac->down_req->op.mod.message = msg = ldb_msg_copy_shallow(ac->down_req, req->op.mod.message);
+
+ /* - remove any imodification to the password from the first commit
+ * we will make the real modification later */
+ if (sambaAttr) ldb_msg_remove_attr(msg, "userPassword");
+ if (ntAttr) ldb_msg_remove_attr(msg, "unicodePwd");
+ if (lmAttr) ldb_msg_remove_attr(msg, "dBCSPwd");
+
+ /* if there was nothing else to be modify skip to next step */
+ if (msg->num_elements == 0) {
+ talloc_free(ac->down_req);
+ ac->down_req = NULL;
+ return password_hash_mod_search_self(h);
+ }
+
+ ac->down_req->context = NULL;
+ ac->down_req->callback = NULL;
+
+ ac->step = PH_MOD_DO_REQ;
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, ac->down_req);
+
+ return ldb_next_request(module, ac->down_req);
+}
+
+static int get_self_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct ph_context *ac;
+
+ ac = talloc_get_type(context, struct ph_context);
+
+ /* we are interested only in the single reply (base search) we receive here */
+ if (ares->type == LDB_REPLY_ENTRY) {
+ if (ac->search_res != NULL) {
+ ldb_set_errstring(ldb, "Too many results");
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* if it is not an entry of type person this is an error */
+ /* TODO: remove this when userPassword will be in schema */
+ if (!ldb_msg_check_string_attribute(ares->message, "objectClass", "person")) {
+ ldb_set_errstring(ldb, "Object class violation");
+ talloc_free(ares);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ ac->search_res = talloc_steal(ac, ares);
+ } else {
+ talloc_free(ares);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int password_hash_mod_search_self(struct ldb_handle *h) {
+
+ struct ph_context *ac;
+ static const char * const attrs[] = { "userAccountControl", "lmPwdHistory",
+ "ntPwdHistory",
+ "objectSid", "msDS-KeyVersionNumber",
+ "objectClass", "userPrincipalName",
+ "sAMAccountName",
+ "dBCSPwd", "unicodePwd",
+ "supplementalCredentials",
+ NULL };
+
+ ac = talloc_get_type(h->private_data, struct ph_context);
+
+ /* prepare the search operation */
+ ac->search_req = talloc_zero(ac, struct ldb_request);
+ if (ac->search_req == NULL) {
+ ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->search_req->operation = LDB_SEARCH;
+ ac->search_req->op.search.base = ac->orig_req->op.mod.message->dn;
+ ac->search_req->op.search.scope = LDB_SCOPE_BASE;
+ ac->search_req->op.search.tree = ldb_parse_tree(ac->search_req, NULL);
+ if (ac->search_req->op.search.tree == NULL) {
+ ldb_set_errstring(ac->module->ldb, "Invalid search filter");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->search_req->op.search.attrs = attrs;
+ ac->search_req->controls = NULL;
+ ac->search_req->context = ac;
+ ac->search_req->callback = get_self_callback;
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
+
+ ac->step = PH_MOD_SEARCH_SELF;
+
+ return ldb_next_request(ac->module, ac->search_req);
+}
+
+static int password_hash_mod_search_dom(struct ldb_handle *h) {
+
+ struct ph_context *ac;
+ int ret;
+
+ ac = talloc_get_type(h->private_data, struct ph_context);
+
+ /* get object domain sid */
+ ac->domain_sid = samdb_result_sid_prefix(ac, ac->search_res->message, "objectSid");
+ if (ac->domain_sid == NULL) {
+ ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "can't handle entry with missing objectSid!\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* get user domain data */
+ ret = build_domain_data_request(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->step = PH_MOD_SEARCH_DOM;
+
+ return ldb_next_request(ac->module, ac->dom_req);
+}
+
+static int password_hash_mod_do_mod(struct ldb_handle *h) {
+
+ struct ph_context *ac;
+ struct domain_data *domain;
+ struct smb_krb5_context *smb_krb5_context;
+ struct ldb_message *msg;
+ struct ldb_message *orig_msg;
+ struct ldb_message *searched_msg;
+ struct setup_password_fields_io io;
+ int ret;
+
+ ac = talloc_get_type(h->private_data, struct ph_context);
+
+ domain = get_domain_data(ac->module, ac, ac->dom_res);
+ if (domain == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->mod_req = talloc(ac, struct ldb_request);
+ if (ac->mod_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *(ac->mod_req) = *(ac->orig_req);
+
+ /* use a new message structure so that we can modify it */
+ ac->mod_req->op.mod.message = msg = ldb_msg_new(ac->mod_req);
+ if (msg == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* modify dn */
+ msg->dn = ac->orig_req->op.mod.message->dn;
+
+ /* Some operations below require kerberos contexts */
+ if (smb_krb5_init_context(ac->mod_req,
+ ldb_get_opaque(h->module->ldb, "EventContext"),
+ (struct loadparm_context *)ldb_get_opaque(h->module->ldb, "loadparm"),
+ &smb_krb5_context) != 0) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ orig_msg = discard_const(ac->orig_req->op.mod.message);
+ searched_msg = ac->search_res->message;
+
+ ZERO_STRUCT(io);
+ io.ac = ac;
+ io.domain = domain;
+ io.smb_krb5_context = smb_krb5_context;
+
+ io.u.user_account_control = samdb_result_uint(searched_msg, "userAccountControl", 0);
+ io.u.sAMAccountName = samdb_result_string(searched_msg, "samAccountName", NULL);
+ io.u.user_principal_name = samdb_result_string(searched_msg, "userPrincipalName", NULL);
+ io.u.is_computer = ldb_msg_check_string_attribute(searched_msg, "objectClass", "computer");
+
+ io.n.cleartext = samdb_result_string(orig_msg, "userPassword", NULL);
+ io.n.nt_hash = samdb_result_hash(io.ac, orig_msg, "unicodePwd");
+ io.n.lm_hash = samdb_result_hash(io.ac, orig_msg, "dBCSPwd");
+
+ io.o.kvno = samdb_result_uint(searched_msg, "msDs-KeyVersionNumber", 0);
+ io.o.nt_history_len = samdb_result_hashes(io.ac, searched_msg, "ntPwdHistory", &io.o.nt_history);
+ io.o.lm_history_len = samdb_result_hashes(io.ac, searched_msg, "lmPwdHistory", &io.o.lm_history);
+ io.o.supplemental = ldb_msg_find_ldb_val(searched_msg, "supplementalCredentials");
+
+ ret = setup_password_fields(&io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* make sure we replace all the old attributes */
+ ret = ldb_msg_add_empty(msg, "unicodePwd", LDB_FLAG_MOD_REPLACE, NULL);
+ ret = ldb_msg_add_empty(msg, "dBCSPwd", LDB_FLAG_MOD_REPLACE, NULL);
+ ret = ldb_msg_add_empty(msg, "ntPwdHistory", LDB_FLAG_MOD_REPLACE, NULL);
+ ret = ldb_msg_add_empty(msg, "lmPwdHistory", LDB_FLAG_MOD_REPLACE, NULL);
+ ret = ldb_msg_add_empty(msg, "supplementalCredentials", LDB_FLAG_MOD_REPLACE, NULL);
+ ret = ldb_msg_add_empty(msg, "pwdLastSet", LDB_FLAG_MOD_REPLACE, NULL);
+ ret = ldb_msg_add_empty(msg, "msDs-KeyVersionNumber", LDB_FLAG_MOD_REPLACE, NULL);
+
+ if (io.g.nt_hash) {
+ ret = samdb_msg_add_hash(ac->module->ldb, ac, msg,
+ "unicodePwd", io.g.nt_hash);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.lm_hash) {
+ ret = samdb_msg_add_hash(ac->module->ldb, ac, msg,
+ "dBCSPwd", io.g.lm_hash);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.nt_history_len > 0) {
+ ret = samdb_msg_add_hashes(ac, msg,
+ "ntPwdHistory",
+ io.g.nt_history,
+ io.g.nt_history_len);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.lm_history_len > 0) {
+ ret = samdb_msg_add_hashes(ac, msg,
+ "lmPwdHistory",
+ io.g.lm_history,
+ io.g.lm_history_len);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io.g.supplemental.length > 0) {
+ ret = ldb_msg_add_value(msg, "supplementalCredentials",
+ &io.g.supplemental, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ ret = samdb_msg_add_uint64(ac->module->ldb, ac, msg,
+ "pwdLastSet",
+ io.g.last_set);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = samdb_msg_add_uint(ac->module->ldb, ac, msg,
+ "msDs-KeyVersionNumber",
+ io.g.kvno);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ h->state = LDB_ASYNC_INIT;
+ h->status = LDB_SUCCESS;
+
+ ac->step = PH_MOD_DO_MOD;
+
+ ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->mod_req);
+
+ /* perform the search */
+ return ldb_next_request(ac->module, ac->mod_req);
+}
+
+static int ph_wait(struct ldb_handle *handle) {
+ struct ph_context *ac;
+ int ret;
+
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct ph_context);
+
+ switch (ac->step) {
+ case PH_ADD_SEARCH_DOM:
+ ret = ldb_wait(ac->dom_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->dom_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->dom_req->handle->status;
+ goto done;
+ }
+
+ if (ac->dom_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* domain search done, go on */
+ return password_hash_add_do_add(handle);
+
+ case PH_ADD_DO_ADD:
+ ret = ldb_wait(ac->down_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ case PH_MOD_DO_REQ:
+ ret = ldb_wait(ac->down_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* non-password mods done, go on */
+ return password_hash_mod_search_self(handle);
+
+ case PH_MOD_SEARCH_SELF:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ if (ac->search_res == NULL) {
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+
+ /* self search done, go on */
+ return password_hash_mod_search_dom(handle);
+
+ case PH_MOD_SEARCH_DOM:
+ ret = ldb_wait(ac->dom_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->dom_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->dom_req->handle->status;
+ goto done;
+ }
+
+ if (ac->dom_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ /* domain search done, go on */
+ return password_hash_mod_do_mod(handle);
+
+ case PH_MOD_DO_MOD:
+ ret = ldb_wait(ac->mod_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->mod_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->mod_req->handle->status;
+ goto done;
+ }
+
+ if (ac->mod_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+
+ default:
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ ret = LDB_SUCCESS;
+
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+}
+
+static int ph_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = ph_wait(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int password_hash_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return ph_wait_all(handle);
+ } else {
+ return ph_wait(handle);
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_password_hash_module_ops = {
+ .name = "password_hash",
+ .add = password_hash_add,
+ .modify = password_hash_modify,
+ .wait = password_hash_wait
+};
diff --git a/source4/dsdb/samdb/ldb_modules/password_modules.h b/source4/dsdb/samdb/ldb_modules/password_modules.h
new file mode 100644
index 0000000000..40d0144416
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/password_modules.h
@@ -0,0 +1,3 @@
+/* We store these passwords under this base DN: */
+
+#define LOCAL_BASE "cn=Passwords"
diff --git a/source4/dsdb/samdb/ldb_modules/pdc_fsmo.c b/source4/dsdb/samdb/ldb_modules/pdc_fsmo.c
new file mode 100644
index 0000000000..09d56d77c9
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/pdc_fsmo.c
@@ -0,0 +1,121 @@
+/*
+ Unix SMB/CIFS mplementation.
+
+ The module that handles the PDC FSMO Role Owner checkings
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ 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 "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb_private.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 "lib/util/dlinklist.h"
+
+static int pdc_fsmo_init(struct ldb_module *module)
+{
+ TALLOC_CTX *mem_ctx;
+ struct ldb_dn *pdc_dn;
+ struct dsdb_pdc_fsmo *pdc_fsmo;
+ struct ldb_result *pdc_res;
+ int ret;
+ static const char *pdc_attrs[] = {
+ "fSMORoleOwner",
+ NULL
+ };
+
+ mem_ctx = talloc_new(module);
+ if (!mem_ctx) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ pdc_dn = samdb_base_dn(module->ldb);
+ if (!pdc_dn) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "pdc_fsmo_init: no domain dn present: (skip loading of domain details)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ }
+
+ pdc_fsmo = talloc_zero(mem_ctx, struct dsdb_pdc_fsmo);
+ if (!pdc_fsmo) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ module->private_data = pdc_fsmo;
+
+ ret = ldb_search(module->ldb, pdc_dn,
+ LDB_SCOPE_BASE,
+ NULL, pdc_attrs,
+ &pdc_res);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "pdc_fsmo_init: no domain object present: (skip loading of domain details)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ } else if (ret != LDB_SUCCESS) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "pdc_fsmo_init: failed to search the domain object: %d:%s",
+ ret, ldb_strerror(ret));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ talloc_steal(mem_ctx, pdc_res);
+ if (pdc_res->count == 0) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "pdc_fsmo_init: no domain object present: (skip loading of domain details)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ } else if (pdc_res->count > 1) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "pdc_fsmo_init: [%u] domain objects found on a base search",
+ pdc_res->count);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ pdc_fsmo->master_dn = ldb_msg_find_attr_as_dn(module->ldb, mem_ctx, pdc_res->msgs[0], "fSMORoleOwner");
+ if (ldb_dn_compare(samdb_ntds_settings_dn(module->ldb), pdc_fsmo->master_dn) == 0) {
+ pdc_fsmo->we_are_master = true;
+ } else {
+ pdc_fsmo->we_are_master = false;
+ }
+
+ if (ldb_set_opaque(module->ldb, "dsdb_pdc_fsmo", pdc_fsmo) != LDB_SUCCESS) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_steal(module, pdc_fsmo);
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE,
+ "pdc_fsmo_init: we are master: %s\n",
+ (pdc_fsmo->we_are_master?"yes":"no"));
+
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_pdc_fsmo_module_ops = {
+ .name = "pdc_fsmo",
+ .init_context = pdc_fsmo_init
+};
diff --git a/source4/dsdb/samdb/ldb_modules/proxy.c b/source4/dsdb/samdb/ldb_modules/proxy.c
new file mode 100644
index 0000000000..0d065425ca
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/proxy.c
@@ -0,0 +1,337 @@
+/*
+ samdb proxy module
+
+ Copyright (C) Andrew Tridgell 2005
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ ldb proxy module. At startup this looks for a record like this:
+
+ dn=@PROXYINFO
+ url=destination url
+ olddn = basedn to proxy in upstream server
+ newdn = basedn in local server
+ username = username to connect to upstream
+ password = password for upstream
+
+ NOTE: this module is a complete hack at this stage. I am committing it just
+ so others can know how I am investigating mmc support
+
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "auth/credentials/credentials.h"
+
+struct proxy_data {
+ struct ldb_context *upstream;
+ struct ldb_dn *olddn;
+ struct ldb_dn *newdn;
+ const char **oldstr;
+ const char **newstr;
+};
+
+
+/*
+ load the @PROXYINFO record
+*/
+static int load_proxy_info(struct ldb_module *module)
+{
+ struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
+ struct ldb_dn *dn;
+ struct ldb_result *res = NULL;
+ int ret;
+ const char *olddn, *newdn, *url, *username, *password, *oldstr, *newstr;
+ struct cli_credentials *creds;
+
+
+ /* see if we have already loaded it */
+ if (proxy->upstream != NULL) {
+ return 0;
+ }
+
+ dn = ldb_dn_new(proxy, module->ldb, "@PROXYINFO");
+ if (dn == NULL) {
+ goto failed;
+ }
+ ret = ldb_search(module->ldb, dn, LDB_SCOPE_BASE, NULL, NULL, &res);
+ talloc_free(dn);
+ if (ret != LDB_SUCCESS || res->count != 1) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Can't find @PROXYINFO\n");
+ goto failed;
+ }
+
+ url = ldb_msg_find_attr_as_string(res->msgs[0], "url", NULL);
+ olddn = ldb_msg_find_attr_as_string(res->msgs[0], "olddn", NULL);
+ newdn = ldb_msg_find_attr_as_string(res->msgs[0], "newdn", NULL);
+ username = ldb_msg_find_attr_as_string(res->msgs[0], "username", NULL);
+ password = ldb_msg_find_attr_as_string(res->msgs[0], "password", NULL);
+ oldstr = ldb_msg_find_attr_as_string(res->msgs[0], "oldstr", NULL);
+ newstr = ldb_msg_find_attr_as_string(res->msgs[0], "newstr", NULL);
+
+ if (url == NULL || olddn == NULL || newdn == NULL || username == NULL || password == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Need url, olddn, newdn, oldstr, newstr, username and password in @PROXYINFO\n");
+ goto failed;
+ }
+
+ proxy->olddn = ldb_dn_new(proxy, module->ldb, olddn);
+ if (proxy->olddn == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Failed to explode olddn '%s'\n", olddn);
+ goto failed;
+ }
+
+ proxy->newdn = ldb_dn_new(proxy, module->ldb, newdn);
+ if (proxy->newdn == NULL) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Failed to explode newdn '%s'\n", newdn);
+ goto failed;
+ }
+
+ proxy->upstream = ldb_init(proxy);
+ if (proxy->upstream == NULL) {
+ ldb_oom(module->ldb);
+ goto failed;
+ }
+
+ proxy->oldstr = str_list_make(proxy, oldstr, ", ");
+ if (proxy->oldstr == NULL) {
+ ldb_oom(module->ldb);
+ goto failed;
+ }
+
+ proxy->newstr = str_list_make(proxy, newstr, ", ");
+ if (proxy->newstr == NULL) {
+ ldb_oom(module->ldb);
+ goto failed;
+ }
+
+ /* setup credentials for connection */
+ creds = cli_credentials_init(proxy->upstream);
+ if (creds == NULL) {
+ ldb_oom(module->ldb);
+ goto failed;
+ }
+ cli_credentials_guess(creds, ldb_get_opaque(module->ldb, "loadparm"));
+ cli_credentials_set_username(creds, username, CRED_SPECIFIED);
+ cli_credentials_set_password(creds, password, CRED_SPECIFIED);
+
+ ldb_set_opaque(proxy->upstream, "credentials", creds);
+
+ ret = ldb_connect(proxy->upstream, url, 0, NULL);
+ if (ret != 0) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "proxy failed to connect to %s\n", url);
+ goto failed;
+ }
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "proxy connected to %s\n", url);
+
+ talloc_free(res);
+
+ return 0;
+
+failed:
+ talloc_free(res);
+ talloc_free(proxy->olddn);
+ talloc_free(proxy->newdn);
+ talloc_free(proxy->upstream);
+ proxy->upstream = NULL;
+ return -1;
+}
+
+
+/*
+ convert a binary blob
+*/
+static void proxy_convert_blob(TALLOC_CTX *mem_ctx, struct ldb_val *v,
+ const char *oldstr, const char *newstr)
+{
+ int len1, len2, len3;
+ uint8_t *olddata = v->data;
+ char *p = strcasestr((char *)v->data, oldstr);
+
+ len1 = (p - (char *)v->data);
+ len2 = strlen(newstr);
+ len3 = v->length - (p+strlen(oldstr) - (char *)v->data);
+ v->length = len1+len2+len3;
+ v->data = talloc_size(mem_ctx, v->length);
+ memcpy(v->data, olddata, len1);
+ memcpy(v->data+len1, newstr, len2);
+ memcpy(v->data+len1+len2, olddata + len1 + strlen(oldstr), len3);
+}
+
+/*
+ convert a returned value
+*/
+static void proxy_convert_value(struct ldb_module *module, struct ldb_message *msg, struct ldb_val *v)
+{
+ struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
+ int i;
+ for (i=0;proxy->oldstr[i];i++) {
+ char *p = strcasestr((char *)v->data, proxy->oldstr[i]);
+ if (p == NULL) continue;
+ proxy_convert_blob(msg, v, proxy->oldstr[i], proxy->newstr[i]);
+ }
+}
+
+
+/*
+ convert a returned value
+*/
+static struct ldb_parse_tree *proxy_convert_tree(struct ldb_module *module,
+ struct ldb_parse_tree *tree)
+{
+ struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
+ int i;
+ char *expression = ldb_filter_from_tree(module, tree);
+ for (i=0;proxy->newstr[i];i++) {
+ struct ldb_val v;
+ char *p = strcasestr(expression, proxy->newstr[i]);
+ if (p == NULL) continue;
+ v.data = (uint8_t *)expression;
+ v.length = strlen(expression)+1;
+ proxy_convert_blob(module, &v, proxy->newstr[i], proxy->oldstr[i]);
+ return ldb_parse_tree(module, (const char *)v.data);
+ }
+ return tree;
+}
+
+
+
+/*
+ convert a returned record
+*/
+static void proxy_convert_record(struct ldb_module *module, struct ldb_message *msg)
+{
+ struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
+ int attr, v;
+
+ /* fix the message DN */
+ if (ldb_dn_compare_base(module->ldb, proxy->olddn, msg->dn) == 0) {
+ ldb_dn_remove_base_components(msg->dn, ldb_dn_get_comp_num(proxy->olddn));
+ ldb_dn_add_base(msg->dn, proxy->newdn);
+ }
+
+ /* fix any attributes */
+ for (attr=0;attr<msg->num_elements;attr++) {
+ for (v=0;v<msg->elements[attr].num_values;v++) {
+ proxy_convert_value(module, msg, &msg->elements[attr].values[v]);
+ }
+ }
+
+ /* fix any DN components */
+ for (attr=0;attr<msg->num_elements;attr++) {
+ for (v=0;v<msg->elements[attr].num_values;v++) {
+ proxy_convert_value(module, msg, &msg->elements[attr].values[v]);
+ }
+ }
+}
+
+/* search */
+static int proxy_search_bytree(struct ldb_module *module, struct ldb_request *req)
+{
+ struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
+ struct ldb_request *newreq;
+ struct ldb_dn *base;
+ int ret, i;
+
+ if (req->op.search.base == NULL ||
+ (req->op.search.base->comp_num == 1 &&
+ req->op.search.base->components[0].name[0] == '@')) {
+ goto passthru;
+ }
+
+ if (load_proxy_info(module) != 0) {
+ return -1;
+ }
+
+ /* see if the dn is within olddn */
+ if (ldb_dn_compare_base(module->ldb, proxy->newdn, req->op.search.base) != 0) {
+ goto passthru;
+ }
+
+ newreq = talloc(module, struct ldb_request);
+ if (newreq == NULL) {
+ return -1;
+ }
+
+ newreq->op.search.tree = proxy_convert_tree(module, req->op.search.tree);
+
+ /* convert the basedn of this search */
+ base = ldb_dn_copy(proxy, req->op.search.base);
+ if (base == NULL) {
+ talloc_free(newreq);
+ goto failed;
+ }
+ ldb_dn_remove_base_components(base, ldb_dn_get_comp_num(proxy->newdn));
+ ldb_dn_add_base(base, proxy->olddn);
+
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "proxying: '%s' with dn '%s' \n",
+ ldb_filter_from_tree(proxy, newreq->op.search.tree), ldb_dn_get_linearized(newreq->op.search.base));
+ for (i = 0; req->op.search.attrs && req->op.search.attrs[i]; i++) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "attr: '%s'\n", req->op.search.attrs[i]);
+ }
+
+ newreq->op.search.base = base;
+ newreq->op.search.scope = req->op.search.scope;
+ newreq->op.search.attrs = req->op.search.attrs;
+ newreq->op.search.res = req->op.search.res;
+ newreq->controls = req->controls;
+ ret = ldb_request(proxy->upstream, newreq);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(module->ldb, ldb_errstring(proxy->upstream));
+ talloc_free(newreq);
+ return -1;
+ }
+
+ for (i = 0; i < newreq->op.search.res->count; i++) {
+ printf("# record %d\n", i+1);
+
+ proxy_convert_record(module, newreq->op.search.res->msgs[i]);
+ }
+
+ talloc_free(newreq);
+ return ret;
+
+failed:
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "proxy failed for %s\n",
+ ldb_dn_get_linearized(req->op.search.base));
+
+passthru:
+ return ldb_next_request(module, req);
+}
+
+static int proxy_request(struct ldb_module *module, struct ldb_request *req)
+{
+ switch (req->operation) {
+
+ case LDB_REQ_SEARCH:
+ return proxy_search_bytree(module, req);
+
+ default:
+ return ldb_next_request(module, req);
+
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_proxy_module_ops = {
+ .name = "proxy",
+ .request = proxy_request
+};
diff --git a/source4/dsdb/samdb/ldb_modules/ranged_results.c b/source4/dsdb/samdb/ldb_modules/ranged_results.c
new file mode 100644
index 0000000000..c6ebea1044
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/ranged_results.c
@@ -0,0 +1,207 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett 2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb ranged results module
+ *
+ * Description: munge AD-style 'ranged results' requests into
+ * requests for all values in an attribute, then return the range to
+ * the client.
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "ldb_includes.h"
+
+struct rr_context {
+ struct ldb_request *orig_req;
+ struct ldb_request *down_req;
+};
+
+static int rr_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct rr_context *rr_context = talloc_get_type(context, struct rr_context);
+ struct ldb_request *orig_req = rr_context->orig_req;
+ int i, j;
+
+ if (ares->type != LDB_REPLY_ENTRY) {
+ return rr_context->orig_req->callback(ldb, rr_context->orig_req->context, ares);
+ }
+
+ /* Find those that are range requests from the attribute list */
+ for (i = 0; orig_req->op.search.attrs[i]; i++) {
+ char *p, *new_attr;
+ const char *end_str;
+ unsigned int start, end, orig_num_values;
+ struct ldb_message_element *el;
+ struct ldb_val *orig_values;
+ p = strchr(orig_req->op.search.attrs[i], ';');
+ if (!p) {
+ continue;
+ }
+ if (strncasecmp(p, ";range=", strlen(";range=")) != 0) {
+ continue;
+ }
+ if (sscanf(p, ";range=%u-%u", &start, &end) == 2) {
+ } else if (sscanf(p, ";range=%u-*", &start) == 1) {
+ end = (unsigned int)-1;
+ } else {
+ continue;
+ }
+ new_attr = talloc_strndup(orig_req,
+ orig_req->op.search.attrs[i],
+ (unsigned int)(p-orig_req->op.search.attrs[i]));
+
+ if (!new_attr) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ el = ldb_msg_find_element(ares->message, new_attr);
+ talloc_free(new_attr);
+ if (!el) {
+ continue;
+ }
+ if (start > end) {
+ ldb_asprintf_errstring(ldb, "range request error: start must not be greater than end");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (end >= (el->num_values - 1)) {
+ /* Need to leave the requested attribute in
+ * there (so add an empty one to match) */
+ end_str = "*";
+ end = el->num_values - 1;
+ } else {
+ end_str = talloc_asprintf(el, "%u", end);
+ if (!end_str) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+ /* If start is greater then where we noe find the end to be */
+ if (start > end) {
+ el->num_values = 0;
+ el->values = NULL;
+ } else {
+ orig_values = el->values;
+ orig_num_values = el->num_values;
+
+ if ((start + end < start) || (start + end < end)) {
+ ldb_asprintf_errstring(ldb, "range request error: start or end would overflow!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ el->num_values = 0;
+
+ el->values = talloc_array(el, struct ldb_val, (end - start) + 1);
+ if (!el->values) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ for (j=start; j <= end; j++) {
+ el->values[el->num_values] = orig_values[j];
+ el->num_values++;
+ }
+ }
+ el->name = talloc_asprintf(el, "%s;range=%u-%s", el->name, start, end_str);
+ if (!el->name) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ return rr_context->orig_req->callback(ldb, rr_context->orig_req->context, ares);
+
+}
+
+/* search */
+static int rr_search(struct ldb_module *module, struct ldb_request *req)
+{
+ int i;
+ unsigned int start, end;
+ const char **new_attrs = NULL;
+ struct rr_context *context;
+ bool found_rr = false;
+
+ /* Strip the range request from the attribute */
+ for (i = 0; req->op.search.attrs && req->op.search.attrs[i]; i++) {
+ char *p;
+ new_attrs = talloc_realloc(req, new_attrs, const char *, i+2);
+ new_attrs[i] = req->op.search.attrs[i];
+ new_attrs[i+1] = NULL;
+ p = strchr(req->op.search.attrs[i], ';');
+ if (!p) {
+ continue;
+ }
+ if (strncasecmp(p, ";range=", strlen(";range=")) != 0) {
+ continue;
+ }
+ if (sscanf(p, ";range=%u-%u", &start, &end) == 2) {
+ } else if (sscanf(p, ";range=%u-*", &start) == 1) {
+ end = (unsigned int)-1;
+ } else {
+ ldb_asprintf_errstring(module->ldb, "range request error: range requst malformed");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (start > end) {
+ ldb_asprintf_errstring(module->ldb, "range request error: start must not be greater than end");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ found_rr = true;
+ new_attrs[i] = talloc_strndup(new_attrs,
+ req->op.search.attrs[i],
+ (unsigned int)(p-req->op.search.attrs[i]));
+
+ if (!new_attrs[i]) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ if (found_rr) {
+ int ret;
+ context = talloc(req, struct rr_context);
+ context->orig_req = req;
+ context->down_req = talloc(context, struct ldb_request);
+ *context->down_req = *req;
+
+ context->down_req->op.search.attrs = new_attrs;
+
+ context->down_req->callback = rr_search_callback;
+ context->down_req->context = context;
+
+ ret = ldb_next_request(module, context->down_req);
+
+ /* We don't need to implement our own 'wait' function, so pass the handle along */
+ if (ret == LDB_SUCCESS) {
+ req->handle = context->down_req->handle;
+ }
+ return ret;
+ }
+
+ /* No change, just run the original request as if we were never here */
+ return ldb_next_request(module, req);
+}
+
+const struct ldb_module_ops ldb_ranged_results_module_ops = {
+ .name = "ranged_results",
+ .search = rr_search,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
new file mode 100644
index 0000000000..dd5faf837a
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
@@ -0,0 +1,1588 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2004-2006
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb repl_meta_data module
+ *
+ * Description: - add a unique objectGUID onto every new record,
+ * - handle whenCreated, whenChanged timestamps
+ * - handle uSNCreated, uSNChanged numbers
+ * - handle replPropertyMetaData attribute
+ *
+ * Author: Simo Sorce
+ * Author: Stefan Metzmacher
+ */
+
+#include "includes.h"
+#include "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/flags.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"
+
+struct replmd_replicated_request {
+ struct ldb_module *module;
+ struct ldb_handle *handle;
+ struct ldb_request *orig_req;
+
+ const struct dsdb_schema *schema;
+
+ struct dsdb_extended_replicated_objects *objs;
+
+ uint32_t index_current;
+
+ struct {
+ TALLOC_CTX *mem_ctx;
+ struct ldb_request *search_req;
+ struct ldb_message *search_msg;
+ int search_ret;
+ struct ldb_request *change_req;
+ int change_ret;
+ } sub;
+};
+
+static struct replmd_replicated_request *replmd_replicated_init_handle(struct ldb_module *module,
+ struct ldb_request *req,
+ struct dsdb_extended_replicated_objects *objs)
+{
+ struct replmd_replicated_request *ar;
+ struct ldb_handle *h;
+ const struct dsdb_schema *schema;
+
+ schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "replmd_replicated_init_handle: no loaded schema found\n");
+ return NULL;
+ }
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+ h->state = LDB_ASYNC_PENDING;
+ h->status = LDB_SUCCESS;
+
+ ar = talloc_zero(h, struct replmd_replicated_request);
+ if (ar == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = ar;
+
+ ar->module = module;
+ ar->handle = h;
+ ar->orig_req = req;
+ ar->schema = schema;
+ ar->objs = objs;
+
+ req->handle = h;
+
+ return ar;
+}
+
+/*
+ add a time element to a record
+*/
+static int add_time_element(struct ldb_message *msg, const char *attr, time_t t)
+{
+ struct ldb_message_element *el;
+ char *s;
+
+ if (ldb_msg_find_element(msg, attr) != NULL) {
+ return 0;
+ }
+
+ s = ldb_timestring(msg, t);
+ if (s == NULL) {
+ return -1;
+ }
+
+ if (ldb_msg_add_string(msg, attr, s) != 0) {
+ return -1;
+ }
+
+ el = ldb_msg_find_element(msg, attr);
+ /* always set as replace. This works because on add ops, the flag
+ is ignored */
+ el->flags = LDB_FLAG_MOD_REPLACE;
+
+ return 0;
+}
+
+/*
+ add a uint64_t element to a record
+*/
+static int add_uint64_element(struct ldb_message *msg, const char *attr, uint64_t v)
+{
+ struct ldb_message_element *el;
+
+ if (ldb_msg_find_element(msg, attr) != NULL) {
+ return 0;
+ }
+
+ if (ldb_msg_add_fmt(msg, attr, "%llu", (unsigned long long)v) != 0) {
+ return -1;
+ }
+
+ el = ldb_msg_find_element(msg, attr);
+ /* always set as replace. This works because on add ops, the flag
+ is ignored */
+ el->flags = LDB_FLAG_MOD_REPLACE;
+
+ return 0;
+}
+
+static int replmd_replPropertyMetaData1_attid_sort(const struct replPropertyMetaData1 *m1,
+ const struct replPropertyMetaData1 *m2,
+ const uint32_t *rdn_attid)
+{
+ if (m1->attid == m2->attid) {
+ return 0;
+ }
+
+ /*
+ * the rdn attribute should be at the end!
+ * so we need to return a value greater than zero
+ * which means m1 is greater than m2
+ */
+ if (m1->attid == *rdn_attid) {
+ return 1;
+ }
+
+ /*
+ * the rdn attribute should be at the end!
+ * so we need to return a value less than zero
+ * which means m2 is greater than m1
+ */
+ if (m2->attid == *rdn_attid) {
+ return -1;
+ }
+
+ return m1->attid - m2->attid;
+}
+
+static void replmd_replPropertyMetaDataCtr1_sort(struct replPropertyMetaDataCtr1 *ctr1,
+ const uint32_t *rdn_attid)
+{
+ ldb_qsort(ctr1->array, ctr1->count, sizeof(struct replPropertyMetaData1),
+ discard_const_p(void, rdn_attid), (ldb_qsort_cmp_fn_t)replmd_replPropertyMetaData1_attid_sort);
+}
+
+static int replmd_ldb_message_element_attid_sort(const struct ldb_message_element *e1,
+ const struct ldb_message_element *e2,
+ const struct dsdb_schema *schema)
+{
+ const struct dsdb_attribute *a1;
+ const struct dsdb_attribute *a2;
+
+ /*
+ * TODO: make this faster by caching the dsdb_attribute pointer
+ * on the ldb_messag_element
+ */
+
+ a1 = dsdb_attribute_by_lDAPDisplayName(schema, e1->name);
+ a2 = dsdb_attribute_by_lDAPDisplayName(schema, e2->name);
+
+ /*
+ * TODO: remove this check, we should rely on e1 and e2 having valid attribute names
+ * in the schema
+ */
+ if (!a1 || !a2) {
+ return strcasecmp(e1->name, e2->name);
+ }
+
+ return a1->attributeID_id - a2->attributeID_id;
+}
+
+static void replmd_ldb_message_sort(struct ldb_message *msg,
+ const struct dsdb_schema *schema)
+{
+ ldb_qsort(msg->elements, msg->num_elements, sizeof(struct ldb_message_element),
+ discard_const_p(void, schema), (ldb_qsort_cmp_fn_t)replmd_ldb_message_element_attid_sort);
+}
+
+static int replmd_prepare_originating(struct ldb_module *module, struct ldb_request *req,
+ struct ldb_dn *dn, const char *fn_name,
+ int (*fn)(struct ldb_module *,
+ struct ldb_request *,
+ const struct dsdb_schema *))
+{
+ const struct dsdb_schema *schema;
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "%s: no dsdb_schema loaded",
+ fn_name);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ return fn(module, req, schema);
+}
+
+static int replmd_add_originating(struct ldb_module *module,
+ struct ldb_request *req,
+ const struct dsdb_schema *schema)
+{
+ enum ndr_err_code ndr_err;
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ const struct dsdb_attribute *rdn_attr = NULL;
+ struct GUID guid;
+ struct ldb_val guid_value;
+ struct replPropertyMetaDataBlob nmd;
+ struct ldb_val nmd_value;
+ uint64_t seq_num;
+ const struct GUID *our_invocation_id;
+ time_t t = time(NULL);
+ NTTIME now;
+ char *time_str;
+ int ret;
+ uint32_t i, ni=0;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "replmd_add_originating\n");
+
+ if (ldb_msg_find_element(req->op.add.message, "objectGUID")) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_ERROR,
+ "replmd_add_originating: it's not allowed to add an object with objectGUID\n");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Get a sequence number from the backend */
+ ret = ldb_sequence_number(module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* a new GUID */
+ guid = GUID_random();
+
+ /* get our invicationId */
+ our_invocation_id = samdb_ntds_invocation_id(module->ldb);
+ if (!our_invocation_id) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_ERROR,
+ "replmd_add_originating: unable to find invocationId\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* create a copy of the request */
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *down_req = *req;
+
+ /* we have to copy the message as the caller might have it as a const */
+ down_req->op.add.message = msg = ldb_msg_copy_shallow(down_req, req->op.add.message);
+ if (msg == NULL) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* generated times */
+ unix_to_nt_time(&now, t);
+ time_str = ldb_timestring(msg, t);
+ if (!time_str) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * remove autogenerated attributes
+ */
+ ldb_msg_remove_attr(msg, "whenCreated");
+ ldb_msg_remove_attr(msg, "whenChanged");
+ ldb_msg_remove_attr(msg, "uSNCreated");
+ ldb_msg_remove_attr(msg, "uSNChanged");
+ ldb_msg_remove_attr(msg, "replPropertyMetaData");
+
+ /*
+ * readd replicated attributes
+ */
+ ret = ldb_msg_add_string(msg, "whenCreated", time_str);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* build the replication meta_data */
+ ZERO_STRUCT(nmd);
+ nmd.version = 1;
+ nmd.ctr.ctr1.count = msg->num_elements;
+ nmd.ctr.ctr1.array = talloc_array(msg,
+ struct replPropertyMetaData1,
+ nmd.ctr.ctr1.count);
+ if (!nmd.ctr.ctr1.array) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; i < msg->num_elements; i++) {
+ struct ldb_message_element *e = &msg->elements[i];
+ struct replPropertyMetaData1 *m = &nmd.ctr.ctr1.array[ni];
+ const struct dsdb_attribute *sa;
+
+ if (e->name[0] == '@') continue;
+
+ sa = dsdb_attribute_by_lDAPDisplayName(schema, e->name);
+ if (!sa) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_ERROR,
+ "replmd_add_originating: attribute '%s' not defined in schema\n",
+ e->name);
+ talloc_free(down_req);
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+
+ if ((sa->systemFlags & 0x00000001) || (sa->systemFlags & 0x00000004)) {
+ /* if the attribute is not replicated (0x00000001)
+ * or constructed (0x00000004) it has no metadata
+ */
+ continue;
+ }
+
+ m->attid = sa->attributeID_id;
+ m->version = 1;
+ m->originating_change_time = now;
+ m->originating_invocation_id = *our_invocation_id;
+ m->originating_usn = seq_num;
+ m->local_usn = seq_num;
+ ni++;
+
+ if (ldb_attr_cmp(e->name, ldb_dn_get_rdn_name(msg->dn))) {
+ rdn_attr = sa;
+ }
+ }
+
+ /* fix meta data count */
+ nmd.ctr.ctr1.count = ni;
+
+ /*
+ * sort meta data array, and move the rdn attribute entry to the end
+ */
+ replmd_replPropertyMetaDataCtr1_sort(&nmd.ctr.ctr1, &rdn_attr->attributeID_id);
+
+ /* generated NDR encoded values */
+ ndr_err = ndr_push_struct_blob(&guid_value, msg,
+ NULL,
+ &guid,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ndr_err = ndr_push_struct_blob(&nmd_value, msg,
+ lp_iconv_convenience(ldb_get_opaque(module->ldb, "loadparm")),
+ &nmd,
+ (ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * add the autogenerated values
+ */
+ ret = ldb_msg_add_value(msg, "objectGUID", &guid_value, NULL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = ldb_msg_add_string(msg, "whenChanged", time_str);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = samdb_msg_add_uint64(module->ldb, msg, msg, "uSNCreated", seq_num);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = samdb_msg_add_uint64(module->ldb, msg, msg, "uSNChanged", seq_num);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = ldb_msg_add_value(msg, "replPropertyMetaData", &nmd_value, NULL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(down_req);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * sort the attributes by attid before storing the object
+ */
+ replmd_ldb_message_sort(msg, schema);
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+static int replmd_add(struct ldb_module *module, struct ldb_request *req)
+{
+ return replmd_prepare_originating(module, req, req->op.add.message->dn,
+ "replmd_add", replmd_add_originating);
+}
+
+static int replmd_modify_originating(struct ldb_module *module,
+ struct ldb_request *req,
+ const struct dsdb_schema *schema)
+{
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ int ret;
+ time_t t = time(NULL);
+ uint64_t seq_num;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "replmd_modify_originating\n");
+
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+
+ /* we have to copy the message as the caller might have it as a const */
+ down_req->op.mod.message = msg = ldb_msg_copy_shallow(down_req, req->op.mod.message);
+ if (msg == NULL) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* TODO:
+ * - get the whole old object
+ * - if the old object doesn't exist report an error
+ * - give an error when a readonly attribute should
+ * be modified
+ * - merge the changed into the old object
+ * if the caller set values to the same value
+ * ignore the attribute, return success when no
+ * attribute was changed
+ * - calculate the new replPropertyMetaData attribute
+ */
+
+ if (add_time_element(msg, "whenChanged", t) != 0) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Get a sequence number from the backend */
+ ret = ldb_sequence_number(module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret == LDB_SUCCESS) {
+ if (add_uint64_element(msg, "uSNChanged", seq_num) != 0) {
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ /* TODO:
+ * - sort the attributes by attid with replmd_ldb_message_sort()
+ * - replace the old object with the newly constructed one
+ */
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ return replmd_prepare_originating(module, req, req->op.mod.message->dn,
+ "replmd_modify", replmd_modify_originating);
+}
+
+static int replmd_replicated_request_reply_helper(struct replmd_replicated_request *ar, int ret)
+{
+ struct ldb_reply *ares = NULL;
+
+ ar->handle->status = ret;
+ ar->handle->state = LDB_ASYNC_DONE;
+
+ if (!ar->orig_req->callback) {
+ return LDB_SUCCESS;
+ }
+
+ /* we're done and need to report the success to the caller */
+ ares = talloc_zero(ar, struct ldb_reply);
+ if (!ares) {
+ ar->handle->status = LDB_ERR_OPERATIONS_ERROR;
+ ar->handle->state = LDB_ASYNC_DONE;
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ares->type = LDB_REPLY_EXTENDED;
+ ares->response = NULL;
+
+ return ar->orig_req->callback(ar->module->ldb, ar->orig_req->context, ares);
+}
+
+static int replmd_replicated_request_done(struct replmd_replicated_request *ar)
+{
+ return replmd_replicated_request_reply_helper(ar, LDB_SUCCESS);
+}
+
+static int replmd_replicated_request_error(struct replmd_replicated_request *ar, int ret)
+{
+ return replmd_replicated_request_reply_helper(ar, ret);
+}
+
+static int replmd_replicated_request_werror(struct replmd_replicated_request *ar, WERROR status)
+{
+ int ret = LDB_ERR_OTHER;
+ /* TODO: do some error mapping */
+ return replmd_replicated_request_reply_helper(ar, ret);
+}
+
+static int replmd_replicated_apply_next(struct replmd_replicated_request *ar);
+
+static int replmd_replicated_apply_add_callback(struct ldb_context *ldb,
+ void *private_data,
+ struct ldb_reply *ares)
+{
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ struct replmd_replicated_request *ar = talloc_get_type(private_data,
+ struct replmd_replicated_request);
+
+ ar->sub.change_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.change_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.change_ret);
+ }
+
+ talloc_free(ar->sub.mem_ctx);
+ ZERO_STRUCT(ar->sub);
+
+ ar->index_current++;
+
+ return replmd_replicated_apply_next(ar);
+#else
+ return LDB_SUCCESS;
+#endif
+}
+
+static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
+{
+ enum ndr_err_code ndr_err;
+ struct ldb_message *msg;
+ struct replPropertyMetaDataBlob *md;
+ struct ldb_val md_value;
+ uint32_t i;
+ uint64_t seq_num;
+ int ret;
+
+ /*
+ * TODO: check if the parent object exist
+ */
+
+ /*
+ * TODO: handle the conflict case where an object with the
+ * same name exist
+ */
+
+ msg = ar->objs->objects[ar->index_current].msg;
+ md = ar->objs->objects[ar->index_current].meta_data;
+
+ ret = ldb_sequence_number(ar->module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ ret = ldb_msg_add_value(msg, "objectGUID", &ar->objs->objects[ar->index_current].guid_value, NULL);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ ret = ldb_msg_add_string(msg, "whenChanged", ar->objs->objects[ar->index_current].when_changed);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ ret = samdb_msg_add_uint64(ar->module->ldb, msg, msg, "uSNCreated", seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ ret = samdb_msg_add_uint64(ar->module->ldb, msg, msg, "uSNChanged", seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ /*
+ * the meta data array is already sorted by the caller
+ */
+ for (i=0; i < md->ctr.ctr1.count; i++) {
+ md->ctr.ctr1.array[i].local_usn = seq_num;
+ }
+ ndr_err = ndr_push_struct_blob(&md_value, msg,
+ lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")),
+ md,
+ (ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+ ret = ldb_msg_add_value(msg, "replPropertyMetaData", &md_value, NULL);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ replmd_ldb_message_sort(msg, ar->schema);
+
+ ret = ldb_build_add_req(&ar->sub.change_req,
+ ar->module->ldb,
+ ar->sub.mem_ctx,
+ msg,
+ NULL,
+ ar,
+ replmd_replicated_apply_add_callback);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ return ldb_next_request(ar->module, ar->sub.change_req);
+#else
+ ret = ldb_next_request(ar->module, ar->sub.change_req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ar->module->ldb, "Failed to add replicated object %s: %s", ldb_dn_get_linearized(ar->sub.change_req->op.add.message->dn),
+ ldb_errstring(ar->module->ldb));
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ ar->sub.change_ret = ldb_wait(ar->sub.change_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.change_ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ar->module->ldb, "Failed while waiting on add replicated object %s: %s", ldb_dn_get_linearized(ar->sub.change_req->op.add.message->dn),
+ ldb_errstring(ar->module->ldb));
+ return replmd_replicated_request_error(ar, ar->sub.change_ret);
+ }
+
+ talloc_free(ar->sub.mem_ctx);
+ ZERO_STRUCT(ar->sub);
+
+ ar->index_current++;
+
+ return LDB_SUCCESS;
+#endif
+}
+
+static int replmd_replPropertyMetaData1_conflict_compare(struct replPropertyMetaData1 *m1,
+ struct replPropertyMetaData1 *m2)
+{
+ int ret;
+
+ if (m1->version != m2->version) {
+ return m1->version - m2->version;
+ }
+
+ if (m1->originating_change_time != m2->originating_change_time) {
+ return m1->originating_change_time - m2->originating_change_time;
+ }
+
+ ret = GUID_compare(&m1->originating_invocation_id, &m2->originating_invocation_id);
+ if (ret != 0) {
+ return ret;
+ }
+
+ return m1->originating_usn - m2->originating_usn;
+}
+
+static int replmd_replicated_apply_merge_callback(struct ldb_context *ldb,
+ void *private_data,
+ struct ldb_reply *ares)
+{
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ struct replmd_replicated_request *ar = talloc_get_type(private_data,
+ struct replmd_replicated_request);
+
+ ret = ldb_next_request(ar->module, ar->sub.change_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ ar->sub.change_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.change_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.change_ret);
+ }
+
+ talloc_free(ar->sub.mem_ctx);
+ ZERO_STRUCT(ar->sub);
+
+ ar->index_current++;
+
+ return LDB_SUCCESS;
+#else
+ return LDB_SUCCESS;
+#endif
+}
+
+static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
+{
+ enum ndr_err_code ndr_err;
+ struct ldb_message *msg;
+ struct replPropertyMetaDataBlob *rmd;
+ struct replPropertyMetaDataBlob omd;
+ const struct ldb_val *omd_value;
+ struct replPropertyMetaDataBlob nmd;
+ struct ldb_val nmd_value;
+ uint32_t i,j,ni=0;
+ uint32_t removed_attrs = 0;
+ uint64_t seq_num;
+ int ret;
+
+ msg = ar->objs->objects[ar->index_current].msg;
+ rmd = ar->objs->objects[ar->index_current].meta_data;
+ ZERO_STRUCT(omd);
+ omd.version = 1;
+
+ /*
+ * TODO: add rename conflict handling
+ */
+ if (ldb_dn_compare(msg->dn, ar->sub.search_msg->dn) != 0) {
+ ldb_debug_set(ar->module->ldb, LDB_DEBUG_FATAL, "replmd_replicated_apply_merge[%u]: rename not supported",
+ ar->index_current);
+ ldb_debug(ar->module->ldb, LDB_DEBUG_FATAL, "%s => %s\n",
+ ldb_dn_get_linearized(ar->sub.search_msg->dn),
+ ldb_dn_get_linearized(msg->dn));
+ return replmd_replicated_request_werror(ar, WERR_NOT_SUPPORTED);
+ }
+
+ ret = ldb_sequence_number(ar->module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ /* find existing meta data */
+ omd_value = ldb_msg_find_ldb_val(ar->sub.search_msg, "replPropertyMetaData");
+ if (omd_value) {
+ ndr_err = ndr_pull_struct_blob(omd_value, ar->sub.mem_ctx,
+ lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")), &omd,
+ (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+
+ if (omd.version != 1) {
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
+ }
+ }
+
+ ZERO_STRUCT(nmd);
+ nmd.version = 1;
+ nmd.ctr.ctr1.count = omd.ctr.ctr1.count + rmd->ctr.ctr1.count;
+ nmd.ctr.ctr1.array = talloc_array(ar->sub.mem_ctx,
+ struct replPropertyMetaData1,
+ nmd.ctr.ctr1.count);
+ if (!nmd.ctr.ctr1.array) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+
+ /* first copy the old meta data */
+ for (i=0; i < omd.ctr.ctr1.count; i++) {
+ nmd.ctr.ctr1.array[ni] = omd.ctr.ctr1.array[i];
+ ni++;
+ }
+
+ /* now merge in the new meta data */
+ for (i=0; i < rmd->ctr.ctr1.count; i++) {
+ bool found = false;
+
+ rmd->ctr.ctr1.array[i].local_usn = seq_num;
+
+ for (j=0; j < ni; j++) {
+ int cmp;
+
+ if (rmd->ctr.ctr1.array[i].attid != nmd.ctr.ctr1.array[j].attid) {
+ continue;
+ }
+
+ cmp = replmd_replPropertyMetaData1_conflict_compare(&rmd->ctr.ctr1.array[i],
+ &nmd.ctr.ctr1.array[j]);
+ if (cmp > 0) {
+ /* replace the entry */
+ nmd.ctr.ctr1.array[j] = rmd->ctr.ctr1.array[i];
+ found = true;
+ break;
+ }
+
+ /* we don't want to apply this change so remove the attribute */
+ ldb_msg_remove_element(msg, &msg->elements[i-removed_attrs]);
+ removed_attrs++;
+
+ found = true;
+ break;
+ }
+
+ if (found) continue;
+
+ nmd.ctr.ctr1.array[ni] = rmd->ctr.ctr1.array[i];
+ ni++;
+ }
+
+ /*
+ * finally correct the size of the meta_data array
+ */
+ nmd.ctr.ctr1.count = ni;
+
+ /*
+ * the rdn attribute (the alias for the name attribute),
+ * 'cn' for most objects is the last entry in the meta data array
+ * we have stored
+ *
+ * sort the new meta data array
+ */
+ {
+ struct replPropertyMetaData1 *rdn_p;
+ uint32_t rdn_idx = omd.ctr.ctr1.count - 1;
+
+ rdn_p = &nmd.ctr.ctr1.array[rdn_idx];
+ replmd_replPropertyMetaDataCtr1_sort(&nmd.ctr.ctr1, &rdn_p->attid);
+ }
+
+ /* create the meta data value */
+ ndr_err = ndr_push_struct_blob(&nmd_value, msg,
+ lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")),
+ &nmd,
+ (ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+
+ /*
+ * check if some replicated attributes left, otherwise skip the ldb_modify() call
+ */
+ if (msg->num_elements == 0) {
+ ldb_debug(ar->module->ldb, LDB_DEBUG_TRACE, "replmd_replicated_apply_merge[%u]: skip replace\n",
+ ar->index_current);
+ goto next_object;
+ }
+
+ ldb_debug(ar->module->ldb, LDB_DEBUG_TRACE, "replmd_replicated_apply_merge[%u]: replace %u attributes\n",
+ ar->index_current, msg->num_elements);
+
+ /*
+ * when we now that we'll modify the record, add the whenChanged, uSNChanged
+ * and replPopertyMetaData attributes
+ */
+ ret = ldb_msg_add_string(msg, "whenChanged", ar->objs->objects[ar->index_current].when_changed);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+ ret = samdb_msg_add_uint64(ar->module->ldb, msg, msg, "uSNChanged", seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+ ret = ldb_msg_add_value(msg, "replPropertyMetaData", &nmd_value, NULL);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ replmd_ldb_message_sort(msg, ar->schema);
+
+ /* we want to replace the old values */
+ for (i=0; i < msg->num_elements; i++) {
+ msg->elements[i].flags = LDB_FLAG_MOD_REPLACE;
+ }
+
+ ret = ldb_build_mod_req(&ar->sub.change_req,
+ ar->module->ldb,
+ ar->sub.mem_ctx,
+ msg,
+ NULL,
+ ar,
+ replmd_replicated_apply_merge_callback);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ return ldb_next_request(ar->module, ar->sub.change_req);
+#else
+ ret = ldb_next_request(ar->module, ar->sub.change_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ ar->sub.change_ret = ldb_wait(ar->sub.change_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.change_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.change_ret);
+ }
+
+next_object:
+ talloc_free(ar->sub.mem_ctx);
+ ZERO_STRUCT(ar->sub);
+
+ ar->index_current++;
+
+ return LDB_SUCCESS;
+#endif
+}
+
+static int replmd_replicated_apply_search_callback(struct ldb_context *ldb,
+ void *private_data,
+ struct ldb_reply *ares)
+{
+ struct replmd_replicated_request *ar = talloc_get_type(private_data,
+ struct replmd_replicated_request);
+ bool is_done = false;
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ ar->sub.search_msg = talloc_steal(ar->sub.mem_ctx, ares->message);
+ break;
+ case LDB_REPLY_REFERRAL:
+ /* we ignore referrals */
+ break;
+ case LDB_REPLY_EXTENDED:
+ case LDB_REPLY_DONE:
+ is_done = true;
+ }
+
+ talloc_free(ares);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ if (is_done) {
+ ar->sub.search_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.search_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.search_ret);
+ }
+ if (ar->sub.search_msg) {
+ return replmd_replicated_apply_merge(ar);
+ }
+ return replmd_replicated_apply_add(ar);
+ }
+#endif
+ return LDB_SUCCESS;
+}
+
+static int replmd_replicated_apply_search(struct replmd_replicated_request *ar)
+{
+ int ret;
+ char *tmp_str;
+ char *filter;
+
+ tmp_str = ldb_binary_encode(ar->sub.mem_ctx, ar->objs->objects[ar->index_current].guid_value);
+ if (!tmp_str) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+
+ filter = talloc_asprintf(ar->sub.mem_ctx, "(objectGUID=%s)", tmp_str);
+ if (!filter) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+ talloc_free(tmp_str);
+
+ ret = ldb_build_search_req(&ar->sub.search_req,
+ ar->module->ldb,
+ ar->sub.mem_ctx,
+ ar->objs->partition_dn,
+ LDB_SCOPE_SUBTREE,
+ filter,
+ NULL,
+ NULL,
+ ar,
+ replmd_replicated_apply_search_callback);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ return ldb_next_request(ar->module, ar->sub.search_req);
+#else
+ ret = ldb_next_request(ar->module, ar->sub.search_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ ar->sub.search_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.search_ret != LDB_SUCCESS && ar->sub.search_ret != LDB_ERR_NO_SUCH_OBJECT) {
+ return replmd_replicated_request_error(ar, ar->sub.search_ret);
+ }
+ if (ar->sub.search_msg) {
+ return replmd_replicated_apply_merge(ar);
+ }
+
+ return replmd_replicated_apply_add(ar);
+#endif
+}
+
+static int replmd_replicated_apply_next(struct replmd_replicated_request *ar)
+{
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ if (ar->index_current >= ar->objs->num_objects) {
+ return replmd_replicated_uptodate_vector(ar);
+ }
+#endif
+
+ ar->sub.mem_ctx = talloc_new(ar);
+ if (!ar->sub.mem_ctx) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+
+ return replmd_replicated_apply_search(ar);
+}
+
+static int replmd_replicated_uptodate_modify_callback(struct ldb_context *ldb,
+ void *private_data,
+ struct ldb_reply *ares)
+{
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ struct replmd_replicated_request *ar = talloc_get_type(private_data,
+ struct replmd_replicated_request);
+
+ ar->sub.change_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.change_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.change_ret);
+ }
+
+ talloc_free(ar->sub.mem_ctx);
+ ZERO_STRUCT(ar->sub);
+
+ return replmd_replicated_request_done(ar);
+#else
+ return LDB_SUCCESS;
+#endif
+}
+
+static int replmd_drsuapi_DsReplicaCursor2_compare(const struct drsuapi_DsReplicaCursor2 *c1,
+ const struct drsuapi_DsReplicaCursor2 *c2)
+{
+ return GUID_compare(&c1->source_dsa_invocation_id, &c2->source_dsa_invocation_id);
+}
+
+static int replmd_replicated_uptodate_modify(struct replmd_replicated_request *ar)
+{
+ enum ndr_err_code ndr_err;
+ struct ldb_message *msg;
+ struct replUpToDateVectorBlob ouv;
+ const struct ldb_val *ouv_value;
+ const struct drsuapi_DsReplicaCursor2CtrEx *ruv;
+ struct replUpToDateVectorBlob nuv;
+ struct ldb_val nuv_value;
+ struct ldb_message_element *nuv_el = NULL;
+ const struct GUID *our_invocation_id;
+ struct ldb_message_element *orf_el = NULL;
+ struct repsFromToBlob nrf;
+ struct ldb_val *nrf_value = NULL;
+ struct ldb_message_element *nrf_el = NULL;
+ uint32_t i,j,ni=0;
+ uint64_t seq_num;
+ bool found = false;
+ time_t t = time(NULL);
+ NTTIME now;
+ int ret;
+
+ ruv = ar->objs->uptodateness_vector;
+ ZERO_STRUCT(ouv);
+ ouv.version = 2;
+ ZERO_STRUCT(nuv);
+ nuv.version = 2;
+
+ unix_to_nt_time(&now, t);
+
+ /*
+ * we use the next sequence number for our own highest_usn
+ * because we will do a modify request and this will increment
+ * our highest_usn
+ */
+ ret = ldb_sequence_number(ar->module->ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ /*
+ * first create the new replUpToDateVector
+ */
+ ouv_value = ldb_msg_find_ldb_val(ar->sub.search_msg, "replUpToDateVector");
+ if (ouv_value) {
+ ndr_err = ndr_pull_struct_blob(ouv_value, ar->sub.mem_ctx,
+ lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")), &ouv,
+ (ndr_pull_flags_fn_t)ndr_pull_replUpToDateVectorBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+
+ if (ouv.version != 2) {
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
+ }
+ }
+
+ /*
+ * the new uptodateness vector will at least
+ * contain 1 entry, one for the source_dsa
+ *
+ * plus optional values from our old vector and the one from the source_dsa
+ */
+ nuv.ctr.ctr2.count = 1 + ouv.ctr.ctr2.count;
+ if (ruv) nuv.ctr.ctr2.count += ruv->count;
+ nuv.ctr.ctr2.cursors = talloc_array(ar->sub.mem_ctx,
+ struct drsuapi_DsReplicaCursor2,
+ nuv.ctr.ctr2.count);
+ if (!nuv.ctr.ctr2.cursors) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+
+ /* first copy the old vector */
+ for (i=0; i < ouv.ctr.ctr2.count; i++) {
+ nuv.ctr.ctr2.cursors[ni] = ouv.ctr.ctr2.cursors[i];
+ ni++;
+ }
+
+ /* get our invocation_id if we have one already attached to the ldb */
+ our_invocation_id = samdb_ntds_invocation_id(ar->module->ldb);
+
+ /* merge in the source_dsa vector is available */
+ for (i=0; (ruv && i < ruv->count); i++) {
+ found = false;
+
+ if (our_invocation_id &&
+ GUID_equal(&ruv->cursors[i].source_dsa_invocation_id,
+ our_invocation_id)) {
+ continue;
+ }
+
+ for (j=0; j < ni; j++) {
+ if (!GUID_equal(&ruv->cursors[i].source_dsa_invocation_id,
+ &nuv.ctr.ctr2.cursors[j].source_dsa_invocation_id)) {
+ continue;
+ }
+
+ found = true;
+
+ /*
+ * we update only the highest_usn and not the latest_sync_success time,
+ * because the last success stands for direct replication
+ */
+ if (ruv->cursors[i].highest_usn > nuv.ctr.ctr2.cursors[j].highest_usn) {
+ nuv.ctr.ctr2.cursors[j].highest_usn = ruv->cursors[i].highest_usn;
+ }
+ break;
+ }
+
+ if (found) continue;
+
+ /* if it's not there yet, add it */
+ nuv.ctr.ctr2.cursors[ni] = ruv->cursors[i];
+ ni++;
+ }
+
+ /*
+ * merge in the current highwatermark for the source_dsa
+ */
+ found = false;
+ for (j=0; j < ni; j++) {
+ if (!GUID_equal(&ar->objs->source_dsa->source_dsa_invocation_id,
+ &nuv.ctr.ctr2.cursors[j].source_dsa_invocation_id)) {
+ continue;
+ }
+
+ found = true;
+
+ /*
+ * here we update the highest_usn and last_sync_success time
+ * because we're directly replicating from the source_dsa
+ *
+ * and use the tmp_highest_usn because this is what we have just applied
+ * to our ldb
+ */
+ nuv.ctr.ctr2.cursors[j].highest_usn = ar->objs->source_dsa->highwatermark.tmp_highest_usn;
+ nuv.ctr.ctr2.cursors[j].last_sync_success = now;
+ break;
+ }
+ if (!found) {
+ /*
+ * here we update the highest_usn and last_sync_success time
+ * because we're directly replicating from the source_dsa
+ *
+ * and use the tmp_highest_usn because this is what we have just applied
+ * to our ldb
+ */
+ nuv.ctr.ctr2.cursors[ni].source_dsa_invocation_id= ar->objs->source_dsa->source_dsa_invocation_id;
+ nuv.ctr.ctr2.cursors[ni].highest_usn = ar->objs->source_dsa->highwatermark.tmp_highest_usn;
+ nuv.ctr.ctr2.cursors[ni].last_sync_success = now;
+ ni++;
+ }
+
+ /*
+ * finally correct the size of the cursors array
+ */
+ nuv.ctr.ctr2.count = ni;
+
+ /*
+ * sort the cursors
+ */
+ qsort(nuv.ctr.ctr2.cursors, nuv.ctr.ctr2.count,
+ sizeof(struct drsuapi_DsReplicaCursor2),
+ (comparison_fn_t)replmd_drsuapi_DsReplicaCursor2_compare);
+
+ /*
+ * create the change ldb_message
+ */
+ msg = ldb_msg_new(ar->sub.mem_ctx);
+ if (!msg) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+ msg->dn = ar->sub.search_msg->dn;
+
+ ndr_err = ndr_push_struct_blob(&nuv_value, msg,
+ lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")),
+ &nuv,
+ (ndr_push_flags_fn_t)ndr_push_replUpToDateVectorBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+ ret = ldb_msg_add_value(msg, "replUpToDateVector", &nuv_value, &nuv_el);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+ nuv_el->flags = LDB_FLAG_MOD_REPLACE;
+
+ /*
+ * now create the new repsFrom value from the given repsFromTo1 structure
+ */
+ ZERO_STRUCT(nrf);
+ nrf.version = 1;
+ nrf.ctr.ctr1 = *ar->objs->source_dsa;
+ /* and fix some values... */
+ nrf.ctr.ctr1.consecutive_sync_failures = 0;
+ nrf.ctr.ctr1.last_success = now;
+ nrf.ctr.ctr1.last_attempt = now;
+ nrf.ctr.ctr1.result_last_attempt = WERR_OK;
+ nrf.ctr.ctr1.highwatermark.highest_usn = nrf.ctr.ctr1.highwatermark.tmp_highest_usn;
+
+ /*
+ * first see if we already have a repsFrom value for the current source dsa
+ * if so we'll later replace this value
+ */
+ orf_el = ldb_msg_find_element(ar->sub.search_msg, "repsFrom");
+ if (orf_el) {
+ for (i=0; i < orf_el->num_values; i++) {
+ struct repsFromToBlob *trf;
+
+ trf = talloc(ar->sub.mem_ctx, struct repsFromToBlob);
+ if (!trf) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+
+ ndr_err = ndr_pull_struct_blob(&orf_el->values[i], trf, lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")), trf,
+ (ndr_pull_flags_fn_t)ndr_pull_repsFromToBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+
+ if (trf->version != 1) {
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
+ }
+
+ /*
+ * we compare the source dsa objectGUID not the invocation_id
+ * because we want only one repsFrom value per source dsa
+ * and when the invocation_id of the source dsa has changed we don't need
+ * the old repsFrom with the old invocation_id
+ */
+ if (!GUID_equal(&trf->ctr.ctr1.source_dsa_obj_guid,
+ &ar->objs->source_dsa->source_dsa_obj_guid)) {
+ talloc_free(trf);
+ continue;
+ }
+
+ talloc_free(trf);
+ nrf_value = &orf_el->values[i];
+ break;
+ }
+
+ /*
+ * copy over all old values to the new ldb_message
+ */
+ ret = ldb_msg_add_empty(msg, "repsFrom", 0, &nrf_el);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+ *nrf_el = *orf_el;
+ }
+
+ /*
+ * if we haven't found an old repsFrom value for the current source dsa
+ * we'll add a new value
+ */
+ if (!nrf_value) {
+ struct ldb_val zero_value;
+ ZERO_STRUCT(zero_value);
+ ret = ldb_msg_add_value(msg, "repsFrom", &zero_value, &nrf_el);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ nrf_value = &nrf_el->values[nrf_el->num_values - 1];
+ }
+
+ /* we now fill the value which is already attached to ldb_message */
+ ndr_err = ndr_push_struct_blob(nrf_value, msg,
+ lp_iconv_convenience(ldb_get_opaque(ar->module->ldb, "loadparm")),
+ &nrf,
+ (ndr_push_flags_fn_t)ndr_push_repsFromToBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+
+ /*
+ * the ldb_message_element for the attribute, has all the old values and the new one
+ * so we'll replace the whole attribute with all values
+ */
+ nrf_el->flags = LDB_FLAG_MOD_REPLACE;
+
+ /* prepare the ldb_modify() request */
+ ret = ldb_build_mod_req(&ar->sub.change_req,
+ ar->module->ldb,
+ ar->sub.mem_ctx,
+ msg,
+ NULL,
+ ar,
+ replmd_replicated_uptodate_modify_callback);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ return ldb_next_request(ar->module, ar->sub.change_req);
+#else
+ ret = ldb_next_request(ar->module, ar->sub.change_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ ar->sub.change_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.change_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.change_ret);
+ }
+
+ talloc_free(ar->sub.mem_ctx);
+ ZERO_STRUCT(ar->sub);
+
+ return replmd_replicated_request_done(ar);
+#endif
+}
+
+static int replmd_replicated_uptodate_search_callback(struct ldb_context *ldb,
+ void *private_data,
+ struct ldb_reply *ares)
+{
+ struct replmd_replicated_request *ar = talloc_get_type(private_data,
+ struct replmd_replicated_request);
+ bool is_done = false;
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ ar->sub.search_msg = talloc_steal(ar->sub.mem_ctx, ares->message);
+ break;
+ case LDB_REPLY_REFERRAL:
+ /* we ignore referrals */
+ break;
+ case LDB_REPLY_EXTENDED:
+ case LDB_REPLY_DONE:
+ is_done = true;
+ }
+
+ talloc_free(ares);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ if (is_done) {
+ ar->sub.search_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.search_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.search_ret);
+ }
+ if (!ar->sub.search_msg) {
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
+ }
+
+ return replmd_replicated_uptodate_modify(ar);
+ }
+#endif
+ return LDB_SUCCESS;
+}
+
+static int replmd_replicated_uptodate_search(struct replmd_replicated_request *ar)
+{
+ int ret;
+ static const char *attrs[] = {
+ "replUpToDateVector",
+ "repsFrom",
+ NULL
+ };
+
+ ret = ldb_build_search_req(&ar->sub.search_req,
+ ar->module->ldb,
+ ar->sub.mem_ctx,
+ ar->objs->partition_dn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs,
+ NULL,
+ ar,
+ replmd_replicated_uptodate_search_callback);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ return ldb_next_request(ar->module, ar->sub.search_req);
+#else
+ ret = ldb_next_request(ar->module, ar->sub.search_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ ar->sub.search_ret = ldb_wait(ar->sub.search_req->handle, LDB_WAIT_ALL);
+ if (ar->sub.search_ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ar->sub.search_ret);
+ }
+ if (!ar->sub.search_msg) {
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
+ }
+
+ return replmd_replicated_uptodate_modify(ar);
+#endif
+}
+
+static int replmd_replicated_uptodate_vector(struct replmd_replicated_request *ar)
+{
+ ar->sub.mem_ctx = talloc_new(ar);
+ if (!ar->sub.mem_ctx) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+
+ return replmd_replicated_uptodate_search(ar);
+}
+
+static int replmd_extended_replicated_objects(struct ldb_module *module, struct ldb_request *req)
+{
+ struct dsdb_extended_replicated_objects *objs;
+ struct replmd_replicated_request *ar;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "replmd_extended_replicated_objects\n");
+
+ objs = talloc_get_type(req->op.extended.data, struct dsdb_extended_replicated_objects);
+ if (!objs) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "replmd_extended_replicated_objects: invalid extended data\n");
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ if (objs->version != DSDB_EXTENDED_REPLICATED_OBJECTS_VERSION) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "replmd_extended_replicated_objects: extended data invalid version [%u != %u]\n",
+ objs->version, DSDB_EXTENDED_REPLICATED_OBJECTS_VERSION);
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ ar = replmd_replicated_init_handle(module, req, objs);
+ if (!ar) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+#ifdef REPLMD_FULL_ASYNC /* TODO: activate this code when ldb support full async code */
+ return replmd_replicated_apply_next(ar);
+#else
+ while (ar->index_current < ar->objs->num_objects &&
+ req->handle->state != LDB_ASYNC_DONE) {
+ replmd_replicated_apply_next(ar);
+ }
+
+ if (req->handle->state != LDB_ASYNC_DONE) {
+ replmd_replicated_uptodate_vector(ar);
+ }
+
+ return LDB_SUCCESS;
+#endif
+}
+
+static int replmd_extended(struct ldb_module *module, struct ldb_request *req)
+{
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_REPLICATED_OBJECTS_OID) == 0) {
+ return replmd_extended_replicated_objects(module, req);
+ }
+
+ return ldb_next_request(module, req);
+}
+
+static int replmd_wait_none(struct ldb_handle *handle) {
+ struct replmd_replicated_request *ar;
+
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ar = talloc_get_type(handle->private_data, struct replmd_replicated_request);
+ if (!ar) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* we do only sync calls */
+ if (handle->state != LDB_ASYNC_DONE) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return handle->status;
+}
+
+static int replmd_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = replmd_wait_none(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int replmd_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return replmd_wait_all(handle);
+ } else {
+ return replmd_wait_none(handle);
+ }
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_repl_meta_data_module_ops = {
+ .name = "repl_meta_data",
+ .add = replmd_add,
+ .modify = replmd_modify,
+ .extended = replmd_extended,
+ .wait = replmd_wait
+};
diff --git a/source4/dsdb/samdb/ldb_modules/rootdse.c b/source4/dsdb/samdb/ldb_modules/rootdse.c
new file mode 100644
index 0000000000..ebc90d4cf3
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/rootdse.c
@@ -0,0 +1,441 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ rootDSE ldb module
+
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Simo Sorce 2005
+
+ 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 "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb_private.h"
+#include "system/time.h"
+#include "dsdb/samdb/samdb.h"
+#include "version.h"
+
+struct private_data {
+ int num_controls;
+ char **controls;
+ int num_partitions;
+ struct ldb_dn **partitions;
+};
+
+/*
+ return 1 if a specific attribute has been requested
+*/
+static int do_attribute(const char * const *attrs, const char *name)
+{
+ return attrs == NULL ||
+ ldb_attr_in_list(attrs, name) ||
+ ldb_attr_in_list(attrs, "*");
+}
+
+static int do_attribute_explicit(const char * const *attrs, const char *name)
+{
+ return attrs != NULL && ldb_attr_in_list(attrs, name);
+}
+
+
+/*
+ add dynamically generated attributes to rootDSE result
+*/
+static int rootdse_add_dynamic(struct ldb_module *module, struct ldb_message *msg, const char * const *attrs)
+{
+ struct private_data *priv = talloc_get_type(module->private_data, struct private_data);
+ char **server_sasl;
+ const struct dsdb_schema *schema;
+
+ schema = dsdb_get_schema(module->ldb);
+
+ msg->dn = ldb_dn_new(msg, module->ldb, NULL);
+
+ /* don't return the distinduishedName, cn and name attributes */
+ ldb_msg_remove_attr(msg, "distinguishedName");
+ ldb_msg_remove_attr(msg, "cn");
+ ldb_msg_remove_attr(msg, "name");
+
+ if (do_attribute(attrs, "currentTime")) {
+ if (ldb_msg_add_steal_string(msg, "currentTime",
+ ldb_timestring(msg, time(NULL))) != 0) {
+ goto failed;
+ }
+ }
+
+ if (do_attribute(attrs, "supportedControl")) {
+ int i;
+ for (i = 0; i < priv->num_controls; i++) {
+ char *control = talloc_strdup(msg, priv->controls[i]);
+ if (!control) {
+ goto failed;
+ }
+ if (ldb_msg_add_steal_string(msg, "supportedControl",
+ control) != 0) {
+ goto failed;
+ }
+ }
+ }
+
+ if (do_attribute(attrs, "namingContexts")) {
+ int i;
+ for (i = 0; i < priv->num_partitions; i++) {
+ struct ldb_dn *dn = priv->partitions[i];
+ if (ldb_msg_add_steal_string(msg, "namingContexts",
+ ldb_dn_alloc_linearized(msg, dn)) != 0) {
+ goto failed;
+ }
+ }
+ }
+
+ server_sasl = talloc_get_type(ldb_get_opaque(module->ldb, "supportedSASLMechanims"),
+ char *);
+ if (server_sasl && do_attribute(attrs, "supportedSASLMechanisms")) {
+ int i;
+ for (i = 0; server_sasl && server_sasl[i]; i++) {
+ char *sasl_name = talloc_strdup(msg, server_sasl[i]);
+ if (!sasl_name) {
+ goto failed;
+ }
+ if (ldb_msg_add_steal_string(msg, "supportedSASLMechanisms",
+ sasl_name) != 0) {
+ goto failed;
+ }
+ }
+ }
+
+ if (do_attribute(attrs, "highestCommittedUSN")) {
+ uint64_t seq_num;
+ int ret = ldb_sequence_number(module->ldb, LDB_SEQ_HIGHEST_SEQ, &seq_num);
+ if (ret == LDB_SUCCESS) {
+ if (ldb_msg_add_fmt(msg, "highestCommittedUSN",
+ "%llu", (unsigned long long)seq_num) != 0) {
+ goto failed;
+ }
+ }
+ }
+
+ if (schema && do_attribute_explicit(attrs, "dsSchemaAttrCount")) {
+ struct dsdb_attribute *cur;
+ uint32_t n = 0;
+
+ for (cur = schema->attributes; cur; cur = cur->next) {
+ n++;
+ }
+
+ if (ldb_msg_add_fmt(msg, "dsSchemaAttrCount",
+ "%u", n) != 0) {
+ goto failed;
+ }
+ }
+
+ if (schema && do_attribute_explicit(attrs, "dsSchemaClassCount")) {
+ struct dsdb_class *cur;
+ uint32_t n = 0;
+
+ for (cur = schema->classes; cur; cur = cur->next) {
+ n++;
+ }
+
+ if (ldb_msg_add_fmt(msg, "dsSchemaClassCount",
+ "%u", n) != 0) {
+ goto failed;
+ }
+ }
+
+ if (schema && do_attribute_explicit(attrs, "dsSchemaPrefixCount")) {
+ if (ldb_msg_add_fmt(msg, "dsSchemaPrefixCount",
+ "%u", schema->num_prefixes) != 0) {
+ goto failed;
+ }
+ }
+
+ if (do_attribute_explicit(attrs, "validFSMOs")) {
+ const struct dsdb_naming_fsmo *naming_fsmo;
+ const struct dsdb_pdc_fsmo *pdc_fsmo;
+ const char *dn_str;
+
+ if (schema && schema->fsmo.we_are_master) {
+ dn_str = ldb_dn_get_linearized(samdb_schema_dn(module->ldb));
+ if (dn_str && dn_str[0]) {
+ if (ldb_msg_add_fmt(msg, "validFSMOs", "%s", dn_str) != 0) {
+ goto failed;
+ }
+ }
+ }
+
+ naming_fsmo = talloc_get_type(ldb_get_opaque(module->ldb, "dsdb_naming_fsmo"),
+ struct dsdb_naming_fsmo);
+ if (naming_fsmo && naming_fsmo->we_are_master) {
+ dn_str = ldb_dn_get_linearized(samdb_partitions_dn(module->ldb, msg));
+ if (dn_str && dn_str[0]) {
+ if (ldb_msg_add_fmt(msg, "validFSMOs", "%s", dn_str) != 0) {
+ goto failed;
+ }
+ }
+ }
+
+ pdc_fsmo = talloc_get_type(ldb_get_opaque(module->ldb, "dsdb_pdc_fsmo"),
+ struct dsdb_pdc_fsmo);
+ if (pdc_fsmo && pdc_fsmo->we_are_master) {
+ dn_str = ldb_dn_get_linearized(samdb_base_dn(module->ldb));
+ if (dn_str && dn_str[0]) {
+ if (ldb_msg_add_fmt(msg, "validFSMOs", "%s", dn_str) != 0) {
+ goto failed;
+ }
+ }
+ }
+ }
+
+ if (schema && do_attribute_explicit(attrs, "vendorVersion")) {
+ if (ldb_msg_add_fmt(msg, "vendorVersion",
+ "%s", SAMBA_VERSION_STRING) != 0) {
+ goto failed;
+ }
+ }
+
+ /* TODO: lots more dynamic attributes should be added here */
+
+ return LDB_SUCCESS;
+
+failed:
+ return LDB_ERR_OPERATIONS_ERROR;
+}
+
+/*
+ handle search requests
+*/
+
+struct rootdse_context {
+ struct ldb_module *module;
+ void *up_context;
+ int (*up_callback)(struct ldb_context *, void *, struct ldb_reply *);
+
+ const char * const * attrs;
+};
+
+static int rootdse_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct rootdse_context *ac;
+
+ ac = talloc_get_type(context, struct rootdse_context);
+
+ if (ares->type == LDB_REPLY_ENTRY) {
+ /*
+ * if the client explicit asks for the 'netlogon' attribute
+ * the reply_entry needs to be skipped
+ */
+ if (ac->attrs && ldb_attr_in_list(ac->attrs, "netlogon")) {
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+
+ /* for each record returned post-process to add any dynamic
+ attributes that have been asked for */
+ if (rootdse_add_dynamic(ac->module, ares->message, ac->attrs) != LDB_SUCCESS) {
+ goto error;
+ }
+ }
+
+ return ac->up_callback(ldb, ac->up_context, ares);
+
+error:
+ talloc_free(ares);
+ return LDB_ERR_OPERATIONS_ERROR;
+}
+
+static int rootdse_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct rootdse_context *ac;
+ struct ldb_request *down_req;
+ int ret;
+
+ /* see if its for the rootDSE - only a base search on the "" DN qualifies */
+ if (req->op.search.scope != LDB_SCOPE_BASE ||
+ ( ! ldb_dn_is_null(req->op.search.base))) {
+ /* Otherwise, pass down to the rest of the stack */
+ return ldb_next_request(module, req);
+ }
+
+ ac = talloc(req, struct rootdse_context);
+ if (ac == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->module = module;
+ ac->up_context = req->context;
+ ac->up_callback = req->callback;
+ ac->attrs = req->op.search.attrs;
+
+ down_req = talloc_zero(req, struct ldb_request);
+ if (down_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ down_req->operation = req->operation;
+ /* in our db we store the rootDSE with a DN of @ROOTDSE */
+ down_req->op.search.base = ldb_dn_new(down_req, module->ldb, "@ROOTDSE");
+ down_req->op.search.scope = LDB_SCOPE_BASE;
+ down_req->op.search.tree = ldb_parse_tree(down_req, NULL);
+ if (down_req->op.search.base == NULL || down_req->op.search.tree == NULL) {
+ ldb_oom(module->ldb);
+ talloc_free(down_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ down_req->op.search.attrs = req->op.search.attrs;
+ down_req->controls = req->controls;
+
+ down_req->context = ac;
+ down_req->callback = rootdse_callback;
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* perform the search */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+static int rootdse_register_control(struct ldb_module *module, struct ldb_request *req)
+{
+ struct private_data *priv = talloc_get_type(module->private_data, struct private_data);
+ char **list;
+
+ list = talloc_realloc(priv, priv->controls, char *, priv->num_controls + 1);
+ if (!list) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ list[priv->num_controls] = talloc_strdup(list, req->op.reg_control.oid);
+ if (!list[priv->num_controls]) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ priv->num_controls += 1;
+ priv->controls = list;
+
+ return LDB_SUCCESS;
+}
+
+static int rootdse_register_partition(struct ldb_module *module, struct ldb_request *req)
+{
+ struct private_data *priv = talloc_get_type(module->private_data, struct private_data);
+ struct ldb_dn **list;
+
+ list = talloc_realloc(priv, priv->partitions, struct ldb_dn *, priv->num_partitions + 1);
+ if (!list) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ list[priv->num_partitions] = ldb_dn_copy(list, req->op.reg_partition.dn);
+ if (!list[priv->num_partitions]) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ priv->num_partitions += 1;
+ priv->partitions = list;
+
+ return LDB_SUCCESS;
+}
+
+
+static int rootdse_request(struct ldb_module *module, struct ldb_request *req)
+{
+ switch (req->operation) {
+
+ case LDB_REQ_REGISTER_CONTROL:
+ return rootdse_register_control(module, req);
+ case LDB_REQ_REGISTER_PARTITION:
+ return rootdse_register_partition(module, req);
+
+ default:
+ break;
+ }
+ return ldb_next_request(module, req);
+}
+
+static int rootdse_init(struct ldb_module *module)
+{
+ struct private_data *data;
+
+ data = talloc(module, struct private_data);
+ if (data == NULL) {
+ return -1;
+ }
+
+ data->num_controls = 0;
+ data->controls = NULL;
+ data->num_partitions = 0;
+ data->partitions = NULL;
+ module->private_data = data;
+
+ ldb_set_default_dns(module->ldb);
+
+ return ldb_next_init(module);
+}
+
+static int rootdse_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_result *ext_res;
+ int ret;
+ struct ldb_dn *schema_dn;
+ struct ldb_message_element *schemaUpdateNowAttr;
+
+ /*
+ If dn is not "" we should let it pass through
+ */
+ if (!ldb_dn_is_null(req->op.mod.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /*
+ dn is empty so check for schemaUpdateNow attribute
+ "The type of modification and values specified in the LDAP modify operation do not matter." MSDN
+ */
+ schemaUpdateNowAttr = ldb_msg_find_element(req->op.mod.message, "schemaUpdateNow");
+ if (!schemaUpdateNowAttr) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ schema_dn = samdb_schema_dn(module->ldb);
+ if (!schema_dn) {
+ ldb_reset_err_string(module->ldb);
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "rootdse_modify: no schema dn present: (skip ldb_extended call)\n");
+ return ldb_next_request(module, req);
+ }
+
+ ret = ldb_extended(module->ldb, DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID, schema_dn, &ext_res);
+ if (ret != LDB_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_free(ext_res);
+ return ret;
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_rootdse_module_ops = {
+ .name = "rootdse",
+ .init_context = rootdse_init,
+ .search = rootdse_search,
+ .request = rootdse_request,
+ .modify = rootdse_modify
+};
diff --git a/source4/dsdb/samdb/ldb_modules/samba3sam.c b/source4/dsdb/samdb/ldb_modules/samba3sam.c
new file mode 100644
index 0000000000..7a123c818f
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/samba3sam.c
@@ -0,0 +1,932 @@
+/*
+ ldb database library - Samba3 SAM compatibility backend
+
+ Copyright (C) Jelmer Vernooij 2005
+ Copyright (C) Martin Kuehl <mkhl@samba.org> 2006
+*/
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_private.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/ldb_map/ldb_map.h"
+#include "system/passwd.h"
+
+#include "librpc/gen_ndr/ndr_security.h"
+#include "librpc/gen_ndr/ndr_samr.h"
+#include "librpc/ndr/libndr.h"
+#include "libcli/security/security.h"
+#include "libcli/security/proto.h"
+#include "lib/samba3/samba3.h"
+
+/*
+ * sambaSID -> member (dn!)
+ * sambaSIDList -> member (dn!)
+ * sambaDomainName -> name
+ * sambaTrustPassword
+ * sambaUnixIdPool
+ * sambaIdmapEntry
+ * sambaAccountPolicy
+ * sambaSidEntry
+ * sambaAcctFlags -> systemFlags ?
+ * sambaPasswordHistory -> ntPwdHistory*/
+
+/* Not necessary:
+ * sambaConfig
+ * sambaShare
+ * sambaConfigOption
+ * sambaNextGroupRid
+ * sambaNextUserRid
+ * sambaAlgorithmicRidBase
+ */
+
+/* Not in Samba4:
+ * sambaKickoffTime
+ * sambaPwdCanChange
+ * sambaPwdMustChange
+ * sambaHomePath
+ * sambaHomeDrive
+ * sambaLogonScript
+ * sambaProfilePath
+ * sambaUserWorkstations
+ * sambaMungedDial
+ * sambaLogonHours */
+
+/* In Samba4 but not in Samba3:
+*/
+
+/* From a sambaPrimaryGroupSID, generate a primaryGroupID (integer) attribute */
+static struct ldb_message_element *generate_primaryGroupID(struct ldb_module *module, TALLOC_CTX *ctx, const char *local_attr, const struct ldb_message *remote)
+{
+ struct ldb_message_element *el;
+ const char *sid = ldb_msg_find_attr_as_string(remote, "sambaPrimaryGroupSID", NULL);
+ const char *p;
+
+ if (!sid)
+ return NULL;
+
+ p = strrchr(sid, '-');
+ if (!p)
+ return NULL;
+
+ el = talloc_zero(ctx, struct ldb_message_element);
+ el->name = talloc_strdup(ctx, "primaryGroupID");
+ el->num_values = 1;
+ el->values = talloc_array(ctx, struct ldb_val, 1);
+ el->values[0].data = (uint8_t *)talloc_strdup(el->values, p+1);
+ el->values[0].length = strlen((char *)el->values[0].data);
+
+ return el;
+}
+
+static void generate_sambaPrimaryGroupSID(struct ldb_module *module, const char *local_attr, const struct ldb_message *local, struct ldb_message *remote_mp, struct ldb_message *remote_fb)
+{
+ const struct ldb_val *sidval;
+ char *sidstring;
+ struct dom_sid *sid;
+ enum ndr_err_code ndr_err;
+
+ /* We need the domain, so we get it from the objectSid that we hope is here... */
+ sidval = ldb_msg_find_ldb_val(local, "objectSid");
+
+ if (!sidval)
+ return; /* Sorry, no SID today.. */
+
+ sid = talloc(remote_mp, struct dom_sid);
+ if (sid == NULL) {
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob(sidval, sid, NULL, sid, (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(sid);
+ return;
+ }
+
+ if (!ldb_msg_find_ldb_val(local, "primaryGroupID"))
+ return; /* Sorry, no SID today.. */
+
+ sid->num_auths--;
+
+ sidstring = dom_sid_string(remote_mp, sid);
+ talloc_free(sid);
+ ldb_msg_add_fmt(remote_mp, "sambaPrimaryGroupSID", "%s-%d", sidstring, ldb_msg_find_attr_as_uint(local, "primaryGroupID", 0));
+ talloc_free(sidstring);
+}
+
+/* Just copy the old value. */
+static struct ldb_val convert_uid_samaccount(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out = data_blob(NULL, 0);
+ ldb_handler_copy(module->ldb, ctx, val, &out);
+
+ return out;
+}
+
+static struct ldb_val lookup_homedir(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct passwd *pwd;
+ struct ldb_val retval;
+
+ pwd = getpwnam((char *)val->data);
+
+ if (!pwd) {
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING, "Unable to lookup '%s' in passwd", (char *)val->data);
+ return *talloc_zero(ctx, struct ldb_val);
+ }
+
+ retval.data = (uint8_t *)talloc_strdup(ctx, pwd->pw_dir);
+ retval.length = strlen((char *)retval.data);
+
+ return retval;
+}
+
+static struct ldb_val lookup_gid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct passwd *pwd;
+ struct ldb_val retval;
+
+ pwd = getpwnam((char *)val->data);
+
+ if (!pwd) {
+ return *talloc_zero(ctx, struct ldb_val);
+ }
+
+ retval.data = (uint8_t *)talloc_asprintf(ctx, "%ld", (unsigned long)pwd->pw_gid);
+ retval.length = strlen((char *)retval.data);
+
+ return retval;
+}
+
+static struct ldb_val lookup_uid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct passwd *pwd;
+ struct ldb_val retval;
+
+ pwd = getpwnam((char *)val->data);
+
+ if (!pwd) {
+ return *talloc_zero(ctx, struct ldb_val);
+ }
+
+ retval.data = (uint8_t *)talloc_asprintf(ctx, "%ld", (unsigned long)pwd->pw_uid);
+ retval.length = strlen((char *)retval.data);
+
+ return retval;
+}
+
+/* Encode a sambaSID to an objectSid. */
+static struct ldb_val encode_sid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out = data_blob(NULL, 0);
+ struct dom_sid *sid;
+ enum ndr_err_code ndr_err;
+
+ sid = dom_sid_parse_talloc(ctx, (char *)val->data);
+ if (sid == NULL) {
+ return out;
+ }
+
+ ndr_err = ndr_push_struct_blob(&out, ctx,
+ NULL,
+ sid, (ndr_push_flags_fn_t)ndr_push_dom_sid);
+ talloc_free(sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return out;
+ }
+
+ return out;
+}
+
+/* Decode an objectSid to a sambaSID. */
+static struct ldb_val decode_sid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out = data_blob(NULL, 0);
+ struct dom_sid *sid;
+ enum ndr_err_code ndr_err;
+
+ sid = talloc(ctx, struct dom_sid);
+ if (sid == NULL) {
+ return out;
+ }
+
+ ndr_err = ndr_pull_struct_blob(val, sid, NULL, sid,
+ (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ goto done;
+ }
+
+ out.data = (uint8_t *)dom_sid_string(ctx, sid);
+ if (out.data == NULL) {
+ goto done;
+ }
+ out.length = strlen((const char *)out.data);
+
+done:
+ talloc_free(sid);
+ return out;
+}
+
+/* Convert 16 bytes to 32 hex digits. */
+static struct ldb_val bin2hex(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out;
+ struct samr_Password pwd;
+ if (val->length != sizeof(pwd.hash)) {
+ return data_blob(NULL, 0);
+ }
+ memcpy(pwd.hash, val->data, sizeof(pwd.hash));
+ out = data_blob_string_const(smbpasswd_sethexpwd(ctx, &pwd, 0));
+ if (!out.data) {
+ return data_blob(NULL, 0);
+ }
+ return out;
+}
+
+/* Convert 32 hex digits to 16 bytes. */
+static struct ldb_val hex2bin(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out;
+ struct samr_Password *pwd;
+ pwd = smbpasswd_gethexpwd(ctx, (const char *)val->data);
+ if (!pwd) {
+ return data_blob(NULL, 0);
+ }
+ out = data_blob_talloc(ctx, pwd->hash, sizeof(pwd->hash));
+ return out;
+}
+
+const struct ldb_map_objectclass samba3_objectclasses[] = {
+ {
+ .local_name = "user",
+ .remote_name = "posixAccount",
+ .base_classes = { "top", NULL },
+ .musts = { "cn", "uid", "uidNumber", "gidNumber", "homeDirectory", NULL },
+ .mays = { "userPassword", "loginShell", "gecos", "description", NULL },
+ },
+ {
+ .local_name = "group",
+ .remote_name = "posixGroup",
+ .base_classes = { "top", NULL },
+ .musts = { "cn", "gidNumber", NULL },
+ .mays = { "userPassword", "memberUid", "description", NULL },
+ },
+ {
+ .local_name = "group",
+ .remote_name = "sambaGroupMapping",
+ .base_classes = { "top", "posixGroup", NULL },
+ .musts = { "gidNumber", "sambaSID", "sambaGroupType", NULL },
+ .mays = { "displayName", "description", "sambaSIDList", NULL },
+ },
+ {
+ .local_name = "user",
+ .remote_name = "sambaSAMAccount",
+ .base_classes = { "top", "posixAccount", NULL },
+ .musts = { "uid", "sambaSID", NULL },
+ .mays = { "cn", "sambaLMPassword", "sambaNTPassword",
+ "sambaPwdLastSet", "sambaLogonTime", "sambaLogoffTime",
+ "sambaKickoffTime", "sambaPwdCanChange", "sambaPwdMustChange",
+ "sambaAcctFlags", "displayName", "sambaHomePath", "sambaHomeDrive",
+ "sambaLogonScript", "sambaProfilePath", "description", "sambaUserWorkstations",
+ "sambaPrimaryGroupSID", "sambaDomainName", "sambaMungedDial",
+ "sambaBadPasswordCount", "sambaBadPasswordTime",
+ "sambaPasswordHistory", "sambaLogonHours", NULL }
+
+ },
+ {
+ .local_name = "domain",
+ .remote_name = "sambaDomain",
+ .base_classes = { "top", NULL },
+ .musts = { "sambaDomainName", "sambaSID", NULL },
+ .mays = { "sambaNextRid", "sambaNextGroupRid", "sambaNextUserRid", "sambaAlgorithmicRidBase", NULL },
+ },
+ { NULL, NULL }
+};
+
+const struct ldb_map_attribute samba3_attributes[] =
+{
+ /* sambaNextRid -> nextRid */
+ {
+ .local_name = "nextRid",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaNextRid",
+ },
+ },
+ },
+
+ /* sambaBadPasswordTime -> badPasswordtime*/
+ {
+ .local_name = "badPasswordTime",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaBadPasswordTime",
+ },
+ },
+ },
+
+ /* sambaLMPassword -> lmPwdHash*/
+ {
+ .local_name = "dBCSPwd",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "sambaLMPassword",
+ .convert_local = bin2hex,
+ .convert_remote = hex2bin,
+ },
+ },
+ },
+
+ /* sambaGroupType -> groupType */
+ {
+ .local_name = "groupType",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaGroupType",
+ },
+ },
+ },
+
+ /* sambaNTPassword -> ntPwdHash*/
+ {
+ .local_name = "ntpwdhash",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "sambaNTPassword",
+ .convert_local = bin2hex,
+ .convert_remote = hex2bin,
+ },
+ },
+ },
+
+ /* sambaPrimaryGroupSID -> primaryGroupID */
+ {
+ .local_name = "primaryGroupID",
+ .type = MAP_GENERATE,
+ .u = {
+ .generate = {
+ .remote_names = { "sambaPrimaryGroupSID", NULL },
+ .generate_local = generate_primaryGroupID,
+ .generate_remote = generate_sambaPrimaryGroupSID,
+ },
+ },
+ },
+
+ /* sambaBadPasswordCount -> badPwdCount */
+ {
+ .local_name = "badPwdCount",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaBadPasswordCount",
+ },
+ },
+ },
+
+ /* sambaLogonTime -> lastLogon*/
+ {
+ .local_name = "lastLogon",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaLogonTime",
+ },
+ },
+ },
+
+ /* sambaLogoffTime -> lastLogoff*/
+ {
+ .local_name = "lastLogoff",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaLogoffTime",
+ },
+ },
+ },
+
+ /* uid -> unixName */
+ {
+ .local_name = "unixName",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "uid",
+ },
+ },
+ },
+
+ /* displayName -> name */
+ {
+ .local_name = "name",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "displayName",
+ },
+ },
+ },
+
+ /* cn */
+ {
+ .local_name = "cn",
+ .type = MAP_KEEP,
+ },
+
+ /* sAMAccountName -> cn */
+ {
+ .local_name = "sAMAccountName",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "uid",
+ .convert_remote = convert_uid_samaccount,
+ },
+ },
+ },
+
+ /* objectCategory */
+ {
+ .local_name = "objectCategory",
+ .type = MAP_IGNORE,
+ },
+
+ /* objectGUID */
+ {
+ .local_name = "objectGUID",
+ .type = MAP_IGNORE,
+ },
+
+ /* objectVersion */
+ {
+ .local_name = "objectVersion",
+ .type = MAP_IGNORE,
+ },
+
+ /* codePage */
+ {
+ .local_name = "codePage",
+ .type = MAP_IGNORE,
+ },
+
+ /* dNSHostName */
+ {
+ .local_name = "dNSHostName",
+ .type = MAP_IGNORE,
+ },
+
+
+ /* dnsDomain */
+ {
+ .local_name = "dnsDomain",
+ .type = MAP_IGNORE,
+ },
+
+ /* dnsRoot */
+ {
+ .local_name = "dnsRoot",
+ .type = MAP_IGNORE,
+ },
+
+ /* countryCode */
+ {
+ .local_name = "countryCode",
+ .type = MAP_IGNORE,
+ },
+
+ /* nTMixedDomain */
+ {
+ .local_name = "nTMixedDomain",
+ .type = MAP_IGNORE,
+ },
+
+ /* operatingSystem */
+ {
+ .local_name = "operatingSystem",
+ .type = MAP_IGNORE,
+ },
+
+ /* operatingSystemVersion */
+ {
+ .local_name = "operatingSystemVersion",
+ .type = MAP_IGNORE,
+ },
+
+
+ /* servicePrincipalName */
+ {
+ .local_name = "servicePrincipalName",
+ .type = MAP_IGNORE,
+ },
+
+ /* msDS-Behavior-Version */
+ {
+ .local_name = "msDS-Behavior-Version",
+ .type = MAP_IGNORE,
+ },
+
+ /* msDS-KeyVersionNumber */
+ {
+ .local_name = "msDS-KeyVersionNumber",
+ .type = MAP_IGNORE,
+ },
+
+ /* msDs-masteredBy */
+ {
+ .local_name = "msDs-masteredBy",
+ .type = MAP_IGNORE,
+ },
+
+ /* ou */
+ {
+ .local_name = "ou",
+ .type = MAP_KEEP,
+ },
+
+ /* dc */
+ {
+ .local_name = "dc",
+ .type = MAP_KEEP,
+ },
+
+ /* description */
+ {
+ .local_name = "description",
+ .type = MAP_KEEP,
+ },
+
+ /* sambaSID -> objectSid*/
+ {
+ .local_name = "objectSid",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "sambaSID",
+ .convert_local = decode_sid,
+ .convert_remote = encode_sid,
+ },
+ },
+ },
+
+ /* sambaPwdLastSet -> pwdLastSet */
+ {
+ .local_name = "pwdLastSet",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaPwdLastSet",
+ },
+ },
+ },
+
+ /* accountExpires */
+ {
+ .local_name = "accountExpires",
+ .type = MAP_IGNORE,
+ },
+
+ /* adminCount */
+ {
+ .local_name = "adminCount",
+ .type = MAP_IGNORE,
+ },
+
+ /* canonicalName */
+ {
+ .local_name = "canonicalName",
+ .type = MAP_IGNORE,
+ },
+
+ /* createTimestamp */
+ {
+ .local_name = "createTimestamp",
+ .type = MAP_IGNORE,
+ },
+
+ /* creationTime */
+ {
+ .local_name = "creationTime",
+ .type = MAP_IGNORE,
+ },
+
+ /* dMDLocation */
+ {
+ .local_name = "dMDLocation",
+ .type = MAP_IGNORE,
+ },
+
+ /* fSMORoleOwner */
+ {
+ .local_name = "fSMORoleOwner",
+ .type = MAP_IGNORE,
+ },
+
+ /* forceLogoff */
+ {
+ .local_name = "forceLogoff",
+ .type = MAP_IGNORE,
+ },
+
+ /* instanceType */
+ {
+ .local_name = "instanceType",
+ .type = MAP_IGNORE,
+ },
+
+ /* invocationId */
+ {
+ .local_name = "invocationId",
+ .type = MAP_IGNORE,
+ },
+
+ /* isCriticalSystemObject */
+ {
+ .local_name = "isCriticalSystemObject",
+ .type = MAP_IGNORE,
+ },
+
+ /* localPolicyFlags */
+ {
+ .local_name = "localPolicyFlags",
+ .type = MAP_IGNORE,
+ },
+
+ /* lockOutObservationWindow */
+ {
+ .local_name = "lockOutObservationWindow",
+ .type = MAP_IGNORE,
+ },
+
+ /* lockoutDuration */
+ {
+ .local_name = "lockoutDuration",
+ .type = MAP_IGNORE,
+ },
+
+ /* lockoutThreshold */
+ {
+ .local_name = "lockoutThreshold",
+ .type = MAP_IGNORE,
+ },
+
+ /* logonCount */
+ {
+ .local_name = "logonCount",
+ .type = MAP_IGNORE,
+ },
+
+ /* masteredBy */
+ {
+ .local_name = "masteredBy",
+ .type = MAP_IGNORE,
+ },
+
+ /* maxPwdAge */
+ {
+ .local_name = "maxPwdAge",
+ .type = MAP_IGNORE,
+ },
+
+ /* member */
+ {
+ .local_name = "member",
+ .type = MAP_IGNORE,
+ },
+
+ /* memberOf */
+ {
+ .local_name = "memberOf",
+ .type = MAP_IGNORE,
+ },
+
+ /* minPwdAge */
+ {
+ .local_name = "minPwdAge",
+ .type = MAP_IGNORE,
+ },
+
+ /* minPwdLength */
+ {
+ .local_name = "minPwdLength",
+ .type = MAP_IGNORE,
+ },
+
+ /* modifiedCount */
+ {
+ .local_name = "modifiedCount",
+ .type = MAP_IGNORE,
+ },
+
+ /* modifiedCountAtLastProm */
+ {
+ .local_name = "modifiedCountAtLastProm",
+ .type = MAP_IGNORE,
+ },
+
+ /* modifyTimestamp */
+ {
+ .local_name = "modifyTimestamp",
+ .type = MAP_IGNORE,
+ },
+
+ /* nCName */
+ {
+ .local_name = "nCName",
+ .type = MAP_IGNORE,
+ },
+
+ /* nETBIOSName */
+ {
+ .local_name = "nETBIOSName",
+ .type = MAP_IGNORE,
+ },
+
+ /* oEMInformation */
+ {
+ .local_name = "oEMInformation",
+ .type = MAP_IGNORE,
+ },
+
+ /* privilege */
+ {
+ .local_name = "privilege",
+ .type = MAP_IGNORE,
+ },
+
+ /* pwdHistoryLength */
+ {
+ .local_name = "pwdHistoryLength",
+ .type = MAP_IGNORE,
+ },
+
+ /* pwdProperties */
+ {
+ .local_name = "pwdProperties",
+ .type = MAP_IGNORE,
+ },
+
+ /* rIDAvailablePool */
+ {
+ .local_name = "rIDAvailablePool",
+ .type = MAP_IGNORE,
+ },
+
+ /* revision */
+ {
+ .local_name = "revision",
+ .type = MAP_IGNORE,
+ },
+
+ /* ridManagerReference */
+ {
+ .local_name = "ridManagerReference",
+ .type = MAP_IGNORE,
+ },
+
+ /* sAMAccountType */
+ {
+ .local_name = "sAMAccountType",
+ .type = MAP_IGNORE,
+ },
+
+ /* sPNMappings */
+ {
+ .local_name = "sPNMappings",
+ .type = MAP_IGNORE,
+ },
+
+ /* serverReference */
+ {
+ .local_name = "serverReference",
+ .type = MAP_IGNORE,
+ },
+
+ /* serverState */
+ {
+ .local_name = "serverState",
+ .type = MAP_IGNORE,
+ },
+
+ /* showInAdvancedViewOnly */
+ {
+ .local_name = "showInAdvancedViewOnly",
+ .type = MAP_IGNORE,
+ },
+
+ /* subRefs */
+ {
+ .local_name = "subRefs",
+ .type = MAP_IGNORE,
+ },
+
+ /* systemFlags */
+ {
+ .local_name = "systemFlags",
+ .type = MAP_IGNORE,
+ },
+
+ /* uASCompat */
+ {
+ .local_name = "uASCompat",
+ .type = MAP_IGNORE,
+ },
+
+ /* uSNChanged */
+ {
+ .local_name = "uSNChanged",
+ .type = MAP_IGNORE,
+ },
+
+ /* uSNCreated */
+ {
+ .local_name = "uSNCreated",
+ .type = MAP_IGNORE,
+ },
+
+ /* userPassword */
+ {
+ .local_name = "userPassword",
+ .type = MAP_IGNORE,
+ },
+
+ /* userAccountControl */
+ {
+ .local_name = "userAccountControl",
+ .type = MAP_IGNORE,
+ },
+
+ /* whenChanged */
+ {
+ .local_name = "whenChanged",
+ .type = MAP_IGNORE,
+ },
+
+ /* whenCreated */
+ {
+ .local_name = "whenCreated",
+ .type = MAP_IGNORE,
+ },
+
+ /* uidNumber */
+ {
+ .local_name = "unixName",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "uidNumber",
+ .convert_local = lookup_uid,
+ },
+ },
+ },
+
+ /* gidNumber. Perhaps make into generate so we can distinguish between
+ * groups and accounts? */
+ {
+ .local_name = "unixName",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "gidNumber",
+ .convert_local = lookup_gid,
+ },
+ },
+ },
+
+ /* homeDirectory */
+ {
+ .local_name = "unixName",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "homeDirectory",
+ .convert_local = lookup_homedir,
+ },
+ },
+ },
+ {
+ .local_name = NULL,
+ }
+};
+
+/* the context init function */
+static int samba3sam_init(struct ldb_module *module)
+{
+ int ret;
+
+ ret = ldb_map_init(module, samba3_attributes, samba3_objectclasses, NULL, NULL, "samba3sam");
+ if (ret != LDB_SUCCESS)
+ return ret;
+
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_samba3sam_module_ops = {
+ LDB_MAP_OPS
+ .name = "samba3sam",
+ .init_context = samba3sam_init,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c
new file mode 100644
index 0000000000..bd491bd011
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/samldb.c
@@ -0,0 +1,825 @@
+/*
+ SAM ldb module
+
+ Copyright (C) Simo Sorce 2004
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+
+ * NOTICE: this module is NOT released under the GNU LGPL license as
+ * other ldb code. This module is release under the GNU GPL v3 or
+ * later license.
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb samldb module
+ *
+ * Description: add embedded user/group creation functionality
+ *
+ * Author: Simo Sorce
+ */
+
+#include "includes.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "util/util_ldb.h"
+
+int samldb_notice_sid(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, const struct dom_sid *sid);
+
+static bool samldb_msg_add_sid(struct ldb_module *module, struct ldb_message *msg, const char *name, const struct dom_sid *sid)
+{
+ struct ldb_val v;
+ enum ndr_err_code ndr_err;
+
+ ndr_err = ndr_push_struct_blob(&v, msg, NULL, sid,
+ (ndr_push_flags_fn_t)ndr_push_dom_sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return false;
+ }
+ return (ldb_msg_add_value(msg, name, &v, NULL) == 0);
+}
+
+/*
+ allocate a new id, attempting to do it atomically
+ return 0 on failure, the id on success
+*/
+static int samldb_set_next_rid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn, uint32_t old_id, uint32_t new_id)
+{
+ struct ldb_message msg;
+ int ret;
+ struct ldb_val vals[2];
+ struct ldb_message_element els[2];
+
+ if (new_id == 0) {
+ /* out of IDs ! */
+ ldb_set_errstring(ldb, "Are we out of valid IDs ?\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* we do a delete and add as a single operation. That prevents
+ a race, in case we are not actually on a transaction db */
+ ZERO_STRUCT(msg);
+ msg.dn = ldb_dn_copy(mem_ctx, dn);
+ if (!msg.dn) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ msg.num_elements = 2;
+ msg.elements = els;
+
+ els[0].num_values = 1;
+ els[0].values = &vals[0];
+ els[0].flags = LDB_FLAG_MOD_DELETE;
+ els[0].name = talloc_strdup(mem_ctx, "nextRid");
+ if (!els[0].name) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ els[1].num_values = 1;
+ els[1].values = &vals[1];
+ els[1].flags = LDB_FLAG_MOD_ADD;
+ els[1].name = els[0].name;
+
+ vals[0].data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", old_id);
+ if (!vals[0].data) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ vals[0].length = strlen((char *)vals[0].data);
+
+ vals[1].data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", new_id);
+ if (!vals[1].data) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ vals[1].length = strlen((char *)vals[1].data);
+
+ ret = ldb_modify(ldb, &msg);
+ return ret;
+}
+
+/*
+ allocate a new id, attempting to do it atomically
+ return 0 on failure, the id on success
+*/
+static int samldb_find_next_rid(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn, uint32_t *old_rid)
+{
+ const char * const attrs[2] = { "nextRid", NULL };
+ struct ldb_result *res = NULL;
+ int ret;
+ const char *str;
+
+ ret = ldb_search(module->ldb, dn, LDB_SCOPE_BASE, "nextRid=*", attrs, &res);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (res->count != 1) {
+ talloc_free(res);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ str = ldb_msg_find_attr_as_string(res->msgs[0], "nextRid", NULL);
+ if (str == NULL) {
+ ldb_asprintf_errstring(module->ldb,
+ "attribute nextRid not found in %s\n",
+ ldb_dn_get_linearized(dn));
+ talloc_free(res);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *old_rid = strtol(str, NULL, 0);
+ talloc_free(res);
+ return LDB_SUCCESS;
+}
+
+static int samldb_allocate_next_rid(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn, const struct dom_sid *dom_sid,
+ struct dom_sid **new_sid)
+{
+ struct dom_sid *obj_sid;
+ uint32_t old_rid;
+ int ret;
+
+ ret = samldb_find_next_rid(module, mem_ctx, dn, &old_rid);
+ if (ret) {
+ return ret;
+ }
+
+ /* return the new object sid */
+ obj_sid = dom_sid_add_rid(mem_ctx, dom_sid, old_rid);
+
+ *new_sid = dom_sid_add_rid(mem_ctx, dom_sid, old_rid + 1);
+ if (!*new_sid) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = samldb_notice_sid(module, mem_ctx, *new_sid);
+ if (ret != 0) {
+ /* gah, there are conflicting sids.
+ * This is a critical situation it means that someone messed up with
+ * the DB and nextRid is not returning free RIDs, report an error
+ * and refuse to create any user until the problem is fixed */
+ ldb_asprintf_errstring(module->ldb,
+ "Critical Error: unconsistent DB, unable to retireve an unique RID to generate a new SID: %s",
+ ldb_errstring(module->ldb));
+ return ret;
+ }
+ return ret;
+}
+
+/* search the domain related to the provided dn
+ allocate a new RID for the domain
+ return the new sid string
+*/
+static int samldb_get_new_sid(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, struct ldb_dn *obj_dn,
+ struct ldb_dn *dom_dn,
+ struct dom_sid **sid)
+{
+ const char * const attrs[2] = { "objectSid", NULL };
+ struct ldb_result *res = NULL;
+ int ret;
+ struct dom_sid *dom_sid;
+
+ /* get the domain component part of the provided dn */
+
+ /* find the domain sid */
+
+ ret = ldb_search(module->ldb, dom_dn, LDB_SCOPE_BASE, "objectSid=*", attrs, &res);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_get_new_sid: error retrieving domain sid from %s: %s!\n",
+ ldb_dn_get_linearized(dom_dn),
+ ldb_errstring(module->ldb));
+ talloc_free(res);
+ return ret;
+ }
+
+ if (res->count != 1) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_get_new_sid: error retrieving domain sid from %s: not found!\n",
+ ldb_dn_get_linearized(dom_dn));
+ talloc_free(res);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ dom_sid = samdb_result_dom_sid(res, res->msgs[0], "objectSid");
+ if (dom_sid == NULL) {
+ ldb_set_errstring(module->ldb, "samldb_get_new_sid: error parsing domain sid!\n");
+ talloc_free(res);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ /* allocate a new Rid for the domain */
+ ret = samldb_allocate_next_rid(module, mem_ctx, dom_dn, dom_sid, sid);
+ if (ret != 0) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Failed to increment nextRid of %s: %s\n", ldb_dn_get_linearized(dom_dn), ldb_errstring(module->ldb));
+ talloc_free(res);
+ return ret;
+ }
+
+ talloc_free(res);
+
+ return ret;
+}
+
+/* If we are adding new users/groups, we need to update the nextRid
+ * attribute to be 'above' all incoming users RIDs. This tries to
+ * avoid clashes in future */
+
+int samldb_notice_sid(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, const struct dom_sid *sid)
+{
+ int ret;
+ struct ldb_dn *dom_dn;
+ struct dom_sid *dom_sid;
+ const char *attrs[] = { NULL };
+ struct ldb_result *dom_res;
+ struct ldb_result *res;
+ uint32_t old_rid;
+
+ /* find if this SID already exists */
+ ret = ldb_search_exp_fmt(module->ldb, mem_ctx, &res,
+ NULL, LDB_SCOPE_SUBTREE, attrs,
+ "(objectSid=%s)", ldap_encode_ndr_dom_sid(mem_ctx, sid));
+ if (ret == LDB_SUCCESS) {
+ if (res->count > 0) {
+ talloc_free(res);
+ ldb_asprintf_errstring(module->ldb,
+ "Attempt to add record with SID %s rejected,"
+ " because this SID is already in the database",
+ dom_sid_string(mem_ctx, sid));
+ /* We have a duplicate SID, we must reject the add */
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ talloc_free(res);
+ } else {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_notice_sid: error searching to see if sid %s is in use: %s\n",
+ dom_sid_string(mem_ctx, sid),
+ ldb_errstring(module->ldb));
+ return ret;
+ }
+
+ dom_sid = dom_sid_dup(mem_ctx, sid);
+ if (!dom_sid) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* get the domain component part of the provided SID */
+ dom_sid->num_auths--;
+
+ /* find the domain DN */
+ ret = ldb_search_exp_fmt(module->ldb, mem_ctx, &dom_res,
+ NULL, LDB_SCOPE_SUBTREE, attrs,
+ "(&(objectSid=%s)(|(|(objectClass=domain)(objectClass=builtinDomain))(objectClass=samba4LocalDomain)))",
+ ldap_encode_ndr_dom_sid(mem_ctx, dom_sid));
+ if (ret == LDB_SUCCESS) {
+ if (dom_res->count == 0) {
+ talloc_free(dom_res);
+ /* This isn't an operation on a domain we know about, so nothing to update */
+ return LDB_SUCCESS;
+ }
+
+ if (dom_res->count > 1) {
+ talloc_free(dom_res);
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_notice_sid: error retrieving domain from sid: duplicate (found %d) domain: %s!\n",
+ dom_res->count, dom_sid_string(dom_res, dom_sid));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ } else {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_notice_sid: error retrieving domain from sid: %s: %s\n",
+ dom_sid_string(dom_res, dom_sid),
+ ldb_errstring(module->ldb));
+ return ret;
+ }
+
+ dom_dn = dom_res->msgs[0]->dn;
+
+ ret = samldb_find_next_rid(module, mem_ctx,
+ dom_dn, &old_rid);
+ if (ret) {
+ talloc_free(dom_res);
+ return ret;
+ }
+
+ if (old_rid <= sid->sub_auths[sid->num_auths - 1]) {
+ ret = samldb_set_next_rid(module->ldb, mem_ctx, dom_dn, old_rid,
+ sid->sub_auths[sid->num_auths - 1] + 1);
+ }
+ talloc_free(dom_res);
+ return ret;
+}
+
+static int samldb_handle_sid(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, struct ldb_message *msg2,
+ struct ldb_dn *parent_dn)
+{
+ int ret;
+
+ struct dom_sid *sid = samdb_result_dom_sid(mem_ctx, msg2, "objectSid");
+ if (sid == NULL) {
+ ret = samldb_get_new_sid(module, msg2, msg2->dn, parent_dn, &sid);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if ( ! samldb_msg_add_sid(module, msg2, "objectSid", sid)) {
+ talloc_free(sid);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ talloc_free(sid);
+ ret = LDB_SUCCESS;
+ } else {
+ ret = samldb_notice_sid(module, msg2, sid);
+ }
+ return ret;
+}
+
+static int samldb_generate_samAccountName(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dom_dn, char **name)
+{
+ const char *attrs[] = { NULL };
+ struct ldb_result *res;
+ int ret;
+
+ /* Format: $000000-000000000000 */
+
+ do {
+ *name = talloc_asprintf(mem_ctx, "$%.6X-%.6X%.6X", (unsigned int)generate_random(), (unsigned int)generate_random(), (unsigned int)generate_random());
+ /* TODO: Figure out exactly what this is meant to conflict with */
+ ret = ldb_search_exp_fmt(module->ldb,
+ mem_ctx, &res, dom_dn, LDB_SCOPE_SUBTREE, attrs,
+ "samAccountName=%s",
+ ldb_binary_encode_string(mem_ctx, *name));
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb, "samldb: Failure searching to determine if samAccountName %s is unique: %s",
+ *name, ldb_errstring(module->ldb));
+ return ret;
+ }
+
+ if (res->count == 0) {
+ talloc_free(res);
+ /* Great. There are no conflicting users/groups/etc */
+ return LDB_SUCCESS;
+ } else {
+ talloc_free(*name);
+ /* gah, there is a conflicting name, lets move around the loop again... */
+ }
+ } while (1);
+}
+
+static int samldb_fill_group_object(struct ldb_module *module, const struct ldb_message *msg,
+ struct ldb_message **ret_msg)
+{
+ int ret;
+ unsigned int group_type;
+ char *name;
+ struct ldb_message *msg2;
+ struct ldb_dn *dom_dn;
+ const char *rdn_name;
+ TALLOC_CTX *mem_ctx = talloc_new(msg);
+ const char *errstr;
+ if (!mem_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* build the new msg */
+ msg2 = ldb_msg_copy(mem_ctx, msg);
+ if (!msg2) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "samldb_fill_group_object: ldb_msg_copy failed!\n");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = samdb_copy_template(module->ldb, msg2,
+ "group",
+ &errstr);
+ if (ret != 0) {
+
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ rdn_name = ldb_dn_get_rdn_name(msg2->dn);
+
+ if (strcasecmp(rdn_name, "cn") != 0) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "samldb_fill_group_object: Bad RDN (%s) for group!\n", rdn_name);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ ret = samdb_search_for_parent_domain(module->ldb, mem_ctx, msg2->dn, &dom_dn, &errstr);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_fill_group_object: %s", errstr);
+ return ret;
+ }
+
+ /* Generate a random name, if no samAccountName was supplied */
+ if (ldb_msg_find_element(msg2, "samAccountName") == NULL) {
+ ret = samldb_generate_samAccountName(module, mem_ctx, dom_dn, &name);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ ret = samdb_find_or_add_attribute(module->ldb, msg2, "sAMAccountName", name);
+ if (ret) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+
+ if (ldb_msg_find_element(msg2, "sAMAccountType") != NULL) {
+ ldb_asprintf_errstring(module->ldb, "sAMAccountType must not be specified");
+ talloc_free(mem_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ group_type = samdb_result_uint(msg2, "groupType", 0);
+ if (group_type == 0) {
+ ldb_asprintf_errstring(module->ldb, "groupType invalid");
+ talloc_free(mem_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ } else {
+ unsigned int account_type = samdb_gtype2atype(group_type);
+ ret = samdb_msg_add_uint(module->ldb, msg2, msg2,
+ "sAMAccountType",
+ account_type);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* Manage SID allocation, conflicts etc */
+ ret = samldb_handle_sid(module, mem_ctx, msg2, dom_dn);
+
+ if (ret == LDB_SUCCESS) {
+ talloc_steal(msg, msg2);
+ *ret_msg = msg2;
+ }
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static int samldb_fill_user_or_computer_object(struct ldb_module *module, const struct ldb_message *msg, struct ldb_message **ret_msg)
+{
+ int ret;
+ char *name;
+ struct ldb_message *msg2;
+ struct ldb_dn *dom_dn;
+ const char *rdn_name;
+ TALLOC_CTX *mem_ctx = talloc_new(msg);
+ const char *errstr;
+ unsigned int user_account_control;
+ if (!mem_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* build the new msg */
+ msg2 = ldb_msg_copy(mem_ctx, msg);
+ if (!msg2) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "samldb_fill_user_or_computer_object: ldb_msg_copy failed!\n");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = samdb_copy_template(module->ldb, msg2,
+ "user",
+ &errstr);
+ if (ret) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_fill_user_or_computer_object: Error copying user template: %s\n",
+ errstr);
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ rdn_name = ldb_dn_get_rdn_name(msg2->dn);
+
+ if (strcasecmp(rdn_name, "cn") != 0) {
+ ldb_asprintf_errstring(module->ldb, "Bad RDN (%s=) for user/computer, should be CN=!\n", rdn_name);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ ret = samdb_search_for_parent_domain(module->ldb, mem_ctx, msg2->dn, &dom_dn, &errstr);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_fill_user_or_computer_object: %s", errstr);
+ return ret;
+ }
+
+ if (ldb_msg_find_element(msg2, "samAccountName") == NULL) {
+ ret = samldb_generate_samAccountName(module, mem_ctx, dom_dn, &name);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ ret = samdb_find_or_add_attribute(module->ldb, msg2, "sAMAccountName", name);
+ if (ret) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+
+ if (ldb_msg_find_element(msg2, "sAMAccountType") != NULL) {
+ ldb_asprintf_errstring(module->ldb, "sAMAccountType must not be specified");
+ talloc_free(mem_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ user_account_control = samdb_result_uint(msg2, "userAccountControl", 0);
+ if (user_account_control == 0) {
+ ldb_asprintf_errstring(module->ldb, "userAccountControl invalid");
+ talloc_free(mem_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ } else {
+ unsigned int account_type = samdb_uf2atype(user_account_control);
+ ret = samdb_msg_add_uint(module->ldb, msg2, msg2,
+ "sAMAccountType",
+ account_type);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* Manage SID allocation, conflicts etc */
+ ret = samldb_handle_sid(module, mem_ctx, msg2, dom_dn);
+
+ /* TODO: userAccountControl, badPwdCount, codePage, countryCode, badPasswordTime, lastLogoff, lastLogon, pwdLastSet, primaryGroupID, accountExpires, logonCount */
+
+ if (ret == 0) {
+ *ret_msg = msg2;
+ talloc_steal(msg, msg2);
+ }
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static int samldb_fill_foreignSecurityPrincipal_object(struct ldb_module *module, const struct ldb_message *msg,
+ struct ldb_message **ret_msg)
+{
+ struct ldb_message *msg2;
+ const char *rdn_name;
+ struct dom_sid *dom_sid;
+ struct dom_sid *sid;
+ const char *dom_attrs[] = { "name", NULL };
+ struct ldb_message **dom_msgs;
+ const char *errstr;
+ int ret;
+
+ TALLOC_CTX *mem_ctx = talloc_new(msg);
+ if (!mem_ctx) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* build the new msg */
+ msg2 = ldb_msg_copy(mem_ctx, msg);
+ if (!msg2) {
+ ldb_debug(module->ldb, LDB_DEBUG_FATAL, "samldb_fill_foreignSecurityPrincipal_object: ldb_msg_copy failed!\n");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = samdb_copy_template(module->ldb, msg2,
+ "ForeignSecurityPrincipal",
+ &errstr);
+ if (ret != 0) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_fill_foreignSecurityPrincipal_object: "
+ "Error copying template: %s",
+ errstr);
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ rdn_name = ldb_dn_get_rdn_name(msg2->dn);
+
+ if (strcasecmp(rdn_name, "cn") != 0) {
+ ldb_asprintf_errstring(module->ldb, "Bad RDN (%s=) for ForeignSecurityPrincipal, should be CN=!", rdn_name);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ sid = samdb_result_dom_sid(msg2, msg, "objectSid");
+ if (!sid) {
+ /* Slightly different for the foreign sids. We don't want
+ * domain SIDs ending up there, it would cause all sorts of
+ * pain */
+
+ sid = dom_sid_parse_talloc(msg2, (const char *)ldb_dn_get_rdn_val(msg2->dn)->data);
+ if (!sid) {
+ ldb_set_errstring(module->ldb, "No valid found SID in ForeignSecurityPrincipal CN!");
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ if ( ! samldb_msg_add_sid(module, msg2, "objectSid", sid)) {
+ talloc_free(sid);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ dom_sid = dom_sid_dup(mem_ctx, sid);
+ if (!dom_sid) {
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* get the domain component part of the provided SID */
+ dom_sid->num_auths--;
+
+ /* find the domain DN */
+
+ ret = gendb_search(module->ldb,
+ mem_ctx, NULL, &dom_msgs, dom_attrs,
+ "(&(objectSid=%s)(objectclass=domain))",
+ ldap_encode_ndr_dom_sid(mem_ctx, dom_sid));
+ if (ret >= 1) {
+ /* We don't really like the idea of foreign sids that are not foreign, but it happens */
+ const char *name = samdb_result_string(dom_msgs[0], "name", NULL);
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "NOTE (strange but valid): Adding foreign SID record with SID %s, but this domian (%s) is already in the database",
+ dom_sid_string(mem_ctx, sid), name);
+ } else if (ret == -1) {
+ ldb_asprintf_errstring(module->ldb,
+ "samldb_fill_foreignSecurityPrincipal_object: error searching for a domain with this sid: %s\n",
+ dom_sid_string(mem_ctx, dom_sid));
+ talloc_free(dom_msgs);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ /* This isn't an operation on a domain we know about, so just
+ * check for the SID, looking for duplicates via the common
+ * code */
+ ret = samldb_notice_sid(module, msg2, sid);
+ if (ret == 0) {
+ talloc_steal(msg, msg2);
+ *ret_msg = msg2;
+ }
+
+ return ret;
+}
+
+/* add_record */
+
+/*
+ * FIXME
+ *
+ * Actually this module is not async at all as it does a number of sync searches
+ * in the process. It still to be decided how to deal with it properly so it is
+ * left SYNC for now until we think of a good solution.
+ */
+
+static int samldb_add(struct ldb_module *module, struct ldb_request *req)
+{
+ const struct ldb_message *msg = req->op.add.message;
+ struct ldb_message *msg2 = NULL;
+ struct ldb_request *down_req;
+ int ret;
+
+ ldb_debug(module->ldb, LDB_DEBUG_TRACE, "samldb_add_record\n");
+
+ if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* is user or computer? */
+ if ((samdb_find_attribute(module->ldb, msg, "objectclass", "user") != NULL) ||
+ (samdb_find_attribute(module->ldb, msg, "objectclass", "computer") != NULL)) {
+ /* add all relevant missing objects */
+ ret = samldb_fill_user_or_computer_object(module, msg, &msg2);
+ if (ret) {
+ return ret;
+ }
+ }
+
+ /* is group? add all relevant missing objects */
+ if ( ! msg2 ) {
+ if (samdb_find_attribute(module->ldb, msg, "objectclass", "group") != NULL) {
+ ret = samldb_fill_group_object(module, msg, &msg2);
+ if (ret) {
+ return ret;
+ }
+ }
+ }
+
+ /* perhaps a foreignSecurityPrincipal? */
+ if ( ! msg2 ) {
+ if (samdb_find_attribute(module->ldb, msg, "objectclass", "foreignSecurityPrincipal") != NULL) {
+ ret = samldb_fill_foreignSecurityPrincipal_object(module, msg, &msg2);
+ if (ret) {
+ return ret;
+ }
+ }
+ }
+
+ if (msg2 == NULL) {
+ return ldb_next_request(module, req);
+ }
+
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+
+ down_req->op.add.message = talloc_steal(down_req, msg2);
+
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+ /* go on with the call chain */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+/* modify */
+static int samldb_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_message *msg;
+ struct ldb_message_element *el, *el2;
+ int ret;
+ unsigned int group_type, user_account_control, account_type;
+ if (ldb_dn_is_special(req->op.mod.message->dn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_msg_find_element(req->op.mod.message, "sAMAccountType") != NULL) {
+ ldb_asprintf_errstring(module->ldb, "sAMAccountType must not be specified");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ el = ldb_msg_find_element(req->op.mod.message, "groupType");
+ if (el && el->flags & (LDB_FLAG_MOD_ADD|LDB_FLAG_MOD_REPLACE) && el->num_values == 1) {
+ req->op.mod.message = msg = ldb_msg_copy_shallow(req, req->op.mod.message);
+
+ group_type = strtoul((const char *)el->values[0].data, NULL, 0);
+ account_type = samdb_gtype2atype(group_type);
+ ret = samdb_msg_add_uint(module->ldb, msg, msg,
+ "sAMAccountType",
+ account_type);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ el2 = ldb_msg_find_element(msg, "sAMAccountType");
+ el2->flags = LDB_FLAG_MOD_REPLACE;
+ }
+
+ el = ldb_msg_find_element(req->op.mod.message, "userAccountControl");
+ if (el && el->flags & (LDB_FLAG_MOD_ADD|LDB_FLAG_MOD_REPLACE) && el->num_values == 1) {
+ req->op.mod.message = msg = ldb_msg_copy_shallow(req, req->op.mod.message);
+
+ user_account_control = strtoul((const char *)el->values[0].data, NULL, 0);
+ account_type = samdb_uf2atype(user_account_control);
+ ret = samdb_msg_add_uint(module->ldb, msg, msg,
+ "sAMAccountType",
+ account_type);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ el2 = ldb_msg_find_element(msg, "sAMAccountType");
+ el2->flags = LDB_FLAG_MOD_REPLACE;
+ }
+ return ldb_next_request(module, req);
+}
+
+
+static int samldb_init(struct ldb_module *module)
+{
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_samldb_module_ops = {
+ .name = "samldb",
+ .init_context = samldb_init,
+ .add = samldb_add,
+ .modify = samldb_modify
+};
diff --git a/source4/dsdb/samdb/ldb_modules/schema_fsmo.c b/source4/dsdb/samdb/ldb_modules/schema_fsmo.c
new file mode 100644
index 0000000000..968b19c038
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/schema_fsmo.c
@@ -0,0 +1,413 @@
+/*
+ Unix SMB/CIFS mplementation.
+
+ The module that handles the Schema FSMO Role Owner
+ checkings, it also loads the dsdb_schema.
+
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+
+ 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 "lib/ldb/include/ldb.h"
+#include "lib/ldb/include/ldb_errors.h"
+#include "lib/ldb/include/ldb_private.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 "lib/util/dlinklist.h"
+#include "param/param.h"
+
+static int generate_objectClasses(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema);
+static int generate_attributeTypes(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema);
+static int generate_dITContentRules(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema);
+
+static const struct {
+ const char *attr;
+ int (*fn)(struct ldb_context *, struct ldb_message *, const struct dsdb_schema *);
+} generated_attrs[] = {
+ {
+ .attr = "objectClasses",
+ .fn = generate_objectClasses
+ },
+ {
+ .attr = "attributeTypes",
+ .fn = generate_attributeTypes
+ },
+ {
+ .attr = "dITContentRules",
+ .fn = generate_dITContentRules
+ }
+};
+
+struct schema_fsmo_private_data {
+ struct ldb_dn *aggregate_dn;
+};
+
+struct schema_fsmo_search_data {
+ struct schema_fsmo_private_data *module_context;
+ struct ldb_request *orig_req;
+};
+
+static int schema_fsmo_init(struct ldb_module *module)
+{
+ TALLOC_CTX *mem_ctx;
+ struct ldb_dn *schema_dn;
+ struct dsdb_schema *schema;
+ char *error_string = NULL;
+ int ret;
+ struct schema_fsmo_private_data *data;
+
+ schema_dn = samdb_schema_dn(module->ldb);
+ if (!schema_dn) {
+ ldb_reset_err_string(module->ldb);
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "schema_fsmo_init: no schema dn present: (skip schema loading)\n");
+ return ldb_next_init(module);
+ }
+
+ data = talloc(module, struct schema_fsmo_private_data);
+ if (data == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Check to see if this is a result on the CN=Aggregate schema */
+ data->aggregate_dn = ldb_dn_copy(data, schema_dn);
+ if (!ldb_dn_add_child_fmt(data->aggregate_dn, "CN=Aggregate")) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ module->private_data = data;
+
+ if (dsdb_get_schema(module->ldb)) {
+ return ldb_next_init(module);
+ }
+
+ mem_ctx = talloc_new(module);
+ if (!mem_ctx) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = dsdb_schema_from_schema_dn(mem_ctx, module->ldb,
+ lp_iconv_convenience(ldb_get_opaque(module->ldb, "loadparm")),
+ schema_dn, &schema, &error_string);
+
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ldb_reset_err_string(module->ldb);
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "schema_fsmo_init: no schema head present: (skip schema loading)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "schema_fsmo_init: dsdb_schema load failed: %s",
+ error_string);
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* dsdb_set_schema() steal schema into the ldb_context */
+ ret = dsdb_set_schema(module->ldb, schema);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "schema_fsmo_init: dsdb_set_schema() failed: %d:%s",
+ ret, ldb_strerror(ret));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ talloc_free(mem_ctx);
+ return ldb_next_init(module);
+}
+
+static int schema_fsmo_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct dsdb_schema *schema;
+ const char *attributeID = NULL;
+ const char *governsID = NULL;
+ const char *oid_attr = NULL;
+ const char *oid = NULL;
+ uint32_t id32;
+ WERROR status;
+
+ schema = dsdb_get_schema(module->ldb);
+ if (!schema) {
+ return ldb_next_request(module, req);
+ }
+
+ if (!schema->fsmo.we_are_master) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_ERROR,
+ "schema_fsmo_add: we are not master: reject request\n");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ attributeID = samdb_result_string(req->op.add.message, "attributeID", NULL);
+ governsID = samdb_result_string(req->op.add.message, "governsID", NULL);
+
+ if (attributeID) {
+ oid_attr = "attributeID";
+ oid = attributeID;
+ } else if (governsID) {
+ oid_attr = "governsID";
+ oid = governsID;
+ }
+
+ if (!oid) {
+ return ldb_next_request(module, req);
+ }
+
+ status = dsdb_map_oid2int(schema, oid, &id32);
+ if (W_ERROR_IS_OK(status)) {
+ return ldb_next_request(module, req);
+ } else if (!W_ERROR_EQUAL(WERR_DS_NO_MSDS_INTID, status)) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_ERROR,
+ "schema_fsmo_add: failed to map %s[%s]: %s\n",
+ oid_attr, oid, win_errstr(status));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ status = dsdb_create_prefix_mapping(module->ldb, schema, oid);
+ if (!W_ERROR_IS_OK(status)) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_ERROR,
+ "schema_fsmo_add: failed to create prefix mapping for %s[%s]: %s\n",
+ oid_attr, oid, win_errstr(status));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ return ldb_next_request(module, req);
+}
+
+static int schema_fsmo_extended(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_dn *schema_dn;
+ struct dsdb_schema *schema;
+ char *error_string = NULL;
+ int ret;
+ TALLOC_CTX *mem_ctx;
+
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID) != 0) {
+ return ldb_next_request(module, req);
+ }
+
+ schema_dn = samdb_schema_dn(module->ldb);
+ if (!schema_dn) {
+ ldb_reset_err_string(module->ldb);
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "schema_fsmo_extended: no schema dn present: (skip schema loading)\n");
+ return ldb_next_request(module, req);
+ }
+
+ mem_ctx = talloc_new(module);
+ if (!mem_ctx) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = dsdb_schema_from_schema_dn(mem_ctx, module->ldb,
+ lp_iconv_convenience(ldb_get_opaque(module->ldb, "loadparm")),
+ schema_dn, &schema, &error_string);
+
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ldb_reset_err_string(module->ldb);
+ ldb_debug(module->ldb, LDB_DEBUG_WARNING,
+ "schema_fsmo_extended: no schema head present: (skip schema loading)\n");
+ talloc_free(mem_ctx);
+ return ldb_next_request(module, req);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(module->ldb,
+ "schema_fsmo_extended: dsdb_schema load failed: %s",
+ error_string);
+ talloc_free(mem_ctx);
+ return ldb_next_request(module, req);
+ }
+
+ /* Replace the old schema*/
+ ret = dsdb_set_schema(module->ldb, schema);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "schema_fsmo_extended: dsdb_set_schema() failed: %d:%s",
+ ret, ldb_strerror(ret));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ talloc_free(mem_ctx);
+ return LDB_SUCCESS;
+}
+
+static int generate_objectClasses(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema)
+{
+ const struct dsdb_class *class;
+ int ret;
+
+ for (class = schema->classes; class; class = class->next) {
+ ret = ldb_msg_add_string(msg, "objectClasses", schema_class_to_description(msg, class));
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ return LDB_SUCCESS;
+}
+static int generate_attributeTypes(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema)
+{
+ const struct dsdb_attribute *attribute;
+ int ret;
+
+ for (attribute = schema->attributes; attribute; attribute = attribute->next) {
+ ret = ldb_msg_add_string(msg, "attributeTypes", schema_attribute_to_description(msg, attribute));
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+static int generate_dITContentRules(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema)
+{
+ const struct dsdb_class *class;
+ int ret;
+
+ for (class = schema->classes; class; class = class->next) {
+ if (class->auxiliaryClass || class->systemAuxiliaryClass) {
+ char *ditcontentrule = schema_class_to_dITContentRule(msg, class, schema);
+ if (!ditcontentrule) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = ldb_msg_add_steal_string(msg, "dITContentRules", ditcontentrule);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+ return 0;
+}
+
+
+
+/* Add objectClasses, attributeTypes and dITContentRules from the
+ schema object (they are not stored in the database)
+ */
+static int schema_fsmo_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb);
+ struct schema_fsmo_search_data *search_data = talloc_get_type(context, struct schema_fsmo_search_data);
+ struct ldb_request *orig_req = search_data->orig_req;
+ TALLOC_CTX *mem_ctx;
+ int i, ret;
+
+ /* Only entries are interesting, and we handle the case of the parent seperatly */
+ if (ares->type != LDB_REPLY_ENTRY) {
+ return orig_req->callback(ldb, orig_req->context, ares);
+ }
+
+ if (ldb_dn_compare(ares->message->dn, search_data->module_context->aggregate_dn) != 0) {
+ talloc_free(mem_ctx);
+ return orig_req->callback(ldb, orig_req->context, ares);
+ }
+
+ mem_ctx = talloc_new(ares);
+ if (!mem_ctx) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; i < ARRAY_SIZE(generated_attrs); i++) {
+ if (ldb_attr_in_list(orig_req->op.search.attrs, generated_attrs[i].attr)) {
+ ret = generated_attrs[i].fn(ldb, ares->message, schema);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ talloc_free(mem_ctx);
+ return orig_req->callback(ldb, orig_req->context, ares);
+}
+
+/* search */
+static int schema_fsmo_search(struct ldb_module *module, struct ldb_request *req)
+{
+ int i, ret;
+ struct schema_fsmo_search_data *search_context;
+ struct ldb_request *down_req;
+ struct dsdb_schema *schema = dsdb_get_schema(module->ldb);
+
+ if (!schema || !module->private_data) {
+ /* If there is no schema, there is little we can do */
+ return ldb_next_request(module, req);
+ }
+ for (i=0; i < ARRAY_SIZE(generated_attrs); i++) {
+ if (ldb_attr_in_list(req->op.search.attrs, generated_attrs[i].attr)) {
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(generated_attrs)) {
+ /* No request for a generated attr found, nothing to
+ * see here, move along... */
+ return ldb_next_request(module, req);
+ }
+
+ search_context = talloc(req, struct schema_fsmo_search_data);
+ if (!search_context) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ down_req = talloc(req, struct ldb_request);
+ if (!down_req) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *down_req = *req;
+ search_context->orig_req = req;
+ search_context->module_context = talloc_get_type(module->private_data, struct schema_fsmo_private_data);
+ down_req->context = search_context;
+
+ down_req->callback = schema_fsmo_search_callback;
+
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+ return ret;
+}
+
+
+_PUBLIC_ const struct ldb_module_ops ldb_schema_fsmo_module_ops = {
+ .name = "schema_fsmo",
+ .init_context = schema_fsmo_init,
+ .add = schema_fsmo_add,
+ .extended = schema_fsmo_extended,
+ .search = schema_fsmo_search
+};
diff --git a/source4/dsdb/samdb/ldb_modules/show_deleted.c b/source4/dsdb/samdb/ldb_modules/show_deleted.c
new file mode 100644
index 0000000000..361cf226dc
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/show_deleted.c
@@ -0,0 +1,201 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2005
+ Copyright (C) Stefa Metzmacher <metze@samba.org> 2007
+
+ ** NOTE! The following LGPL license applies to the ldb
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb deleted objects control module
+ *
+ * Description: this module hides deleted objects, and returns them if the control is there
+ *
+ * Author: Stefan Metzmacher
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_private.h"
+#include "dsdb/samdb/samdb.h"
+
+/* search */
+struct show_deleted_search_request {
+
+ struct ldb_module *module;
+ void *up_context;
+ int (*up_callback)(struct ldb_context *, void *, struct ldb_reply *);
+
+ bool remove_from_msg;
+};
+
+static int show_deleted_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct show_deleted_search_request *ar;
+
+ ar = talloc_get_type(context, struct show_deleted_search_request);
+
+ if (ares->type == LDB_REPLY_ENTRY) {
+ bool isDeleted;
+
+ isDeleted = ldb_msg_find_attr_as_bool(ares->message, "isDeleted", false);
+
+ if (isDeleted) {
+ goto skip_deleted;
+ }
+
+ if (ar->remove_from_msg) {
+ ldb_msg_remove_attr(ares->message, "isDeleted");
+ }
+ }
+
+ return ar->up_callback(ldb, ar->up_context, ares);
+
+skip_deleted:
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+static int show_deleted_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_control *control;
+ struct ldb_control **saved_controls;
+ struct show_deleted_search_request *ar;
+ struct ldb_request *down_req;
+ char **new_attrs;
+ uint32_t num_attrs = 0;
+ uint32_t i;
+ int ret;
+
+ /* check if there's a show deleted control */
+ control = ldb_request_get_control(req, LDB_CONTROL_SHOW_DELETED_OID);
+
+ /* copy the request for modification */
+ down_req = talloc(req, struct ldb_request);
+ if (down_req == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* copy the request */
+ *down_req = *req;
+
+ /* if a control is there remove if from the modified request */
+ if (control && !save_controls(control, down_req, &saved_controls)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* if we had a control, then just go on to the next request as we have nothing to hide */
+ if (control) {
+ goto next_request;
+ }
+
+ ar = talloc(down_req, struct show_deleted_search_request);
+ if (ar == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ar->module = module;
+ ar->up_context = req->context;
+ ar->up_callback = req->callback;
+ ar->remove_from_msg = true;
+
+ /* check if attrs only is specified, in that case check wether we need to modify them */
+ if (down_req->op.search.attrs) {
+ for (i=0; (down_req->op.search.attrs && down_req->op.search.attrs[i]); i++) {
+ num_attrs++;
+ if (strcasecmp(down_req->op.search.attrs[i], "*") == 0) {
+ ar->remove_from_msg = false;
+ } else if (strcasecmp(down_req->op.search.attrs[i], "isDeleted") == 0) {
+ ar->remove_from_msg = false;
+ }
+ }
+ } else {
+ ar->remove_from_msg = false;
+ }
+
+ if (ar->remove_from_msg) {
+ new_attrs = talloc_array(down_req, char *, num_attrs + 2);
+ if (!new_attrs) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ for (i=0; i < num_attrs; i++) {
+ new_attrs[i] = discard_const_p(char, down_req->op.search.attrs[i]);
+ }
+ new_attrs[i] = talloc_strdup(new_attrs, "isDeleted");
+ if (!new_attrs[i]) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ new_attrs[i+1] = NULL;
+ down_req->op.search.attrs = (const char * const *)new_attrs;
+ }
+
+ down_req->context = ar;
+ down_req->callback = show_deleted_search_callback;
+ ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
+
+next_request:
+ /* perform the search */
+ ret = ldb_next_request(module, down_req);
+
+ /* do not free down_req as the call results may be linked to it,
+ * it will be freed when the upper level request get freed */
+ if (ret == LDB_SUCCESS) {
+ req->handle = down_req->handle;
+ }
+
+ return ret;
+}
+
+static int show_deleted_init(struct ldb_module *module)
+{
+ struct ldb_request *req;
+ int ret;
+
+ req = talloc(module, struct ldb_request);
+ if (req == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ req->operation = LDB_REQ_REGISTER_CONTROL;
+ req->op.reg_control.oid = LDB_CONTROL_SHOW_DELETED_OID;
+ req->controls = NULL;
+
+ ret = ldb_request(module->ldb, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(module->ldb, LDB_DEBUG_ERROR, "show_deleted: Unable to register control with rootdse!\n");
+ talloc_free(req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_free(req);
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_show_deleted_module_ops = {
+ .name = "show_deleted",
+ .search = show_deleted_search,
+ .init_context = show_deleted_init
+};
diff --git a/source4/dsdb/samdb/ldb_modules/simple_ldap_map.c b/source4/dsdb/samdb/ldb_modules/simple_ldap_map.c
new file mode 100644
index 0000000000..8f92995145
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/simple_ldap_map.c
@@ -0,0 +1,716 @@
+/*
+ ldb database module
+
+ LDAP semantics mapping module
+
+ Copyright (C) Jelmer Vernooij 2005
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006
+
+ 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/>.
+*/
+
+/*
+ This module relies on ldb_map to do all the real work, but performs
+ some of the trivial mappings between AD semantics and that provided
+ by OpenLDAP and similar servers.
+*/
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_private.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/ldb_map/ldb_map.h"
+
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/ndr/libndr.h"
+#include "dsdb/samdb/samdb.h"
+
+static struct ldb_val encode_guid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct GUID guid;
+ NTSTATUS status = GUID_from_string((char *)val->data, &guid);
+ enum ndr_err_code ndr_err;
+ struct ldb_val out = data_blob(NULL, 0);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return out;
+ }
+ ndr_err = ndr_push_struct_blob(&out, ctx, NULL, &guid,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return out;
+ }
+
+ return out;
+}
+
+static struct ldb_val guid_always_string(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct GUID *guid;
+ struct ldb_val out = data_blob(NULL, 0);
+ if (val->length >= 32 && val->data[val->length] == '\0') {
+ ldb_handler_copy(module->ldb, ctx, val, &out);
+ } else {
+ enum ndr_err_code ndr_err;
+
+ guid = talloc(ctx, struct GUID);
+ if (guid == NULL) {
+ return out;
+ }
+ ndr_err = ndr_pull_struct_blob(val, guid, NULL, guid,
+ (ndr_pull_flags_fn_t)ndr_pull_GUID);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(guid);
+ return out;
+ }
+ out = data_blob_string_const(GUID_string(ctx, guid));
+ talloc_free(guid);
+ }
+ return out;
+}
+
+static struct ldb_val encode_ns_guid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct GUID guid;
+ NTSTATUS status = NS_GUID_from_string((char *)val->data, &guid);
+ enum ndr_err_code ndr_err;
+ struct ldb_val out = data_blob(NULL, 0);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return out;
+ }
+ ndr_err = ndr_push_struct_blob(&out, ctx, NULL, &guid,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return out;
+ }
+
+ return out;
+}
+
+static struct ldb_val guid_ns_string(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out = data_blob(NULL, 0);
+ if (val->length >= 32 && val->data[val->length] == '\0') {
+ struct GUID guid;
+ GUID_from_string((char *)val->data, &guid);
+ out = data_blob_string_const(NS_GUID_string(ctx, &guid));
+ } else {
+ enum ndr_err_code ndr_err;
+ struct GUID *guid_p;
+ guid_p = talloc(ctx, struct GUID);
+ if (guid_p == NULL) {
+ return out;
+ }
+ ndr_err = ndr_pull_struct_blob(val, guid_p, NULL, guid_p,
+ (ndr_pull_flags_fn_t)ndr_pull_GUID);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(guid_p);
+ return out;
+ }
+ out = data_blob_string_const(NS_GUID_string(ctx, guid_p));
+ talloc_free(guid_p);
+ }
+ return out;
+}
+
+/* The backend holds binary sids, so just copy them back */
+static struct ldb_val val_copy(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out = data_blob(NULL, 0);
+ ldb_handler_copy(module->ldb, ctx, val, &out);
+
+ return out;
+}
+
+/* Ensure we always convert sids into binary, so the backend doesn't have to know about both forms */
+static struct ldb_val sid_always_binary(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out = data_blob(NULL, 0);
+ const struct ldb_schema_attribute *a = ldb_schema_attribute_by_name(module->ldb, "objectSid");
+
+ if (a->syntax->canonicalise_fn(module->ldb, ctx, val, &out) != LDB_SUCCESS) {
+ return data_blob(NULL, 0);
+ }
+
+ return out;
+}
+
+/* Ensure we always convert objectCategory into a DN */
+static struct ldb_val objectCategory_always_dn(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_dn *dn;
+ struct ldb_val out = data_blob(NULL, 0);
+ const struct ldb_schema_attribute *a = ldb_schema_attribute_by_name(module->ldb, "objectCategory");
+
+ dn = ldb_dn_from_ldb_val(ctx, module->ldb, val);
+ if (dn && ldb_dn_validate(dn)) {
+ talloc_free(dn);
+ return val_copy(module, ctx, val);
+ }
+ talloc_free(dn);
+
+ if (a->syntax->canonicalise_fn(module->ldb, ctx, val, &out) != LDB_SUCCESS) {
+ return data_blob(NULL, 0);
+ }
+
+ return out;
+}
+
+static struct ldb_val normalise_to_signed32(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ long long int signed_ll = strtoll((const char *)val->data, NULL, 10);
+ if (signed_ll >= 0x80000000LL) {
+ union {
+ int32_t signed_int;
+ uint32_t unsigned_int;
+ } u = {
+ .unsigned_int = strtoul((const char *)val->data, NULL, 10)
+ };
+
+ struct ldb_val out = data_blob_string_const(talloc_asprintf(ctx, "%d", u.signed_int));
+ return out;
+ }
+ return val_copy(module, ctx, val);
+}
+
+static struct ldb_val usn_to_entryCSN(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out;
+ unsigned long long usn = strtoull((const char *)val->data, NULL, 10);
+ time_t t = (usn >> 24);
+ out = data_blob_string_const(talloc_asprintf(ctx, "%s#%06x#00#000000", ldb_timestring(ctx, t), (unsigned int)(usn & 0xFFFFFF)));
+ return out;
+}
+
+static unsigned long long entryCSN_to_usn_int(TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ char *entryCSN = talloc_strdup(ctx, (const char *)val->data);
+ char *mod_per_sec;
+ time_t t;
+ unsigned long long usn;
+ char *p;
+ if (!entryCSN) {
+ return 0;
+ }
+ p = strchr(entryCSN, '#');
+ if (!p) {
+ return 0;
+ }
+ p[0] = '\0';
+ p++;
+ mod_per_sec = p;
+
+ p = strchr(p, '#');
+ if (!p) {
+ return 0;
+ }
+ p[0] = '\0';
+ p++;
+
+ usn = strtol(mod_per_sec, NULL, 16);
+
+ t = ldb_string_to_time(entryCSN);
+
+ usn = usn | ((unsigned long long)t <<24);
+ return usn;
+}
+
+static struct ldb_val entryCSN_to_usn(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out;
+ unsigned long long usn = entryCSN_to_usn_int(ctx, val);
+ out = data_blob_string_const(talloc_asprintf(ctx, "%lld", usn));
+ return out;
+}
+
+static struct ldb_val usn_to_timestamp(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out;
+ unsigned long long usn = strtoull((const char *)val->data, NULL, 10);
+ time_t t = (usn >> 24);
+ out = data_blob_string_const(ldb_timestring(ctx, t));
+ return out;
+}
+
+static struct ldb_val timestamp_to_usn(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_val out;
+ time_t t;
+ unsigned long long usn;
+
+ t = ldb_string_to_time((const char *)val->data);
+
+ usn = ((unsigned long long)t <<24);
+
+ out = data_blob_string_const(talloc_asprintf(ctx, "%lld", usn));
+ return out;
+}
+
+
+static const struct ldb_map_attribute entryuuid_attributes[] =
+{
+ /* objectGUID */
+ {
+ .local_name = "objectGUID",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "entryUUID",
+ .convert_local = guid_always_string,
+ .convert_remote = encode_guid,
+ },
+ },
+ },
+ /* invocationId */
+ {
+ .local_name = "invocationId",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "invocationId",
+ .convert_local = guid_always_string,
+ .convert_remote = encode_guid,
+ },
+ },
+ },
+ /* objectSid */
+ {
+ .local_name = "objectSid",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "objectSid",
+ .convert_local = sid_always_binary,
+ .convert_remote = val_copy,
+ },
+ },
+ },
+ {
+ .local_name = "name",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "samba4RDN"
+ }
+ }
+ },
+ {
+ .local_name = "whenCreated",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "createTimestamp"
+ }
+ }
+ },
+ {
+ .local_name = "whenChanged",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "modifyTimestamp"
+ }
+ }
+ },
+ {
+ .local_name = "objectClasses",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "samba4ObjectClasses"
+ }
+ }
+ },
+ {
+ .local_name = "dITContentRules",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "samba4DITContentRules"
+ }
+ }
+ },
+ {
+ .local_name = "attributeTypes",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "samba4AttributeTypes"
+ }
+ }
+ },
+ {
+ .local_name = "objectCategory",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "objectCategory",
+ .convert_local = objectCategory_always_dn,
+ .convert_remote = val_copy,
+ },
+ },
+ },
+ {
+ .local_name = "distinguishedName",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "entryDN"
+ }
+ }
+ },
+ {
+ .local_name = "groupType",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "groupType",
+ .convert_local = normalise_to_signed32,
+ .convert_remote = val_copy,
+ },
+ }
+ },
+ {
+ .local_name = "sAMAccountType",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "sAMAccountType",
+ .convert_local = normalise_to_signed32,
+ .convert_remote = val_copy,
+ },
+ }
+ },
+ {
+ .local_name = "usnChanged",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "entryCSN",
+ .convert_local = usn_to_entryCSN,
+ .convert_remote = entryCSN_to_usn
+ },
+ },
+ },
+ {
+ .local_name = "usnCreated",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "createTimestamp",
+ .convert_local = usn_to_timestamp,
+ .convert_remote = timestamp_to_usn,
+ },
+ },
+ },
+ {
+ .local_name = "*",
+ .type = MAP_KEEP,
+ },
+ {
+ .local_name = NULL,
+ }
+};
+
+/* This objectClass conflicts with builtin classes on OpenLDAP */
+const struct ldb_map_objectclass entryuuid_objectclasses[] =
+{
+ {
+ .local_name = "subSchema",
+ .remote_name = "samba4SubSchema"
+ },
+ {
+ .local_name = NULL
+ }
+};
+
+/* These things do not show up in wildcard searches in OpenLDAP, but
+ * we need them to show up in the AD-like view */
+static const char * const entryuuid_wildcard_attributes[] = {
+ "objectGUID",
+ "whenCreated",
+ "whenChanged",
+ "usnCreated",
+ "usnChanged",
+ "memberOf",
+ NULL
+};
+
+static const struct ldb_map_attribute nsuniqueid_attributes[] =
+{
+ /* objectGUID */
+ {
+ .local_name = "objectGUID",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "nsuniqueid",
+ .convert_local = guid_ns_string,
+ .convert_remote = encode_ns_guid,
+ },
+ },
+ },
+ /* objectSid */
+ {
+ .local_name = "objectSid",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "objectSid",
+ .convert_local = sid_always_binary,
+ .convert_remote = val_copy,
+ },
+ },
+ },
+ {
+ .local_name = "whenCreated",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "createTimestamp"
+ }
+ }
+ },
+ {
+ .local_name = "whenChanged",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "modifyTimestamp"
+ }
+ }
+ },
+ {
+ .local_name = "objectCategory",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "objectCategory",
+ .convert_local = objectCategory_always_dn,
+ .convert_remote = val_copy,
+ },
+ },
+ },
+ {
+ .local_name = "distinguishedName",
+ .type = MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "entryDN"
+ }
+ }
+ },
+ {
+ .local_name = "groupType",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "groupType",
+ .convert_local = normalise_to_signed32,
+ .convert_remote = val_copy,
+ },
+ }
+ },
+ {
+ .local_name = "sAMAccountType",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "sAMAccountType",
+ .convert_local = normalise_to_signed32,
+ .convert_remote = val_copy,
+ },
+ }
+ },
+ {
+ .local_name = "usnChanged",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "modifyTimestamp",
+ .convert_local = usn_to_timestamp,
+ .convert_remote = timestamp_to_usn,
+ },
+ },
+ },
+ {
+ .local_name = "usnCreated",
+ .type = MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "createTimestamp",
+ .convert_local = usn_to_timestamp,
+ .convert_remote = timestamp_to_usn,
+ },
+ },
+ },
+ {
+ .local_name = "*",
+ .type = MAP_KEEP,
+ },
+ {
+ .local_name = NULL,
+ }
+};
+
+/* These things do not show up in wildcard searches in OpenLDAP, but
+ * we need them to show up in the AD-like view */
+static const char * const nsuniqueid_wildcard_attributes[] = {
+ "objectGUID",
+ "whenCreated",
+ "whenChanged",
+ "usnCreated",
+ "usnChanged",
+ NULL
+};
+
+/* the context init function */
+static int entryuuid_init(struct ldb_module *module)
+{
+ int ret;
+ ret = ldb_map_init(module, entryuuid_attributes, entryuuid_objectclasses, entryuuid_wildcard_attributes, "samba4Top", NULL);
+ if (ret != LDB_SUCCESS)
+ return ret;
+
+ return ldb_next_init(module);
+}
+
+/* the context init function */
+static int nsuniqueid_init(struct ldb_module *module)
+{
+ int ret;
+ ret = ldb_map_init(module, nsuniqueid_attributes, NULL, nsuniqueid_wildcard_attributes, "extensibleObject", NULL);
+ if (ret != LDB_SUCCESS)
+ return ret;
+
+ return ldb_next_init(module);
+}
+
+static int get_seq(struct ldb_context *ldb, void *context,
+ struct ldb_reply *ares)
+{
+ unsigned long long *seq = (unsigned long long *)context;
+ if (ares->type == LDB_REPLY_ENTRY) {
+ struct ldb_message_element *el = ldb_msg_find_element(ares->message, "contextCSN");
+ if (el) {
+ *seq = entryCSN_to_usn_int(ares, &el->values[0]);
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int entryuuid_sequence_number(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ struct map_private *map_private;
+ struct entryuuid_private *entryuuid_private;
+ unsigned long long seq = 0;
+ struct ldb_request *search_req;
+
+ const struct ldb_control *partition_ctrl;
+ const struct dsdb_control_current_partition *partition;
+
+ static const char *contextCSN_attr[] = {
+ "contextCSN", NULL
+ };
+
+ map_private = talloc_get_type(module->private_data, struct map_private);
+
+ entryuuid_private = talloc_get_type(map_private->caller_private, struct entryuuid_private);
+
+ /* All this to get the DN of the parition, so we can search the right thing */
+ partition_ctrl = ldb_request_get_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID);
+ if (!partition_ctrl) {
+ ldb_debug_set(module->ldb, LDB_DEBUG_FATAL,
+ "instancetype_add: no current partition control found");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ partition = talloc_get_type(partition_ctrl->data,
+ struct dsdb_control_current_partition);
+ SMB_ASSERT(partition && partition->version == DSDB_CONTROL_CURRENT_PARTITION_VERSION);
+
+ search_req = talloc(req, struct ldb_request);
+ if (search_req == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Finally, we have it. This saves searching over more
+ * partitions than we expose to the client, such as a cn=samba
+ * configuration partition */
+
+ search_req->operation = LDB_SEARCH;
+ search_req->op.search.base = partition->dn;
+ search_req->op.search.scope = LDB_SCOPE_BASE;
+
+ search_req->op.search.tree = ldb_parse_tree(search_req, "objectClass=*");
+ if (search_req->op.search.tree == NULL) {
+ ldb_set_errstring(module->ldb, "Unable to parse search expression");
+ talloc_free(search_req);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ search_req->op.search.attrs = contextCSN_attr;
+ search_req->controls = NULL;
+ search_req->context = &seq;
+ search_req->callback = get_seq;
+ ldb_set_timeout(module->ldb, search_req, 0); /* use default timeout */
+
+ ret = ldb_next_request(module, search_req);
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(search_req->handle, LDB_WAIT_ALL);
+ }
+
+ talloc_free(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ switch (req->op.seq_num.type) {
+ case LDB_SEQ_HIGHEST_SEQ:
+ req->op.seq_num.seq_num = seq;
+ break;
+ case LDB_SEQ_NEXT:
+ req->op.seq_num.seq_num = seq;
+ req->op.seq_num.seq_num++;
+ break;
+ case LDB_SEQ_HIGHEST_TIMESTAMP:
+ {
+ req->op.seq_num.seq_num = (seq >> 24);
+ break;
+ }
+ }
+ req->op.seq_num.flags = 0;
+ req->op.seq_num.flags |= LDB_SEQ_TIMESTAMP_SEQUENCE;
+ req->op.seq_num.flags |= LDB_SEQ_GLOBAL_SEQUENCE;
+ return LDB_SUCCESS;
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_entryuuid_module_ops = {
+ .name = "entryuuid",
+ .init_context = entryuuid_init,
+ .sequence_number = entryuuid_sequence_number,
+ LDB_MAP_OPS
+};
+
+_PUBLIC_ const struct ldb_module_ops ldb_nsuniqueid_module_ops = {
+ .name = "nsuniqueid",
+ .init_context = nsuniqueid_init,
+ .sequence_number = entryuuid_sequence_number,
+ LDB_MAP_OPS
+};
diff --git a/source4/dsdb/samdb/ldb_modules/subtree_delete.c b/source4/dsdb/samdb/ldb_modules/subtree_delete.c
new file mode 100644
index 0000000000..9c332d2969
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/subtree_delete.c
@@ -0,0 +1,260 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2007
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb subtree delete (prevention) module
+ *
+ * Description: Prevent deletion of a subtree in LDB
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "ldb_includes.h"
+
+struct subtree_delete_context {
+ enum sd_step {SD_SEARCH, SD_DO_DEL} step;
+
+ struct ldb_module *module;
+ struct ldb_handle *handle;
+ struct ldb_request *orig_req;
+
+ struct ldb_request *search_req;
+ struct ldb_request *down_req;
+
+ int num_children;
+};
+
+static struct subtree_delete_context *subtree_delete_init_handle(struct ldb_request *req,
+ struct ldb_module *module)
+{
+ struct subtree_delete_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct subtree_delete_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = ac;
+
+ ac->module = module;
+ ac->handle = h;
+ ac->orig_req = req;
+
+ req->handle = h;
+
+ return ac;
+}
+
+static int subtree_delete_check_for_children(struct subtree_delete_context *ac)
+{
+ if (ac->num_children > 0) {
+ ldb_asprintf_errstring(ac->module->ldb, "Cannot delete %s, not a leaf node (has %d children)\n",
+ ldb_dn_get_linearized(ac->orig_req->op.del.dn), ac->num_children);
+ return LDB_ERR_NOT_ALLOWED_ON_NON_LEAF;
+ } else {
+ struct ldb_request *req = talloc(ac, struct ldb_request);
+ if (!req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *req = *ac->orig_req;
+
+ /* Ensure any (io) errors during the search for
+ * children don't propgate back in the error string */
+ ldb_set_errstring(ac->module->ldb, NULL);
+
+ ac->down_req = req;
+ ac->step = SD_DO_DEL;
+ return ldb_next_request(ac->module, req);
+ }
+}
+
+static int subtree_delete_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct subtree_delete_context *ac = talloc_get_type(context, struct subtree_delete_context);
+ TALLOC_CTX *mem_ctx = talloc_new(ac);
+
+ if (!mem_ctx) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* OK, we have one of *many* search results here:
+
+ We should also get the entry we tried to rename. This
+ callback handles this and everything below it.
+ */
+
+ /* Only entries are interesting, and we handle the case of the parent seperatly */
+ if (ares->type == LDB_REPLY_ENTRY
+ && ldb_dn_compare(ares->message->dn, ac->orig_req->op.del.dn) != 0) {
+ /* And it is an actual entry: now object bitterly that we are not a leaf node */
+ ac->num_children++;
+ }
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+/* rename */
+static int subtree_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ const char *attrs[] = { NULL };
+ struct ldb_request *new_req;
+ struct subtree_delete_context *ac;
+ int ret;
+ if (ldb_dn_is_special(req->op.rename.olddn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* This gets complex: We need to:
+ - Do a search for all entires under this entry
+ - Wait for these results to appear
+ - In the callback for each result, count the children (if any)
+ - return an error if there are any
+ */
+
+ ac = subtree_delete_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_search_req(&new_req, module->ldb, req,
+ req->op.del.dn,
+ LDB_SCOPE_SUBTREE,
+ "(objectClass=*)",
+ attrs,
+ req->controls,
+ ac,
+ subtree_delete_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->search_req = new_req;
+ if (req == NULL) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ return ldb_next_request(module, new_req);
+}
+
+
+static int subtree_delete_wait_none(struct ldb_handle *handle) {
+ struct subtree_delete_context *ac;
+ int ret = LDB_ERR_OPERATIONS_ERROR;
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct subtree_delete_context);
+
+ switch (ac->step) {
+ case SD_SEARCH:
+ ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS && ret != LDB_ERR_NO_SUCH_OBJECT) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->search_req->handle->status != LDB_SUCCESS
+ && ac->search_req->handle->status != LDB_ERR_NO_SUCH_OBJECT) {
+ handle->status = ac->search_req->handle->status;
+ goto done;
+ }
+
+ return subtree_delete_check_for_children(ac);
+
+ case SD_DO_DEL:
+ ret = ldb_wait(ac->down_req->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+
+ break;
+ }
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+}
+
+static int subtree_delete_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = subtree_delete_wait_none(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int subtree_delete_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return subtree_delete_wait_all(handle);
+ } else {
+ return subtree_delete_wait_none(handle);
+ }
+}
+
+const struct ldb_module_ops ldb_subtree_delete_module_ops = {
+ .name = "subtree_delete",
+ .del = subtree_delete,
+ .wait = subtree_delete_wait,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/subtree_rename.c b/source4/dsdb/samdb/ldb_modules/subtree_rename.c
new file mode 100644
index 0000000000..fd1388d416
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/subtree_rename.c
@@ -0,0 +1,285 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2007
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb subtree rename module
+ *
+ * Description: Rename a subtree in LDB
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "ldb_includes.h"
+
+struct subtree_rename_context {
+ struct ldb_module *module;
+ struct ldb_handle *handle;
+ struct ldb_request *orig_req;
+
+ struct ldb_request **down_req;
+ int num_requests;
+ int finished_requests;
+
+ int num_children;
+};
+
+static struct subtree_rename_context *subtree_rename_init_handle(struct ldb_request *req,
+ struct ldb_module *module)
+{
+ struct subtree_rename_context *ac;
+ struct ldb_handle *h;
+
+ h = talloc_zero(req, struct ldb_handle);
+ if (h == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ return NULL;
+ }
+
+ h->module = module;
+
+ ac = talloc_zero(h, struct subtree_rename_context);
+ if (ac == NULL) {
+ ldb_set_errstring(module->ldb, "Out of Memory");
+ talloc_free(h);
+ return NULL;
+ }
+
+ h->private_data = ac;
+
+ ac->module = module;
+ ac->handle = h;
+ ac->orig_req = req;
+
+ req->handle = h;
+
+ return ac;
+}
+
+
+static int subtree_rename_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
+{
+ struct ldb_request *req;
+ struct subtree_rename_context *ac = talloc_get_type(context, struct subtree_rename_context);
+ TALLOC_CTX *mem_ctx = talloc_new(ac);
+
+ if (!mem_ctx) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* OK, we have one of *many* search results here:
+
+ We should also get the entry we tried to rename. This
+ callback handles this and everything below it.
+ */
+
+ /* Only entries are interesting, and we handle the case of the parent seperatly */
+ if (ares->type == LDB_REPLY_ENTRY
+ && ldb_dn_compare(ares->message->dn, ac->orig_req->op.rename.olddn) != 0) {
+ /* And it is an actual entry: now create a rename from it */
+ int ret;
+
+ struct ldb_dn *newdn = ldb_dn_copy(mem_ctx, ares->message->dn);
+ if (!newdn) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ldb_dn_remove_base_components(newdn, ldb_dn_get_comp_num(ac->orig_req->op.rename.olddn));
+
+ if (!ldb_dn_add_base(newdn, ac->orig_req->op.rename.newdn)) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_rename_req(&req, ldb, mem_ctx,
+ ares->message->dn,
+ newdn,
+ NULL,
+ NULL,
+ NULL);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_set_timeout_from_prev_req(ldb, ac->orig_req, req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_steal(req, newdn);
+
+ talloc_steal(req, ares->message->dn);
+
+ talloc_free(ares);
+
+ } else if (ares->type == LDB_REPLY_DONE) {
+ req = talloc(mem_ctx, struct ldb_request);
+ *req = *ac->orig_req;
+ talloc_free(ares);
+
+ } else {
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->down_req[ac->num_requests] = req;
+ ac->num_requests++;
+
+ return ldb_next_request(ac->module, req);
+
+}
+
+/* rename */
+static int subtree_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ const char *attrs[] = { NULL };
+ struct ldb_request *new_req;
+ struct subtree_rename_context *ac;
+ int ret;
+ if (ldb_dn_is_special(req->op.rename.olddn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* This gets complex: We need to:
+ - Do a search for all entires under this entry
+ - Wait for these results to appear
+ - In the callback for each result, issue a modify request
+ - That will include this rename, we hope
+ - Wait for each modify result
+ - Regain our sainity
+ */
+
+ ac = subtree_rename_init_handle(req, module);
+ if (!ac) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_search_req(&new_req, module->ldb, req,
+ req->op.rename.olddn,
+ LDB_SCOPE_SUBTREE,
+ "(objectClass=*)",
+ attrs,
+ req->controls,
+ ac,
+ subtree_rename_search_callback);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_set_timeout_from_prev_req(module->ldb, req, new_req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->down_req = talloc_realloc(ac, ac->down_req,
+ struct ldb_request *, ac->num_requests + 1);
+ if (!ac->down_req) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->down_req[ac->num_requests] = new_req;
+ if (req == NULL) {
+ ldb_oom(ac->module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->num_requests++;
+ return ldb_next_request(module, new_req);
+}
+
+
+static int subtree_rename_wait_none(struct ldb_handle *handle) {
+ struct subtree_rename_context *ac;
+ int i, ret = LDB_ERR_OPERATIONS_ERROR;
+ if (!handle || !handle->private_data) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (handle->state == LDB_ASYNC_DONE) {
+ return handle->status;
+ }
+
+ handle->state = LDB_ASYNC_PENDING;
+ handle->status = LDB_SUCCESS;
+
+ ac = talloc_get_type(handle->private_data, struct subtree_rename_context);
+
+ for (i=0; i < ac->num_requests; i++) {
+ ret = ldb_wait(ac->down_req[i]->handle, LDB_WAIT_NONE);
+
+ if (ret != LDB_SUCCESS) {
+ handle->status = ret;
+ goto done;
+ }
+ if (ac->down_req[i]->handle->status != LDB_SUCCESS) {
+ handle->status = ac->down_req[i]->handle->status;
+ goto done;
+ }
+
+ if (ac->down_req[i]->handle->state != LDB_ASYNC_DONE) {
+ return LDB_SUCCESS;
+ }
+ }
+
+done:
+ handle->state = LDB_ASYNC_DONE;
+ return ret;
+
+}
+
+static int subtree_rename_wait_all(struct ldb_handle *handle) {
+
+ int ret;
+
+ while (handle->state != LDB_ASYNC_DONE) {
+ ret = subtree_rename_wait_none(handle);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return handle->status;
+}
+
+static int subtree_rename_wait(struct ldb_handle *handle, enum ldb_wait_type type)
+{
+ if (type == LDB_WAIT_ALL) {
+ return subtree_rename_wait_all(handle);
+ } else {
+ return subtree_rename_wait_none(handle);
+ }
+}
+
+const struct ldb_module_ops ldb_subtree_rename_module_ops = {
+ .name = "subtree_rename",
+ .rename = subtree_rename,
+ .wait = subtree_rename_wait,
+};
diff --git a/source4/dsdb/samdb/ldb_modules/tests/samba3sam.py b/source4/dsdb/samdb/ldb_modules/tests/samba3sam.py
new file mode 100644
index 0000000000..428e6b4d4b
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/samba3sam.py
@@ -0,0 +1,1046 @@
+#!/usr/bin/python
+
+# Unix SMB/CIFS implementation.
+# Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2005-2007
+# Copyright (C) Martin Kuehl <mkhl@samba.org> 2006
+#
+# This is a Python port of the original in testprogs/ejs/samba3sam.js
+#
+# 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/>.
+#
+
+"""Tests for the samba3sam LDB module, which maps Samba3 LDAP to AD LDAP."""
+
+import os
+import sys
+import samba
+import ldb
+from ldb import SCOPE_DEFAULT, SCOPE_BASE, SCOPE_SUBTREE
+from samba import Ldb, substitute_var
+from samba.tests import LdbTestCase, TestCaseInTempDir
+
+datadir = os.path.join(os.path.dirname(__file__), "../../../../../testdata/samba3")
+
+class MapBaseTestCase(TestCaseInTempDir):
+ def setup_data(self, obj, ldif):
+ self.assertTrue(ldif is not None)
+ obj.db.add_ldif(substitute_var(ldif, obj.substvars))
+
+ def setup_modules(self, ldb, s3, s4):
+ ldb.add({"dn": "@MAP=samba3sam",
+ "@FROM": s4.basedn,
+ "@TO": "sambaDomainName=TESTS," + s3.basedn})
+
+ ldb.add({"dn": "@MODULES",
+ "@LIST": "rootdse,paged_results,server_sort,extended_dn,asq,samldb,password_hash,operational,objectguid,rdn_name,samba3sam,partition"})
+
+ ldb.add({"dn": "@PARTITION",
+ "partition": [s4.basedn + ":" + s4.url, s3.basedn + ":" + s3.url],
+ "replicateEntries": ["@ATTRIBUTES", "@INDEXLIST"]})
+
+ def setUp(self):
+ super(MapBaseTestCase, self).setUp()
+
+ def make_dn(basedn, rdn):
+ return rdn + ",sambaDomainName=TESTS," + basedn
+
+ def make_s4dn(basedn, rdn):
+ return rdn + "," + basedn
+
+ self.ldbfile = os.path.join(self.tempdir, "test.ldb")
+ self.ldburl = "tdb://" + self.ldbfile
+
+ tempdir = self.tempdir
+ print tempdir
+
+ class Target:
+ """Simple helper class that contains data for a specific SAM connection."""
+ def __init__(self, file, basedn, dn):
+ self.file = os.path.join(tempdir, file)
+ self.url = "tdb://" + self.file
+ self.basedn = basedn
+ self.substvars = {"BASEDN": self.basedn}
+ self.db = Ldb()
+ self._dn = dn
+
+ def dn(self, rdn):
+ return self._dn(rdn, self.basedn)
+
+ def connect(self):
+ return self.db.connect(self.url)
+
+ self.samba4 = Target("samba4.ldb", "dc=vernstok,dc=nl", make_s4dn)
+ self.samba3 = Target("samba3.ldb", "cn=Samba3Sam", make_dn)
+ self.templates = Target("templates.ldb", "cn=templates", None)
+
+ self.samba3.connect()
+ self.templates.connect()
+ self.samba4.connect()
+
+ def tearDown(self):
+ os.unlink(self.ldbfile)
+ os.unlink(self.samba3.file)
+ os.unlink(self.templates.file)
+ os.unlink(self.samba4.file)
+ super(MapBaseTestCase, self).tearDown()
+
+
+class Samba3SamTestCase(MapBaseTestCase):
+ def setUp(self):
+ super(Samba3SamTestCase, self).setUp()
+ ldb = Ldb(self.ldburl)
+ self.setup_data(self.samba3, open(os.path.join(datadir, "samba3.ldif"), 'r').read())
+ self.setup_data(self.templates, open(os.path.join(datadir, "provision_samba3sam_templates.ldif"), 'r').read())
+ ldif = open(os.path.join(datadir, "provision_samba3sam.ldif"), 'r').read()
+ ldb.add_ldif(substitute_var(ldif, self.samba4.substvars))
+ self.setup_modules(ldb, self.samba3, self.samba4)
+ self.ldb = Ldb(self.ldburl)
+
+ def test_s3sam_search(self):
+ print "Looking up by non-mapped attribute"
+ msg = self.ldb.search(expression="(cn=Administrator)")
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(msg[0]["cn"], "Administrator")
+
+ print "Looking up by mapped attribute"
+ msg = self.ldb.search(expression="(name=Backup Operators)")
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(msg[0]["name"], "Backup Operators")
+
+ print "Looking up by old name of renamed attribute"
+ msg = self.ldb.search(expression="(displayName=Backup Operators)")
+ self.assertEquals(len(msg), 0)
+
+ print "Looking up mapped entry containing SID"
+ msg = self.ldb.search(expression="(cn=Replicator)")
+ self.assertEquals(len(msg), 1)
+ print msg[0].dn
+ self.assertEquals(str(msg[0].dn), "cn=Replicator,ou=Groups,dc=vernstok,dc=nl")
+ self.assertEquals(msg[0]["objectSid"], "S-1-5-21-4231626423-2410014848-2360679739-552")
+
+ print "Checking mapping of objectClass"
+ oc = set(msg[0]["objectClass"])
+ self.assertTrue(oc is not None)
+ for i in oc:
+ self.assertEquals(oc[i] == "posixGroup" or oc[i], "group")
+
+ print "Looking up by objectClass"
+ msg = self.ldb.search(expression="(|(objectClass=user)(cn=Administrator))")
+ self.assertEquals(len(msg), 2)
+ for i in range(len(msg)):
+ self.assertEquals((str(msg[i].dn), "unixName=Administrator,ou=Users,dc=vernstok,dc=nl") or
+ (str(msg[i].dn) == "unixName=nobody,ou=Users,dc=vernstok,dc=nl"))
+
+
+ def test_s3sam_modify(self):
+ print "Adding a record that will be fallbacked"
+ self.ldb.add({"dn": "cn=Foo",
+ "foo": "bar",
+ "blah": "Blie",
+ "cn": "Foo",
+ "showInAdvancedViewOnly": "TRUE"}
+ )
+
+ print "Checking for existence of record (local)"
+ # TODO: This record must be searched in the local database, which is currently only supported for base searches
+ # msg = ldb.search(expression="(cn=Foo)", ['foo','blah','cn','showInAdvancedViewOnly')]
+ # TODO: Actually, this version should work as well but doesn't...
+ #
+ #
+ msg = self.ldb.search(expression="(cn=Foo)", base="cn=Foo", scope=SCOPE_BASE, attrs=['foo','blah','cn','showInAdvancedViewOnly'])
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(msg[0]["showInAdvancedViewOnly"], "TRUE")
+ self.assertEquals(msg[0]["foo"], "bar")
+ self.assertEquals(msg[0]["blah"], "Blie")
+
+ print "Adding record that will be mapped"
+ self.ldb.add({"dn": "cn=Niemand,cn=Users,dc=vernstok,dc=nl",
+ "objectClass": "user",
+ "unixName": "bin",
+ "sambaUnicodePwd": "geheim",
+ "cn": "Niemand"})
+
+ print "Checking for existence of record (remote)"
+ msg = self.ldb.search(expression="(unixName=bin)",
+ attrs=['unixName','cn','dn', 'sambaUnicodePwd'])
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(msg[0]["cn"], "Niemand")
+ self.assertEquals(msg[0]["sambaUnicodePwd"], "geheim")
+
+ print "Checking for existence of record (local && remote)"
+ msg = self.ldb.search(expression="(&(unixName=bin)(sambaUnicodePwd=geheim))",
+ attrs=['unixName','cn','dn', 'sambaUnicodePwd'])
+ self.assertEquals(len(msg), 1) # TODO: should check with more records
+ self.assertEquals(msg[0]["cn"], "Niemand")
+ self.assertEquals(msg[0]["unixName"], "bin")
+ self.assertEquals(msg[0]["sambaUnicodePwd"], "geheim")
+
+ print "Checking for existence of record (local || remote)"
+ msg = self.ldb.search(expression="(|(unixName=bin)(sambaUnicodePwd=geheim))",
+ attrs=['unixName','cn','dn', 'sambaUnicodePwd'])
+ print "got " + len(msg) + " replies"
+ self.assertEquals(len(msg), 1) # TODO: should check with more records
+ self.assertEquals(msg[0]["cn"], "Niemand")
+ self.assertEquals(msg[0]["unixName"] == "bin" or msg[0]["sambaUnicodePwd"], "geheim")
+
+ print "Checking for data in destination database"
+ msg = s3.db.search("(cn=Niemand)")
+ self.assertTrue(len(msg) >= 1)
+ self.assertEquals(msg[0]["sambaSID"], "S-1-5-21-4231626423-2410014848-2360679739-2001")
+ self.assertEquals(msg[0]["displayName"], "Niemand")
+
+ print "Adding attribute..."
+ self.ldb.modify_ldif("""
+dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl
+changetype: modify
+add: description
+description: Blah
+""")
+
+ print "Checking whether changes are still there..."
+ msg = self.ldb.search(expression="(cn=Niemand)")
+ self.assertTrue(len(msg) >= 1)
+ self.assertEquals(msg[0]["cn"], "Niemand")
+ self.assertEquals(msg[0]["description"], "Blah")
+
+ print "Modifying attribute..."
+ self.ldb.modify_ldif("""
+dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl
+changetype: modify
+replace: description
+description: Blie
+""")
+
+ print "Checking whether changes are still there..."
+ msg = self.ldb.search(expression="(cn=Niemand)")
+ self.assertTrue(len(msg) >= 1)
+ self.assertEquals(msg[0]["description"], "Blie")
+
+ print "Deleting attribute..."
+ self.ldb.modify_ldif("""
+dn: cn=Niemand,cn=Users,dc=vernstok,dc=nl
+changetype: modify
+delete: description
+""")
+
+ print "Checking whether changes are no longer there..."
+ msg = self.ldb.search(expression="(cn=Niemand)")
+ self.assertTrue(len(msg) >= 1)
+ self.assertTrue(not "description" in res[0])
+
+ print "Renaming record..."
+ self.ldb.rename("cn=Niemand,cn=Users,dc=vernstok,dc=nl", "cn=Niemand2,cn=Users,dc=vernstok,dc=nl")
+
+ print "Checking whether DN has changed..."
+ msg = self.ldb.search(expression="(cn=Niemand2)")
+ self.assertEquals(len(msg), 1)
+ self.assertEquals(str(msg[0].dn), "cn=Niemand2,cn=Users,dc=vernstok,dc=nl")
+
+ print "Deleting record..."
+ self.ldb.delete("cn=Niemand2,cn=Users,dc=vernstok,dc=nl")
+
+ print "Checking whether record is gone..."
+ msg = self.ldb.search(expression="(cn=Niemand2)")
+ self.assertEquals(len(msg), 0)
+
+
+
+class MapTestCase(MapBaseTestCase):
+ def setUp(self):
+ super(MapTestCase, self).setUp()
+ ldb = Ldb(self.ldburl)
+ self.setup_data(self.templates, open(os.path.join(datadir, "provision_samba3sam_templates.ldif"), 'r').read())
+ ldif = open(os.path.join(datadir, "provision_samba3sam.ldif"), 'r').read()
+ ldb.add_ldif(substitute_var(ldif, self.samba4.substvars))
+ self.setup_modules(ldb, self.samba3, self.samba4)
+ self.ldb = Ldb(self.ldburl)
+
+ def test_map_search(self):
+ print "Running search tests on mapped data"
+ ldif = """
+dn: """ + "sambaDomainName=TESTS,""" + self.samba3.basedn + """
+objectclass: sambaDomain
+objectclass: top
+sambaSID: S-1-5-21-4231626423-2410014848-2360679739
+sambaNextRid: 2000
+sambaDomainName: TESTS"""
+ self.samba3.db.add_ldif(substitute_var(ldif, self.samba3.substvars))
+
+ print "Add a set of split records"
+ ldif = """
+dn: """ + self.samba4.dn("cn=X") + """
+objectClass: user
+cn: X
+codePage: x
+revision: x
+dnsHostName: x
+nextRid: y
+lastLogon: x
+description: x
+objectSid: S-1-5-21-4231626423-2410014848-2360679739-552
+primaryGroupID: 1-5-21-4231626423-2410014848-2360679739-512
+
+dn: """ + self.samba4.dn("cn=Y") + """
+objectClass: top
+cn: Y
+codePage: x
+revision: x
+dnsHostName: y
+nextRid: y
+lastLogon: y
+description: x
+
+dn: """ + self.samba4.dn("cn=Z") + """
+objectClass: top
+cn: Z
+codePage: x
+revision: y
+dnsHostName: z
+nextRid: y
+lastLogon: z
+description: y
+"""
+
+ self.ldb.add_ldif(substitute_var(ldif, self.samba4.substvars))
+
+ print "Add a set of remote records"
+
+ ldif = """
+dn: """ + self.samba3.dn("cn=A") + """
+objectClass: posixAccount
+cn: A
+sambaNextRid: x
+sambaBadPasswordCount: x
+sambaLogonTime: x
+description: x
+sambaSID: S-1-5-21-4231626423-2410014848-2360679739-552
+sambaPrimaryGroupSID: S-1-5-21-4231626423-2410014848-2360679739-512
+
+dn: """ + self.samba3.dn("cn=B") + """
+objectClass: top
+cn:B
+sambaNextRid: x
+sambaBadPasswordCount: x
+sambaLogonTime: y
+description: x
+
+dn: """ + self.samba3.dn("cn=C") + """
+objectClass: top
+cn: C
+sambaNextRid: x
+sambaBadPasswordCount: y
+sambaLogonTime: z
+description: y
+"""
+ self.samba3.add_ldif(substitute_var(ldif, self.samba3.substvars))
+
+ print "Testing search by DN"
+
+ # Search remote record by local DN
+ dn = self.samba4.dn("cn=A")
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(str(res[0].dn)), dn)
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "x")
+
+ # Search remote record by remote DN
+ dn = self.samba3.dn("cn=A")
+ attrs = ["dnsHostName", "lastLogon", "sambaLogonTime"]
+ res = self.samba3.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(str(res[0].dn)), dn)
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertTrue(not "lastLogon" in res[0])
+ self.assertEquals(res[0]["sambaLogonTime"], "x")
+
+ # Search split record by local DN
+ dn = self.samba4.dn("cn=X")
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(str(res[0].dn)), dn)
+ self.assertEquals(res[0]["dnsHostName"], "x")
+ self.assertEquals(res[0]["lastLogon"], "x")
+
+ # Search split record by remote DN
+ dn = self.samba3.dn("cn=X")
+ attrs = ["dnsHostName", "lastLogon", "sambaLogonTime"]
+ res = self.samba3.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(str(res[0].dn)), dn)
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertTrue(not "lastLogon" in res[0])
+ self.assertEquals(res[0]["sambaLogonTime"], "x")
+
+ print "Testing search by attribute"
+
+ # Search by ignored attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(revision=x)", scope=SCOPE_DEFAULT, attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(str(res[0].dn)), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(str(res[1].dn)), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+
+ # Search by kept attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(description=y)", scope=SCOPE_DEFAULT, attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(str(res[0].dn)), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[0]["dnsHostName"], "z")
+ self.assertEquals(res[0]["lastLogon"], "z")
+ self.assertEquals(str(str(res[1].dn)), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "z")
+
+ # Search by renamed attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(badPwdCount=x)", scope=SCOPE_DEFAULT, attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+
+ # Search by converted attribute
+ attrs = ["dnsHostName", "lastLogon", "objectSid"]
+ # TODO:
+ # Using the SID directly in the parse tree leads to conversion
+ # errors, letting the search fail with no results.
+ #res = self.ldb.search("(objectSid=S-1-5-21-4231626423-2410014848-2360679739-552)", scope=SCOPE_DEFAULT, attrs)
+ res = self.ldb.search(expression="(objectSid=*)", attrs=attrs)
+ self.assertEquals(len(res), 3)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[0]["dnsHostName"], "x")
+ self.assertEquals(res[0]["lastLogon"], "x")
+ self.assertEquals(res[0]["objectSid"], "S-1-5-21-4231626423-2410014848-2360679739-552")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(res[1]["objectSid"], "S-1-5-21-4231626423-2410014848-2360679739-552")
+
+ # Search by generated attribute
+ # In most cases, this even works when the mapping is missing
+ # a `convert_operator' by enumerating the remote db.
+ attrs = ["dnsHostName", "lastLogon", "primaryGroupID"]
+ res = self.ldb.search(expression="(primaryGroupID=512)", attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "x")
+ self.assertEquals(res[0]["primaryGroupID"], "512")
+
+ # TODO: There should actually be two results, A and X. The
+ # primaryGroupID of X seems to get corrupted somewhere, and the
+ # objectSid isn't available during the generation of remote (!) data,
+ # which can be observed with the following search. Also note that Xs
+ # objectSid seems to be fine in the previous search for objectSid... */
+ #res = ldb.search(expression="(primaryGroupID=*)", NULL, ldb. SCOPE_DEFAULT, attrs)
+ #print len(res) + " results found"
+ #for i in range(len(res)):
+ # for (obj in res[i]) {
+ # print obj + ": " + res[i][obj]
+ # }
+ # print "---"
+ #
+
+ # Search by remote name of renamed attribute */
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(sambaBadPasswordCount=*)", attrs=attrs)
+ self.assertEquals(len(res), 0)
+
+ # Search by objectClass
+ attrs = ["dnsHostName", "lastLogon", "objectClass"]
+ res = self.ldb.search(expression="(objectClass=user)", attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[0]["dnsHostName"], "x")
+ self.assertEquals(res[0]["lastLogon"], "x")
+ self.assertTrue(res[0]["objectClass"] is not None)
+ self.assertEquals(res[0]["objectClass"][0], "user")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertTrue(res[1]["objectClass"] is not None)
+ self.assertEquals(res[1]["objectClass"][0], "user")
+
+ # Prove that the objectClass is actually used for the search
+ res = self.ldb.search(expression="(|(objectClass=user)(badPwdCount=x))", attrs=attrs)
+ self.assertEquals(len(res), 3)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertTrue(res[0]["objectClass"] is not None)
+ for oc in set(res[0]["objectClass"]):
+ self.assertEquals(oc, "user")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertTrue(res[1]["objectClass"] is not None)
+ self.assertEquals(res[1]["objectClass"][0], "user")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[2])
+ self.assertEquals(res[2]["lastLogon"], "x")
+ self.assertTrue(res[2]["objectClass"] is not None)
+ self.assertEquals(res[2]["objectClass"][0], "user")
+
+ print "Testing search by parse tree"
+
+ # Search by conjunction of local attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(&(codePage=x)(revision=x))", attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+
+ # Search by conjunction of remote attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(&(lastLogon=x)(description=x))", attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[0]["dnsHostName"], "x")
+ self.assertEquals(res[0]["lastLogon"], "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+
+ # Search by conjunction of local and remote attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(&(codePage=x)(description=x))", attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+
+ # Search by conjunction of local and remote attribute w/o match
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(&(codePage=x)(nextRid=x))", attrs=attrs)
+ self.assertEquals(len(res), 0)
+ res = self.ldb.search(expression="(&(revision=x)(lastLogon=z))", attrs=attrs)
+ self.assertEquals(len(res), 0)
+
+ # Search by disjunction of local attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(|(revision=x)(dnsHostName=x))", attrs=attrs)
+ self.assertEquals(len(res), 2)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+
+ # Search by disjunction of remote attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(|(badPwdCount=x)(lastLogon=x))", attrs=attrs)
+ self.assertEquals(len(res), 3)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue("dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A"))
+ self.assertTrue("dnsHostName" in res[2])
+ self.assertEquals(res[2]["lastLogon"], "x")
+
+ # Search by disjunction of local and remote attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(|(revision=x)(lastLogon=y))", attrs=attrs)
+ self.assertEquals(len(res), 3)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
+ self.assertTrue("dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "y")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[2]["dnsHostName"], "x")
+ self.assertEquals(res[2]["lastLogon"], "x")
+
+ # Search by disjunction of local and remote attribute w/o match
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(|(codePage=y)(nextRid=z))", attrs=attrs)
+ self.assertEquals(len(res), 0)
+
+ # Search by negated local attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(revision=x))", attrs=attrs)
+ self.assertEquals(len(res), 5)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[2]["dnsHostName"], "z")
+ self.assertEquals(res[2]["lastLogon"], "z")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(res[3]["lastLogon"], "z")
+
+ # Search by negated remote attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(description=x))", attrs=attrs)
+ self.assertEquals(len(res), 3)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[0]["dnsHostName"], "z")
+ self.assertEquals(res[0]["lastLogon"], "z")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "z")
+
+ # Search by negated conjunction of local attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(&(codePage=x)(revision=x)))", attrs=attrs)
+ self.assertEquals(len(res), 5)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[2]["dnsHostName"], "z")
+ self.assertEquals(res[2]["lastLogon"], "z")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(res[3]["lastLogon"], "z")
+
+ # Search by negated conjunction of remote attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(&(lastLogon=x)(description=x)))", attrs=attrs)
+ self.assertEquals(len(res), 5)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "y")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[2]["dnsHostName"], "z")
+ self.assertEquals(res[2]["lastLogon"], "z")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(res[3]["lastLogon"], "z")
+
+ # Search by negated conjunction of local and remote attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(&(codePage=x)(description=x)))", attrs=attrs)
+ self.assertEquals(len(res), 5)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[2]["dnsHostName"], "z")
+ self.assertEquals(res[2]["lastLogon"], "z")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(res[3]["lastLogon"], "z")
+
+ # Search by negated disjunction of local attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(|(revision=x)(dnsHostName=x)))", attrs=attrs)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[1])
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[2]["dnsHostName"], "z")
+ self.assertEquals(res[2]["lastLogon"], "z")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[3])
+ self.assertEquals(res[3]["lastLogon"], "z")
+
+ # Search by negated disjunction of remote attributes
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(|(badPwdCount=x)(lastLogon=x)))", attrs=attrs)
+ self.assertEquals(len(res), 4)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=Y"))
+ self.assertEquals(res[0]["dnsHostName"], "y")
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[1]["dnsHostName"], "z")
+ self.assertEquals(res[1]["lastLogon"], "z")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[2])
+ self.assertEquals(res[2]["lastLogon"], "z")
+
+ # Search by negated disjunction of local and remote attribute
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(!(|(revision=x)(lastLogon=y)))", attrs=attrs)
+ self.assertEquals(len(res), 4)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "x")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[1]["dnsHostName"], "z")
+ self.assertEquals(res[1]["lastLogon"], "z")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[2])
+ self.assertEquals(res[2]["lastLogon"], "z")
+
+ print "Search by complex parse tree"
+ attrs = ["dnsHostName", "lastLogon"]
+ res = self.ldb.search(expression="(|(&(revision=x)(dnsHostName=x))(!(&(description=x)(nextRid=y)))(badPwdCount=y))", attrs=attrs)
+ self.assertEquals(len(res), 6)
+ self.assertEquals(str(res[0].dn), self.samba4.dn("cn=B"))
+ self.assertTrue(not "dnsHostName" in res[0])
+ self.assertEquals(res[0]["lastLogon"], "y")
+ self.assertEquals(str(res[1].dn), self.samba4.dn("cn=X"))
+ self.assertEquals(res[1]["dnsHostName"], "x")
+ self.assertEquals(res[1]["lastLogon"], "x")
+ self.assertEquals(str(res[2].dn), self.samba4.dn("cn=A"))
+ self.assertTrue(not "dnsHostName" in res[2])
+ self.assertEquals(res[2]["lastLogon"], "x")
+ self.assertEquals(str(res[3].dn), self.samba4.dn("cn=Z"))
+ self.assertEquals(res[3]["dnsHostName"], "z")
+ self.assertEquals(res[3]["lastLogon"], "z")
+ self.assertEquals(str(res[4].dn), self.samba4.dn("cn=C"))
+ self.assertTrue(not "dnsHostName" in res[4])
+ self.assertEquals(res[4]["lastLogon"], "z")
+
+ # Clean up
+ dns = [self.samba4.dn("cn=%s" % n) for n in ["A","B","C","X","Y","Z"]]
+ for dn in dns:
+ self.ldb.delete(dn)
+
+ def test_map_modify_local(self):
+ """Modification of local records."""
+ # Add local record
+ dn = "cn=test,dc=idealx,dc=org"
+ self.ldb.add({"dn": dn,
+ "cn": "test",
+ "foo": "bar",
+ "revision": "1",
+ "description": "test"})
+ # Check it's there
+ attrs = ["foo", "revision", "description"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["foo"], "bar")
+ self.assertEquals(res[0]["revision"], "1")
+ self.assertEquals(res[0]["description"], "test")
+ # Check it's not in the local db
+ res = self.samba4.db.search(expression="(cn=test)", scope=SCOPE_DEFAULT, attrs=attrs)
+ self.assertEquals(len(res), 0)
+ # Check it's not in the remote db
+ res = self.samba3.db.search(expression="(cn=test)", scope=SCOPE_DEFAULT, attrs=attrs)
+ self.assertEquals(len(res), 0)
+
+ # Modify local record
+ ldif = """
+dn: """ + dn + """
+replace: foo
+foo: baz
+replace: description
+description: foo
+"""
+ self.ldb.modify_ldif(ldif)
+ # Check in local db
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["foo"], "baz")
+ self.assertEquals(res[0]["revision"], "1")
+ self.assertEquals(res[0]["description"], "foo")
+
+ # Rename local record
+ dn2 = "cn=toast,dc=idealx,dc=org"
+ self.ldb.rename(dn, dn2)
+ # Check in local db
+ res = self.ldb.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["foo"], "baz")
+ self.assertEquals(res[0]["revision"], "1")
+ self.assertEquals(res[0]["description"], "foo")
+
+ # Delete local record
+ self.ldb.delete(dn2)
+ # Check it's gone
+ res = self.ldb.search(dn2, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+
+ def test_map_modify_remote_remote(self):
+ """Modification of remote data of remote records"""
+ # Add remote record
+ dn = self.samba4.dn("cn=test")
+ dn2 = self.samba3.dn("cn=test")
+ self.samba3.db.add({"dn": dn2,
+ "cn": "test",
+ "description": "foo",
+ "sambaBadPasswordCount": "3",
+ "sambaNextRid": "1001"})
+ # Check it's there
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "foo")
+ self.assertEquals(res[0]["sambaBadPasswordCount"], "3")
+ self.assertEquals(res[0]["sambaNextRid"], "1001")
+ # Check in mapped db
+ attrs = ["description", "badPwdCount", "nextRid"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "foo")
+ self.assertEquals(res[0]["badPwdCount"], "3")
+ self.assertEquals(res[0]["nextRid"], "1001")
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 0)
+
+ # Modify remote data of remote record
+ ldif = """
+dn: """ + dn + """
+replace: description
+description: test
+replace: badPwdCount
+badPwdCount: 4
+"""
+ self.ldb.modify_ldif(ldif)
+ # Check in mapped db
+ attrs = ["description", "badPwdCount", "nextRid"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["badPwdCount"], "4")
+ self.assertEquals(res[0]["nextRid"], "1001")
+ # Check in remote db
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["sambaBadPasswordCount"], "4")
+ self.assertEquals(res[0]["sambaNextRid"], "1001")
+
+ # Rename remote record
+ dn2 = self.samba4.dn("cn=toast")
+ self.ldb.rename(dn, dn2)
+ # Check in mapped db
+ dn = dn2
+ attrs = ["description", "badPwdCount", "nextRid"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["badPwdCount"], "4")
+ self.assertEquals(res[0]["nextRid"], "1001")
+ # Check in remote db
+ dn2 = self.samba3.dn("cn=toast")
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["sambaBadPasswordCount"], "4")
+ self.assertEquals(res[0]["sambaNextRid"], "1001")
+
+ # Delete remote record
+ self.ldb.delete(dn)
+ # Check in mapped db
+ res = self.ldb.search(dn, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+ # Check in remote db
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+
+ def test_map_modify_remote_local(self):
+ """Modification of local data of remote records"""
+ # Add remote record (same as before)
+ dn = self.samba4.dn("cn=test")
+ dn2 = self.samba3.dn("cn=test")
+ self.samba3.db.add({"dn": dn2,
+ "cn": "test",
+ "description": "foo",
+ "sambaBadPasswordCount": "3",
+ "sambaNextRid": "1001"})
+
+ # Modify local data of remote record
+ ldif = """
+dn: """ + dn + """
+add: revision
+revision: 1
+replace: description
+description: test
+"""
+ self.ldb.modify_ldif(ldif)
+ # Check in mapped db
+ attrs = ["revision", "description"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["revision"], "1")
+ # Check in remote db
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertTrue(not "revision" in res[0])
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertTrue(not "description" in res[0])
+ self.assertEquals(res[0]["revision"], "1")
+
+ # Delete (newly) split record
+ self.ldb.delete(dn)
+
+ def test_map_modify_split(self):
+ """Testing modification of split records"""
+ # Add split record
+ dn = self.samba4.dn("cn=test")
+ dn2 = self.samba3.dn("cn=test")
+ self.ldb.add({
+ "dn": dn,
+ "cn": "test",
+ "description": "foo",
+ "badPwdCount": "3",
+ "nextRid": "1001",
+ "revision": "1"})
+ # Check it's there
+ attrs = ["description", "badPwdCount", "nextRid", "revision"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "foo")
+ self.assertEquals(res[0]["badPwdCount"], "3")
+ self.assertEquals(res[0]["nextRid"], "1001")
+ self.assertEquals(res[0]["revision"], "1")
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertTrue(not "description" in res[0])
+ self.assertTrue(not "badPwdCount" in res[0])
+ self.assertTrue(not "nextRid" in res[0])
+ self.assertEquals(res[0]["revision"], "1")
+ # Check in remote db
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid", "revision"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "foo")
+ self.assertEquals(res[0]["sambaBadPasswordCount"], "3")
+ self.assertEquals(res[0]["sambaNextRid"], "1001")
+ self.assertTrue(not "revision" in res[0])
+
+ # Modify of split record
+ ldif = """
+dn: """ + dn + """
+replace: description
+description: test
+replace: badPwdCount
+badPwdCount: 4
+replace: revision
+revision: 2
+"""
+ self.ldb.modify_ldif(ldif)
+ # Check in mapped db
+ attrs = ["description", "badPwdCount", "nextRid", "revision"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["badPwdCount"], "4")
+ self.assertEquals(res[0]["nextRid"], "1001")
+ self.assertEquals(res[0]["revision"], "2")
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertTrue(not "description" in res[0])
+ self.assertTrue(not "badPwdCount" in res[0])
+ self.assertTrue(not "nextRid" in res[0])
+ self.assertEquals(res[0]["revision"], "2")
+ # Check in remote db
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid", "revision"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["sambaBadPasswordCount"], "4")
+ self.assertEquals(res[0]["sambaNextRid"], "1001")
+ self.assertTrue(not "revision" in res[0])
+
+ # Rename split record
+ dn2 = self.samba4.dn("cn=toast")
+ self.ldb.rename(dn, dn2)
+ # Check in mapped db
+ dn = dn2
+ attrs = ["description", "badPwdCount", "nextRid", "revision"]
+ res = self.ldb.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["badPwdCount"], "4")
+ self.assertEquals(res[0]["nextRid"], "1001")
+ self.assertEquals(res[0]["revision"], "2")
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn)
+ self.assertTrue(not "description" in res[0])
+ self.assertTrue(not "badPwdCount" in res[0])
+ self.assertTrue(not "nextRid" in res[0])
+ self.assertEquals(res[0]["revision"], "2")
+ # Check in remote db
+ dn2 = self.samba3.dn("cn=toast")
+ attrs = ["description", "sambaBadPasswordCount", "sambaNextRid", "revision"]
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE, attrs=attrs)
+ self.assertEquals(len(res), 1)
+ self.assertEquals(str(res[0].dn), dn2)
+ self.assertEquals(res[0]["description"], "test")
+ self.assertEquals(res[0]["sambaBadPasswordCount"], "4")
+ self.assertEquals(res[0]["sambaNextRid"], "1001")
+ self.assertTrue(not "revision" in res[0])
+
+ # Delete split record
+ self.ldb.delete(dn)
+ # Check in mapped db
+ res = self.ldb.search(dn, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+ # Check in local db
+ res = self.samba4.db.search(dn, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
+ # Check in remote db
+ res = self.samba3.db.search(dn2, scope=SCOPE_BASE)
+ self.assertEquals(len(res), 0)
diff --git a/source4/dsdb/samdb/ldb_modules/update_keytab.c b/source4/dsdb/samdb/ldb_modules/update_keytab.c
new file mode 100644
index 0000000000..b36c2c9b71
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/update_keytab.c
@@ -0,0 +1,211 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007
+
+ 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/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb update_keytabs module
+ *
+ * Description: Update keytabs whenever their matching secret record changes
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb/include/ldb_includes.h"
+#include "auth/credentials/credentials.h"
+#include "auth/credentials/credentials_krb5.h"
+#include "system/kerberos.h"
+#include "param/param.h"
+
+struct dn_list {
+ struct cli_credentials *creds;
+ struct dn_list *prev, *next;
+};
+
+struct update_kt_private {
+ struct dn_list *changed_dns;
+};
+
+static int add_modified(struct ldb_module *module, struct ldb_dn *dn, bool delete) {
+ struct update_kt_private *data = talloc_get_type(module->private_data, struct update_kt_private);
+ struct dn_list *item;
+ char *filter;
+ struct ldb_result *res;
+ const char *attrs[] = { NULL };
+ int ret;
+ NTSTATUS status;
+
+ filter = talloc_asprintf(data, "(&(dn=%s)(&(objectClass=kerberosSecret)(privateKeytab=*)))",
+ ldb_dn_get_linearized(dn));
+ if (!filter) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_search(module->ldb, dn, LDB_SCOPE_BASE,
+ filter, attrs, &res);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(filter);
+ return ret;
+ }
+
+ if (res->count != 1) {
+ /* if it's not a kerberosSecret then we don't have anything to update */
+ talloc_free(res);
+ talloc_free(filter);
+ return LDB_SUCCESS;
+ }
+ talloc_free(res);
+
+ item = talloc(data->changed_dns? (void *)data->changed_dns: (void *)data, struct dn_list);
+ if (!item) {
+ talloc_free(filter);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ item->creds = cli_credentials_init(item);
+ if (!item->creds) {
+ DEBUG(1, ("cli_credentials_init failed!"));
+ talloc_free(filter);
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ cli_credentials_set_conf(item->creds, ldb_get_opaque(module->ldb, "loadparm"));
+ status = cli_credentials_set_secrets(item->creds, ldb_get_opaque(module->ldb, "EventContext"), ldb_get_opaque(module->ldb, "loadparm"), module->ldb, NULL, filter);
+ talloc_free(filter);
+ if (NT_STATUS_IS_OK(status)) {
+ if (delete) {
+ /* Ensure we don't helpfully keep an old keytab entry */
+ cli_credentials_set_kvno(item->creds, cli_credentials_get_kvno(item->creds)+2);
+ /* Wipe passwords */
+ cli_credentials_set_nt_hash(item->creds, NULL,
+ CRED_SPECIFIED);
+ }
+ DLIST_ADD_END(data->changed_dns, item, struct dn_list *);
+ }
+ return LDB_SUCCESS;
+}
+
+/* add */
+static int update_kt_add(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ ret = ldb_next_request(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return add_modified(module, req->op.add.message->dn, false);
+}
+
+/* modify */
+static int update_kt_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ ret = ldb_next_request(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return add_modified(module, req->op.mod.message->dn, false);
+}
+
+/* delete */
+static int update_kt_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ /* Before we delete it, record the details */
+ ret = add_modified(module, req->op.del.dn, true);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(module, req);
+}
+
+/* rename */
+static int update_kt_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ ret = ldb_next_request(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return add_modified(module, req->op.rename.newdn, false);
+}
+
+/* end a transaction */
+static int update_kt_end_trans(struct ldb_module *module)
+{
+ struct update_kt_private *data = talloc_get_type(module->private_data, struct update_kt_private);
+
+ struct dn_list *p;
+ for (p=data->changed_dns; p; p = p->next) {
+ int kret;
+ kret = cli_credentials_update_keytab(p->creds, ldb_get_opaque(module->ldb, "EventContext"), ldb_get_opaque(module->ldb, "loadparm"));
+ if (kret != 0) {
+ talloc_free(data->changed_dns);
+ data->changed_dns = NULL;
+ ldb_asprintf_errstring(module->ldb, "Failed to update keytab: %s", error_message(kret));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ talloc_free(data->changed_dns);
+ data->changed_dns = NULL;
+ return ldb_next_end_trans(module);
+}
+
+/* end a transaction */
+static int update_kt_del_trans(struct ldb_module *module)
+{
+ struct update_kt_private *data = talloc_get_type(module->private_data, struct update_kt_private);
+
+ talloc_free(data->changed_dns);
+ data->changed_dns = NULL;
+
+ return ldb_next_del_trans(module);
+}
+
+static int update_kt_init(struct ldb_module *module)
+{
+ struct update_kt_private *data;
+
+ data = talloc(module, struct update_kt_private);
+ if (data == NULL) {
+ ldb_oom(module->ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ module->private_data = data;
+ data->changed_dns = NULL;
+
+ return ldb_next_init(module);
+}
+
+_PUBLIC_ const struct ldb_module_ops ldb_update_keytab_module_ops = {
+ .name = "update_keytab",
+ .init_context = update_kt_init,
+ .add = update_kt_add,
+ .modify = update_kt_modify,
+ .rename = update_kt_rename,
+ .del = update_kt_delete,
+ .end_transaction = update_kt_end_trans,
+ .del_transaction = update_kt_del_trans,
+};