diff options
Diffstat (limited to 'source4/dsdb/samdb/ldb_modules')
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, +}; |