summaryrefslogtreecommitdiffstats
path: root/source4/dsdb/samdb/ldb_modules
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 17:47:29 +0000
commit4f5791ebd03eaec1c7da0865a383175b05102712 (patch)
tree8ce7b00f7a76baa386372422adebbe64510812d4 /source4/dsdb/samdb/ldb_modules
parentInitial commit. (diff)
downloadsamba-upstream.tar.xz
samba-upstream.zip
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/dsdb/samdb/ldb_modules')
-rw-r--r--source4/dsdb/samdb/ldb_modules/acl.c2585
-rw-r--r--source4/dsdb/samdb/ldb_modules/acl_read.c1293
-rw-r--r--source4/dsdb/samdb/ldb_modules/acl_util.c356
-rw-r--r--source4/dsdb/samdb/ldb_modules/anr.c436
-rw-r--r--source4/dsdb/samdb/ldb_modules/audit_log.c1913
-rw-r--r--source4/dsdb/samdb/ldb_modules/audit_util.c697
-rw-r--r--source4/dsdb/samdb/ldb_modules/count_attrs.c644
-rw-r--r--source4/dsdb/samdb/ldb_modules/descriptor.c1901
-rw-r--r--source4/dsdb/samdb/ldb_modules/dirsync.c1428
-rw-r--r--source4/dsdb/samdb/ldb_modules/dns_notify.c450
-rw-r--r--source4/dsdb/samdb/ldb_modules/dsdb_notification.c262
-rw-r--r--source4/dsdb/samdb/ldb_modules/encrypted_secrets.c1401
-rw-r--r--source4/dsdb/samdb/ldb_modules/extended_dn_in.c801
-rw-r--r--source4/dsdb/samdb/ldb_modules/extended_dn_out.c648
-rw-r--r--source4/dsdb/samdb/ldb_modules/extended_dn_store.c830
-rw-r--r--source4/dsdb/samdb/ldb_modules/group_audit.c1555
-rw-r--r--source4/dsdb/samdb/ldb_modules/instancetype.c173
-rw-r--r--source4/dsdb/samdb/ldb_modules/lazy_commit.c128
-rw-r--r--source4/dsdb/samdb/ldb_modules/linked_attributes.c1581
-rw-r--r--source4/dsdb/samdb/ldb_modules/netlogon.c510
-rw-r--r--source4/dsdb/samdb/ldb_modules/new_partition.c213
-rw-r--r--source4/dsdb/samdb/ldb_modules/objectclass.c1477
-rw-r--r--source4/dsdb/samdb/ldb_modules/objectclass_attrs.c744
-rw-r--r--source4/dsdb/samdb/ldb_modules/objectguid.c252
-rw-r--r--source4/dsdb/samdb/ldb_modules/operational.c1829
-rw-r--r--source4/dsdb/samdb/ldb_modules/paged_results.c855
-rw-r--r--source4/dsdb/samdb/ldb_modules/partition.c1721
-rw-r--r--source4/dsdb/samdb/ldb_modules/partition.h64
-rw-r--r--source4/dsdb/samdb/ldb_modules/partition_init.c885
-rw-r--r--source4/dsdb/samdb/ldb_modules/partition_metadata.c605
-rw-r--r--source4/dsdb/samdb/ldb_modules/password_hash.c5134
-rw-r--r--source4/dsdb/samdb/ldb_modules/password_modules.h3
-rw-r--r--source4/dsdb/samdb/ldb_modules/proxy.c415
-rw-r--r--source4/dsdb/samdb/ldb_modules/ranged_results.c295
-rw-r--r--source4/dsdb/samdb/ldb_modules/repl_meta_data.c8658
-rw-r--r--source4/dsdb/samdb/ldb_modules/resolve_oids.c704
-rw-r--r--source4/dsdb/samdb/ldb_modules/ridalloc.c829
-rw-r--r--source4/dsdb/samdb/ldb_modules/rootdse.c1796
-rw-r--r--source4/dsdb/samdb/ldb_modules/samba3sam.c977
-rw-r--r--source4/dsdb/samdb/ldb_modules/samba3sid.c207
-rw-r--r--source4/dsdb/samdb/ldb_modules/samba_dsdb.c608
-rw-r--r--source4/dsdb/samdb/ldb_modules/samba_secrets.c103
-rw-r--r--source4/dsdb/samdb/ldb_modules/samldb.c5724
-rw-r--r--source4/dsdb/samdb/ldb_modules/schema_data.c691
-rw-r--r--source4/dsdb/samdb/ldb_modules/schema_load.c657
-rw-r--r--source4/dsdb/samdb/ldb_modules/schema_util.c345
-rw-r--r--source4/dsdb/samdb/ldb_modules/secrets_tdb_sync.c532
-rw-r--r--source4/dsdb/samdb/ldb_modules/show_deleted.c220
-rw-r--r--source4/dsdb/samdb/ldb_modules/subtree_delete.c142
-rw-r--r--source4/dsdb/samdb/ldb_modules/subtree_rename.c202
-rwxr-xr-xsource4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py265
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c2305
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_audit_log_errors.c639
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c1260
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_encrypted_secrets.c973
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_group_audit.c2022
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_group_audit.valgrind19
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_group_audit_errors.c266
-rw-r--r--source4/dsdb/samdb/ldb_modules/tests/test_unique_object_sids.c514
-rw-r--r--source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c423
-rw-r--r--source4/dsdb/samdb/ldb_modules/unique_object_sids.c262
-rw-r--r--source4/dsdb/samdb/ldb_modules/update_keytab.c510
-rw-r--r--source4/dsdb/samdb/ldb_modules/util.c1890
-rw-r--r--source4/dsdb/samdb/ldb_modules/util.h42
-rw-r--r--source4/dsdb/samdb/ldb_modules/vlv_pagination.c946
-rw-r--r--source4/dsdb/samdb/ldb_modules/wscript38
-rw-r--r--source4/dsdb/samdb/ldb_modules/wscript_build60
-rw-r--r--source4/dsdb/samdb/ldb_modules/wscript_build_server539
68 files changed, 69452 insertions, 0 deletions
diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c
new file mode 100644
index 0000000..78a5ddf
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/acl.c
@@ -0,0 +1,2585 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2006-2008
+ Copyright (C) Nadezhda Ivanova 2009
+ Copyright (C) Anatoliy Atanasov 2009
+
+ 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 ACL module
+ *
+ * Description: Module that performs authorisation access checks based on the
+ * account's security context and the DACL of the object being polled.
+ * Only DACL checks implemented at this point
+ *
+ * Authors: Nadezhda Ivanova, Anatoliy Atanasov
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "auth/auth.h"
+#include "libcli/security/security.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "param/param.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "lib/util/tsort.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+
+#undef strcasecmp
+#undef strncasecmp
+
+struct acl_private {
+ bool acl_search;
+ const char **password_attrs;
+ void *cached_schema_ptr;
+ uint64_t cached_schema_metadata_usn;
+ uint64_t cached_schema_loaded_usn;
+ const char **confidential_attrs;
+};
+
+struct acl_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+ bool am_system;
+ bool am_administrator;
+ bool constructed_attrs;
+ bool allowedAttributes;
+ bool allowedAttributesEffective;
+ bool allowedChildClasses;
+ bool allowedChildClassesEffective;
+ bool sDRightsEffective;
+ struct dsdb_schema *schema;
+};
+
+static int acl_module_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb;
+ struct acl_private *data;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ data = talloc_zero(module, struct acl_private);
+ if (data == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ data->acl_search = lpcfg_parm_bool(ldb_get_opaque(ldb, "loadparm"),
+ NULL, "acl", "search", true);
+ ldb_module_set_private(module, data);
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_SD_FLAGS_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "acl_module_init: Unable to register control with rootdse!\n");
+ return ldb_operr(ldb);
+ }
+
+ return ldb_next_init(module);
+}
+
+static int acl_allowedAttributes(struct ldb_module *module,
+ const struct dsdb_schema *schema,
+ struct ldb_message *sd_msg,
+ struct ldb_message *msg,
+ struct acl_context *ac)
+{
+ struct ldb_message_element *oc_el;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *mem_ctx;
+ const char **attr_list;
+ int i, ret;
+ const struct dsdb_class *objectclass;
+
+ /* If we don't have a schema yet, we can't do anything... */
+ if (schema == NULL) {
+ ldb_asprintf_errstring(ldb, "cannot add allowedAttributes to %s because no schema is loaded", ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Must remove any existing attribute */
+ if (ac->allowedAttributes) {
+ ldb_msg_remove_attr(msg, "allowedAttributes");
+ }
+
+ mem_ctx = talloc_new(msg);
+ if (!mem_ctx) {
+ return ldb_oom(ldb);
+ }
+
+ oc_el = ldb_msg_find_element(sd_msg, "objectClass");
+ attr_list = dsdb_full_attribute_list(mem_ctx, schema, oc_el, DSDB_SCHEMA_ALL);
+ if (!attr_list) {
+ ldb_asprintf_errstring(ldb, "acl: Failed to get list of attributes");
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * Get the top-most structural object class for the ACL check
+ */
+ objectclass = dsdb_get_last_structural_class(ac->schema,
+ oc_el);
+ if (objectclass == NULL) {
+ ldb_asprintf_errstring(ldb, "acl_read: Failed to find a structural class for %s",
+ ldb_dn_get_linearized(sd_msg->dn));
+ talloc_free(mem_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (ac->allowedAttributes) {
+ for (i=0; attr_list && attr_list[i]; i++) {
+ ldb_msg_add_string(msg, "allowedAttributes", attr_list[i]);
+ }
+ }
+ if (ac->allowedAttributesEffective) {
+ struct security_descriptor *sd;
+ struct dom_sid *sid = NULL;
+ struct ldb_control *as_system = ldb_request_get_control(ac->req,
+ LDB_CONTROL_AS_SYSTEM_OID);
+
+ if (as_system != NULL) {
+ as_system->critical = 0;
+ }
+
+ ldb_msg_remove_attr(msg, "allowedAttributesEffective");
+ if (ac->am_system || as_system) {
+ for (i=0; attr_list && attr_list[i]; i++) {
+ ldb_msg_add_string(msg, "allowedAttributesEffective", attr_list[i]);
+ }
+ return LDB_SUCCESS;
+ }
+
+ ret = dsdb_get_sd_from_ldb_message(ldb_module_get_ctx(module), mem_ctx, sd_msg, &sd);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ sid = samdb_result_dom_sid(mem_ctx, sd_msg, "objectSid");
+ for (i=0; attr_list && attr_list[i]; i++) {
+ const struct dsdb_attribute *attr = dsdb_attribute_by_lDAPDisplayName(schema,
+ attr_list[i]);
+ if (!attr) {
+ return ldb_operr(ldb);
+ }
+ /* remove constructed attributes */
+ if (attr->systemFlags & DS_FLAG_ATTR_IS_CONSTRUCTED
+ || attr->systemOnly
+ || (attr->linkID != 0 && attr->linkID % 2 != 0 )) {
+ continue;
+ }
+ ret = acl_check_access_on_attribute(module,
+ msg,
+ sd,
+ sid,
+ SEC_ADS_WRITE_PROP,
+ attr,
+ objectclass);
+ if (ret == LDB_SUCCESS) {
+ ldb_msg_add_string(msg, "allowedAttributesEffective", attr_list[i]);
+ }
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+static int acl_childClasses(struct ldb_module *module,
+ const struct dsdb_schema *schema,
+ struct ldb_message *sd_msg,
+ struct ldb_message *msg,
+ const char *attrName)
+{
+ struct ldb_message_element *oc_el;
+ struct ldb_message_element *allowedClasses;
+ const struct dsdb_class *sclass;
+ unsigned int i, j;
+ int ret;
+
+ /* If we don't have a schema yet, we can't do anything... */
+ if (schema == NULL) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module), "cannot add childClassesEffective to %s because no schema is loaded", ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* 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;
+ }
+
+ oc_el = ldb_msg_find_element(sd_msg, "objectClass");
+
+ for (i=0; oc_el && i < oc_el->num_values; i++) {
+ sclass = dsdb_class_by_lDAPDisplayName_ldb_val(schema, &oc_el->values[i]);
+ if (!sclass) {
+ /* We don't know this class? what is going on? */
+ continue;
+ }
+
+ for (j=0; sclass->possibleInferiors && sclass->possibleInferiors[j]; j++) {
+ ldb_msg_add_string(msg, attrName, sclass->possibleInferiors[j]);
+ }
+ }
+ if (allowedClasses->num_values > 1) {
+ TYPESAFE_QSORT(allowedClasses->values, allowedClasses->num_values, 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 LDB_SUCCESS;
+}
+
+static int acl_childClassesEffective(struct ldb_module *module,
+ const struct dsdb_schema *schema,
+ struct ldb_message *sd_msg,
+ struct ldb_message *msg,
+ struct acl_context *ac)
+{
+ struct ldb_message_element *oc_el;
+ struct ldb_message_element *allowedClasses = NULL;
+ const struct dsdb_class *sclass;
+ struct security_descriptor *sd;
+ struct ldb_control *as_system = ldb_request_get_control(ac->req,
+ LDB_CONTROL_AS_SYSTEM_OID);
+ struct dom_sid *sid = NULL;
+ unsigned int i, j;
+ int ret;
+
+ if (as_system != NULL) {
+ as_system->critical = 0;
+ }
+
+ if (ac->am_system || as_system) {
+ return acl_childClasses(module, schema, sd_msg, msg, "allowedChildClassesEffective");
+ }
+
+ /* If we don't have a schema yet, we can't do anything... */
+ if (schema == NULL) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module), "cannot add allowedChildClassesEffective to %s because no schema is loaded", ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Must remove any existing attribute, or else confusion reins */
+ ldb_msg_remove_attr(msg, "allowedChildClassesEffective");
+
+ oc_el = ldb_msg_find_element(sd_msg, "objectClass");
+ ret = dsdb_get_sd_from_ldb_message(ldb_module_get_ctx(module), msg, sd_msg, &sd);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ sid = samdb_result_dom_sid(msg, sd_msg, "objectSid");
+ for (i=0; oc_el && i < oc_el->num_values; i++) {
+ sclass = dsdb_class_by_lDAPDisplayName_ldb_val(schema, &oc_el->values[i]);
+ if (!sclass) {
+ /* We don't know this class? what is going on? */
+ continue;
+ }
+
+ for (j=0; sclass->possibleInferiors && sclass->possibleInferiors[j]; j++) {
+ const struct dsdb_class *sc;
+
+ sc = dsdb_class_by_lDAPDisplayName(schema,
+ sclass->possibleInferiors[j]);
+ if (!sc) {
+ /* We don't know this class? what is going on? */
+ continue;
+ }
+
+ ret = acl_check_access_on_objectclass(module, ac,
+ sd, sid,
+ SEC_ADS_CREATE_CHILD,
+ sc);
+ if (ret == LDB_SUCCESS) {
+ ldb_msg_add_string(msg, "allowedChildClassesEffective",
+ sclass->possibleInferiors[j]);
+ }
+ }
+ }
+ allowedClasses = ldb_msg_find_element(msg, "allowedChildClassesEffective");
+ if (!allowedClasses) {
+ return LDB_SUCCESS;
+ }
+
+ if (allowedClasses->num_values > 1) {
+ TYPESAFE_QSORT(allowedClasses->values, allowedClasses->num_values, 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 LDB_SUCCESS;
+}
+
+static int acl_sDRightsEffective(struct ldb_module *module,
+ struct ldb_message *sd_msg,
+ struct ldb_message *msg,
+ struct acl_context *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_message_element *rightsEffective;
+ int ret;
+ struct security_descriptor *sd;
+ struct ldb_control *as_system = ldb_request_get_control(ac->req,
+ LDB_CONTROL_AS_SYSTEM_OID);
+ struct dom_sid *sid = NULL;
+ uint32_t flags = 0;
+
+ if (as_system != NULL) {
+ as_system->critical = 0;
+ }
+
+ /* Must remove any existing attribute, or else confusion reins */
+ ldb_msg_remove_attr(msg, "sDRightsEffective");
+ ret = ldb_msg_add_empty(msg, "sDRightsEffective", 0, &rightsEffective);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (ac->am_system || as_system) {
+ flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_SACL | SECINFO_DACL;
+ } else {
+ const struct dsdb_class *objectclass;
+ const struct dsdb_attribute *attr;
+
+ objectclass = dsdb_get_structural_oc_from_msg(ac->schema, sd_msg);
+ if (objectclass == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ attr = dsdb_attribute_by_lDAPDisplayName(ac->schema,
+ "nTSecurityDescriptor");
+ if (attr == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* Get the security descriptor from the message */
+ ret = dsdb_get_sd_from_ldb_message(ldb, msg, sd_msg, &sd);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ sid = samdb_result_dom_sid(msg, sd_msg, "objectSid");
+ ret = acl_check_access_on_attribute(module,
+ msg,
+ sd,
+ sid,
+ SEC_STD_WRITE_OWNER,
+ attr,
+ objectclass);
+ if (ret == LDB_SUCCESS) {
+ flags |= SECINFO_OWNER | SECINFO_GROUP;
+ }
+ ret = acl_check_access_on_attribute(module,
+ msg,
+ sd,
+ sid,
+ SEC_STD_WRITE_DAC,
+ attr,
+ objectclass);
+ if (ret == LDB_SUCCESS) {
+ flags |= SECINFO_DACL;
+ }
+ ret = acl_check_access_on_attribute(module,
+ msg,
+ sd,
+ sid,
+ SEC_FLAG_SYSTEM_SECURITY,
+ attr,
+ objectclass);
+ if (ret == LDB_SUCCESS) {
+ flags |= SECINFO_SACL;
+ }
+ }
+ return samdb_msg_add_uint(ldb_module_get_ctx(module), msg, msg,
+ "sDRightsEffective", flags);
+}
+
+static int acl_validate_spn_value(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ const struct ldb_val *spn_value,
+ uint32_t userAccountControl,
+ const struct ldb_val *samAccountName,
+ const struct ldb_val *dnsHostName,
+ const char *netbios_name,
+ const char *ntds_guid)
+{
+ int ret, princ_size;
+ krb5_context krb_ctx;
+ krb5_error_code kerr;
+ krb5_principal principal;
+ char *instanceName;
+ char *serviceType;
+ char *serviceName;
+ const char *spn_value_str = NULL;
+ size_t account_name_len;
+ const char *forest_name = samdb_forest_name(ldb, mem_ctx);
+ const char *base_domain = samdb_default_domain_name(ldb, mem_ctx);
+ struct loadparm_context *lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+ bool is_dc = (userAccountControl & UF_SERVER_TRUST_ACCOUNT) ||
+ (userAccountControl & UF_PARTIAL_SECRETS_ACCOUNT);
+
+ spn_value_str = talloc_strndup(mem_ctx,
+ (const char *)spn_value->data,
+ spn_value->length);
+ if (spn_value_str == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ if (spn_value->length == samAccountName->length &&
+ strncasecmp((const char *)spn_value->data,
+ (const char *)samAccountName->data,
+ spn_value->length) == 0)
+ {
+ /* MacOS X sets this value, and setting an SPN of your
+ * own samAccountName is both pointless and safe */
+ return LDB_SUCCESS;
+ }
+
+ kerr = smb_krb5_init_context_basic(mem_ctx,
+ lp_ctx,
+ &krb_ctx);
+ if (kerr != 0) {
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "Could not initialize kerberos context.");
+ }
+
+ ret = krb5_parse_name(krb_ctx, spn_value_str, &principal);
+ if (ret) {
+ krb5_free_context(krb_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ princ_size = krb5_princ_size(krb_ctx, principal);
+ if (princ_size < 2) {
+ DBG_WARNING("princ_size=%d\n", princ_size);
+ goto fail;
+ }
+
+ instanceName = smb_krb5_principal_get_comp_string(mem_ctx, krb_ctx,
+ principal, 1);
+ serviceType = smb_krb5_principal_get_comp_string(mem_ctx, krb_ctx,
+ principal, 0);
+ if (krb5_princ_size(krb_ctx, principal) == 3) {
+ serviceName = smb_krb5_principal_get_comp_string(mem_ctx, krb_ctx,
+ principal, 2);
+ } else {
+ serviceName = NULL;
+ }
+
+ if (serviceName) {
+ if (!is_dc) {
+ DBG_WARNING("is_dc=false, serviceName=%s,"
+ "serviceType=%s\n", serviceName,
+ serviceType);
+ goto fail;
+ }
+ if (strcasecmp(serviceType, "ldap") == 0) {
+ if (strcasecmp(serviceName, netbios_name) != 0 &&
+ strcasecmp(serviceName, forest_name) != 0) {
+ DBG_WARNING("serviceName=%s\n", serviceName);
+ goto fail;
+ }
+
+ } else if (strcasecmp(serviceType, "gc") == 0) {
+ if (strcasecmp(serviceName, forest_name) != 0) {
+ DBG_WARNING("serviceName=%s\n", serviceName);
+ goto fail;
+ }
+ } else {
+ if (strcasecmp(serviceName, base_domain) != 0 &&
+ strcasecmp(serviceName, netbios_name) != 0) {
+ DBG_WARNING("serviceType=%s, "
+ "serviceName=%s\n",
+ serviceType, serviceName);
+ goto fail;
+ }
+ }
+ }
+
+ account_name_len = samAccountName->length;
+ if (account_name_len &&
+ samAccountName->data[account_name_len - 1] == '$')
+ {
+ /* Account for the '$' character. */
+ --account_name_len;
+ }
+
+ /* instanceName can be samAccountName without $ or dnsHostName
+ * or "ntds_guid._msdcs.forest_domain for DC objects */
+ if (strlen(instanceName) == account_name_len
+ && strncasecmp(instanceName,
+ (const char *)samAccountName->data,
+ account_name_len) == 0)
+ {
+ goto success;
+ }
+ if ((dnsHostName != NULL) &&
+ strlen(instanceName) == dnsHostName->length &&
+ (strncasecmp(instanceName,
+ (const char *)dnsHostName->data,
+ dnsHostName->length) == 0))
+ {
+ goto success;
+ }
+ if (is_dc) {
+ const char *guid_str;
+ guid_str = talloc_asprintf(mem_ctx,"%s._msdcs.%s",
+ ntds_guid,
+ forest_name);
+ if (strcasecmp(instanceName, guid_str) == 0) {
+ goto success;
+ }
+ }
+
+fail:
+ krb5_free_principal(krb_ctx, principal);
+ krb5_free_context(krb_ctx);
+ ldb_debug_set(ldb, LDB_DEBUG_WARNING,
+ "acl: spn validation failed for "
+ "spn[%.*s] uac[0x%x] account[%.*s] hostname[%.*s] "
+ "nbname[%s] ntds[%s] forest[%s] domain[%s]\n",
+ (int)spn_value->length, spn_value->data,
+ (unsigned)userAccountControl,
+ (int)samAccountName->length, samAccountName->data,
+ dnsHostName != NULL ? (int)dnsHostName->length : 0,
+ dnsHostName != NULL ? (const char *)dnsHostName->data : "",
+ netbios_name, ntds_guid,
+ forest_name, base_domain);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+
+success:
+ krb5_free_principal(krb_ctx, principal);
+ krb5_free_context(krb_ctx);
+ return LDB_SUCCESS;
+}
+
+/*
+ * Passing in 'el' is critical, we want to check all the values.
+ *
+ */
+static int acl_check_spn(TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ struct ldb_request *req,
+ const struct ldb_message_element *el,
+ struct security_descriptor *sd,
+ struct dom_sid *sid,
+ const struct dsdb_attribute *attr,
+ const struct dsdb_class *objectclass,
+ const struct ldb_control *implicit_validated_write_control)
+{
+ int ret;
+ unsigned int i;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_result *acl_res;
+ struct ldb_result *netbios_res;
+ struct ldb_dn *partitions_dn = samdb_partitions_dn(ldb, tmp_ctx);
+ uint32_t userAccountControl;
+ const char *netbios_name;
+ const struct ldb_val *dns_host_name_val = NULL;
+ const struct ldb_val *sam_account_name_val = NULL;
+ struct GUID ntds;
+ char *ntds_guid = NULL;
+
+ static const char *acl_attrs[] = {
+ "samAccountName",
+ "dnsHostName",
+ "userAccountControl",
+ NULL
+ };
+ static const char *netbios_attrs[] = {
+ "nETBIOSName",
+ NULL
+ };
+
+ if (implicit_validated_write_control != NULL) {
+ /*
+ * The validated write control dispenses with ACL
+ * checks. We act as if we have an implicit Self Write
+ * privilege, but, assuming we don't have Write
+ * Property, still proceed with further validation
+ * checks.
+ */
+ } else {
+ /* if we have wp, we can do whatever we like */
+ if (acl_check_access_on_attribute(module,
+ tmp_ctx,
+ sd,
+ sid,
+ SEC_ADS_WRITE_PROP,
+ attr, objectclass) == LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ ret = acl_check_extended_right(tmp_ctx,
+ module,
+ req,
+ objectclass,
+ sd,
+ acl_user_token(module),
+ GUID_DRS_VALIDATE_SPN,
+ SEC_ADS_SELF_WRITE,
+ sid);
+
+ if (ret != LDB_SUCCESS) {
+ dsdb_acl_debug(sd, acl_user_token(module),
+ req->op.mod.message->dn,
+ true,
+ 10);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ /*
+ * If we have "validated write spn", allow delete of any
+ * existing value (this keeps constrained delete to the same
+ * rules as unconstrained)
+ */
+ if (req->operation == LDB_MODIFY) {
+ /*
+ * If not add or replace (eg delete),
+ * return success
+ */
+ if (LDB_FLAG_MOD_TYPE(el->flags) != LDB_FLAG_MOD_ADD &&
+ LDB_FLAG_MOD_TYPE(el->flags) != LDB_FLAG_MOD_REPLACE)
+ {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+ }
+
+ ret = dsdb_module_search_dn(module, tmp_ctx,
+ &acl_res, req->op.mod.message->dn,
+ acl_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ dns_host_name_val = ldb_msg_find_ldb_val(acl_res->msgs[0], "dNSHostName");
+
+ ret = dsdb_msg_get_single_value(req->op.mod.message,
+ "dNSHostName",
+ dns_host_name_val,
+ &dns_host_name_val,
+ req->operation);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ userAccountControl = ldb_msg_find_attr_as_uint(acl_res->msgs[0], "userAccountControl", 0);
+
+ sam_account_name_val = ldb_msg_find_ldb_val(acl_res->msgs[0], "sAMAccountName");
+
+ ret = dsdb_msg_get_single_value(req->op.mod.message,
+ "sAMAccountName",
+ sam_account_name_val,
+ &sam_account_name_val,
+ req->operation);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_module_search(module, tmp_ctx,
+ &netbios_res, partitions_dn,
+ LDB_SCOPE_ONELEVEL,
+ netbios_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM,
+ req,
+ "(ncName=%s)",
+ ldb_dn_get_linearized(ldb_get_default_basedn(ldb)));
+
+ netbios_name = ldb_msg_find_attr_as_string(netbios_res->msgs[0], "nETBIOSName", NULL);
+
+ /* NTDSDSA objectGuid of object we are checking SPN for */
+ if (userAccountControl & (UF_SERVER_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)) {
+ ret = dsdb_module_find_ntdsguid_for_computer(module, tmp_ctx,
+ req->op.mod.message->dn, &ntds, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to find NTDSDSA objectGuid for %s: %s",
+ ldb_dn_get_linearized(req->op.mod.message->dn),
+ ldb_strerror(ret));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ntds_guid = GUID_string(tmp_ctx, &ntds);
+ }
+
+ for (i=0; i < el->num_values; i++) {
+ ret = acl_validate_spn_value(tmp_ctx,
+ ldb,
+ &el->values[i],
+ userAccountControl,
+ sam_account_name_val,
+ dns_host_name_val,
+ netbios_name,
+ ntds_guid);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+static int acl_check_dns_host_name(TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ struct ldb_request *req,
+ const struct ldb_message_element *el,
+ struct security_descriptor *sd,
+ struct dom_sid *sid,
+ const struct dsdb_attribute *attr,
+ const struct dsdb_class *objectclass,
+ const struct ldb_control *implicit_validated_write_control)
+{
+ int ret;
+ unsigned i;
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const struct dsdb_schema *schema = NULL;
+ const struct ldb_message_element *allowed_suffixes = NULL;
+ struct ldb_result *nc_res = NULL;
+ struct ldb_dn *nc_root = NULL;
+ const char *nc_dns_name = NULL;
+ const char *dnsHostName_str = NULL;
+ size_t dns_host_name_len;
+ size_t account_name_len;
+ const struct ldb_message *msg = NULL;
+ const struct ldb_message *search_res = NULL;
+ const struct ldb_val *samAccountName = NULL;
+ const struct ldb_val *dnsHostName = NULL;
+ const struct dsdb_class *computer_objectclass = NULL;
+ bool is_subclass;
+
+ static const char *nc_attrs[] = {
+ "msDS-AllowedDNSSuffixes",
+ NULL
+ };
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ if (implicit_validated_write_control != NULL) {
+ /*
+ * The validated write control dispenses with ACL
+ * checks. We act as if we have an implicit Self Write
+ * privilege, but, assuming we don't have Write
+ * Property, still proceed with further validation
+ * checks.
+ */
+ } else {
+ /* if we have wp, we can do whatever we like */
+ ret = acl_check_access_on_attribute(module,
+ tmp_ctx,
+ sd,
+ sid,
+ SEC_ADS_WRITE_PROP,
+ attr, objectclass);
+ if (ret == LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ ret = acl_check_extended_right(tmp_ctx,
+ module,
+ req,
+ objectclass,
+ sd,
+ acl_user_token(module),
+ GUID_DRS_DNS_HOST_NAME,
+ SEC_ADS_SELF_WRITE,
+ sid);
+
+ if (ret != LDB_SUCCESS) {
+ dsdb_acl_debug(sd, acl_user_token(module),
+ req->op.mod.message->dn,
+ true,
+ 10);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ /*
+ * If we have "validated write dnshostname", allow delete of
+ * any existing value (this keeps constrained delete to the
+ * same rules as unconstrained)
+ */
+ if (req->operation == LDB_MODIFY) {
+ struct ldb_result *acl_res = NULL;
+
+ static const char *acl_attrs[] = {
+ "sAMAccountName",
+ NULL
+ };
+
+ msg = req->op.mod.message;
+
+ /*
+ * If not add or replace (eg delete),
+ * return success
+ */
+ if ((el->flags
+ & (LDB_FLAG_MOD_ADD|LDB_FLAG_MOD_REPLACE)) == 0)
+ {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ ret = dsdb_module_search_dn(module, tmp_ctx,
+ &acl_res, msg->dn,
+ acl_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ search_res = acl_res->msgs[0];
+ } else if (req->operation == LDB_ADD) {
+ msg = req->op.add.message;
+ search_res = msg;
+ } else {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Check if the account has objectclass 'computer' or 'server'. */
+
+ schema = dsdb_get_schema(ldb, req);
+ if (schema == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ computer_objectclass = dsdb_class_by_lDAPDisplayName(schema, "computer");
+ if (computer_objectclass == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ is_subclass = dsdb_is_subclass_of(schema, objectclass, computer_objectclass);
+ if (!is_subclass) {
+ /* The account is not a computer -- check if it's a server. */
+
+ const struct dsdb_class *server_objectclass = NULL;
+
+ server_objectclass = dsdb_class_by_lDAPDisplayName(schema, "server");
+ if (server_objectclass == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ is_subclass = dsdb_is_subclass_of(schema, objectclass, server_objectclass);
+ if (!is_subclass) {
+ /* Not a computer or server, so no need to validate. */
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+ }
+
+ samAccountName = ldb_msg_find_ldb_val(search_res, "sAMAccountName");
+
+ ret = dsdb_msg_get_single_value(msg,
+ "sAMAccountName",
+ samAccountName,
+ &samAccountName,
+ req->operation);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ account_name_len = samAccountName->length;
+ if (account_name_len && samAccountName->data[account_name_len - 1] == '$') {
+ /* Account for the '$' character. */
+ --account_name_len;
+ }
+
+ /* Check for add or replace requests with no value. */
+ if (el->num_values == 0) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+ dnsHostName = &el->values[0];
+
+ dnsHostName_str = (const char *)dnsHostName->data;
+ dns_host_name_len = dnsHostName->length;
+
+ /* Check that sAMAccountName matches the new dNSHostName. */
+
+ if (dns_host_name_len < account_name_len) {
+ goto fail;
+ }
+ if (strncasecmp(dnsHostName_str,
+ (const char *)samAccountName->data,
+ account_name_len) != 0)
+ {
+ goto fail;
+ }
+
+ dnsHostName_str += account_name_len;
+ dns_host_name_len -= account_name_len;
+
+ /* Check the '.' character */
+
+ if (dns_host_name_len == 0 || *dnsHostName_str != '.') {
+ goto fail;
+ }
+
+ ++dnsHostName_str;
+ --dns_host_name_len;
+
+ /* Now we check the suffix. */
+
+ ret = dsdb_find_nc_root(ldb,
+ tmp_ctx,
+ search_res->dn,
+ &nc_root);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ nc_dns_name = samdb_dn_to_dns_domain(tmp_ctx, nc_root);
+ if (nc_dns_name == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ if (strlen(nc_dns_name) == dns_host_name_len &&
+ strncasecmp(dnsHostName_str,
+ nc_dns_name,
+ dns_host_name_len) == 0)
+ {
+ /* It matches -- success. */
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ /* We didn't get a match, so now try msDS-AllowedDNSSuffixes. */
+
+ ret = dsdb_module_search_dn(module, tmp_ctx,
+ &nc_res, nc_root,
+ nc_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ allowed_suffixes = ldb_msg_find_element(nc_res->msgs[0],
+ "msDS-AllowedDNSSuffixes");
+ if (allowed_suffixes == NULL) {
+ goto fail;
+ }
+
+ for (i = 0; i < allowed_suffixes->num_values; ++i) {
+ const struct ldb_val *suffix = &allowed_suffixes->values[i];
+
+ if (suffix->length == dns_host_name_len &&
+ strncasecmp(dnsHostName_str,
+ (const char *)suffix->data,
+ dns_host_name_len) == 0)
+ {
+ /* It matches -- success. */
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+ }
+
+fail:
+ ldb_debug_set(ldb, LDB_DEBUG_WARNING,
+ "acl: hostname validation failed for "
+ "hostname[%.*s] account[%.*s]\n",
+ (int)dnsHostName->length, dnsHostName->data,
+ (int)samAccountName->length, samAccountName->data);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+}
+
+static int acl_add(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ struct ldb_dn *parent;
+ struct ldb_context *ldb;
+ const struct dsdb_schema *schema;
+ const struct dsdb_class *objectclass;
+ struct ldb_control *as_system;
+ struct ldb_message_element *el;
+ unsigned int instanceType = 0;
+
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID);
+ if (as_system != NULL) {
+ as_system->critical = 0;
+ }
+
+ if (dsdb_module_am_system(module) || as_system) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ parent = ldb_dn_get_parent(req, req->op.add.message->dn);
+ if (parent == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ schema = dsdb_get_schema(ldb, req);
+ if (!schema) {
+ return ldb_operr(ldb);
+ }
+
+ objectclass = dsdb_get_structural_oc_from_msg(schema, req->op.add.message);
+ if (!objectclass) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "acl: unable to find or validate structural objectClass on %s\n",
+ ldb_dn_get_linearized(req->op.add.message->dn));
+ return ldb_module_done(req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ el = ldb_msg_find_element(req->op.add.message, "instanceType");
+ if ((el != NULL) && (el->num_values != 1)) {
+ ldb_set_errstring(ldb, "acl: the 'instanceType' attribute is single-valued!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ instanceType = ldb_msg_find_attr_as_uint(req->op.add.message,
+ "instanceType", 0);
+ if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
+ static const char *no_attrs[] = { NULL };
+ struct ldb_result *partition_res;
+ struct ldb_dn *partitions_dn;
+
+ partitions_dn = samdb_partitions_dn(ldb, req);
+ if (!partitions_dn) {
+ ldb_set_errstring(ldb, "acl: CN=partitions dn could not be generated!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ ret = dsdb_module_search(module, req, &partition_res,
+ partitions_dn, LDB_SCOPE_ONELEVEL,
+ no_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_ONE_ONLY |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ req,
+ "(&(nCName=%s)(objectClass=crossRef))",
+ ldb_dn_get_linearized(req->op.add.message->dn));
+
+ if (ret == LDB_SUCCESS) {
+ /* Check that we can write to the crossRef object MS-ADTS 3.1.1.5.2.8.2 */
+ ret = dsdb_module_check_access_on_dn(module, req, partition_res->msgs[0]->dn,
+ SEC_ADS_WRITE_PROP,
+ &objectclass->schemaIDGUID, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "acl: ACL check failed on crossRef object %s: %s\n",
+ ldb_dn_get_linearized(partition_res->msgs[0]->dn),
+ ldb_errstring(ldb));
+ return ret;
+ }
+
+ /*
+ * TODO: Remaining checks, like if we are
+ * the naming master etc need to be handled
+ * in the instanceType module
+ */
+ return ldb_next_request(module, req);
+ }
+
+ /* Check that we can create a crossRef object MS-ADTS 3.1.1.5.2.8.2 */
+ ret = dsdb_module_check_access_on_dn(module, req, partitions_dn,
+ SEC_ADS_CREATE_CHILD,
+ &objectclass->schemaIDGUID, req);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT &&
+ ldb_request_get_control(req, LDB_CONTROL_RELAX_OID))
+ {
+ /* Allow provision bootstrap */
+ ret = LDB_SUCCESS;
+ }
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "acl: ACL check failed on CN=Partitions crossRef container %s: %s\n",
+ ldb_dn_get_linearized(partitions_dn), ldb_errstring(ldb));
+ return ret;
+ }
+
+ /*
+ * TODO: Remaining checks, like if we are the naming
+ * master and adding the crossRef object need to be
+ * handled in the instanceType module
+ */
+ return ldb_next_request(module, req);
+ }
+
+ ret = dsdb_module_check_access_on_dn(module, req, parent,
+ SEC_ADS_CREATE_CHILD,
+ &objectclass->schemaIDGUID, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "acl: unable to get access to %s\n",
+ ldb_dn_get_linearized(req->op.add.message->dn));
+ return ret;
+ }
+ return ldb_next_request(module, req);
+}
+
+/* checks if modifications are allowed on "Member" attribute */
+static int acl_check_self_membership(TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ struct ldb_request *req,
+ struct security_descriptor *sd,
+ struct dom_sid *sid,
+ const struct dsdb_attribute *attr,
+ const struct dsdb_class *objectclass)
+{
+ int ret;
+ unsigned int i;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_dn *user_dn;
+ struct ldb_message_element *member_el;
+ const struct ldb_message *msg = NULL;
+
+ if (req->operation == LDB_MODIFY) {
+ msg = req->op.mod.message;
+ } else if (req->operation == LDB_ADD) {
+ msg = req->op.add.message;
+ } else {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* if we have wp, we can do whatever we like */
+ if (acl_check_access_on_attribute(module,
+ mem_ctx,
+ sd,
+ sid,
+ SEC_ADS_WRITE_PROP,
+ attr, objectclass) == LDB_SUCCESS) {
+ return LDB_SUCCESS;
+ }
+ /* if we are adding/deleting ourselves, check for self membership */
+ ret = dsdb_find_dn_by_sid(ldb, mem_ctx,
+ &acl_user_token(module)->sids[PRIMARY_USER_SID_INDEX],
+ &user_dn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ member_el = ldb_msg_find_element(msg, "member");
+ if (!member_el) {
+ return ldb_operr(ldb);
+ }
+ /* user can only remove oneself */
+ if (member_el->num_values == 0) {
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+ for (i = 0; i < member_el->num_values; i++) {
+ if (strcasecmp((const char *)member_el->values[i].data,
+ ldb_dn_get_extended_linearized(mem_ctx, user_dn, 1)) != 0) {
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+ }
+ ret = acl_check_extended_right(mem_ctx,
+ module,
+ req,
+ objectclass,
+ sd,
+ acl_user_token(module),
+ GUID_DRS_SELF_MEMBERSHIP,
+ SEC_ADS_SELF_WRITE,
+ sid);
+ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ dsdb_acl_debug(sd, acl_user_token(module),
+ msg->dn,
+ true,
+ 10);
+ }
+ return ret;
+}
+
+static int acl_check_password_rights(
+ TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ struct ldb_request *req,
+ struct security_descriptor *sd,
+ struct dom_sid *sid,
+ const struct dsdb_class *objectclass,
+ bool userPassword,
+ struct dsdb_control_password_acl_validation **control_for_response)
+{
+ int ret = LDB_SUCCESS;
+ unsigned int del_attr_cnt = 0, add_attr_cnt = 0, rep_attr_cnt = 0;
+ unsigned int del_val_cnt = 0, add_val_cnt = 0, rep_val_cnt = 0;
+ struct ldb_message_element *el;
+ struct ldb_message *msg;
+ struct ldb_control *c = NULL;
+ const char *passwordAttrs[] = { "userPassword", "clearTextPassword",
+ "unicodePwd", NULL }, **l;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ struct dsdb_control_password_acl_validation *pav = NULL;
+
+ if (tmp_ctx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ pav = talloc_zero(req, struct dsdb_control_password_acl_validation);
+ if (pav == NULL) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /*
+ * Set control_for_response to pav so it can be added to the response
+ * and be passed up to the audit_log module which uses it to identify
+ * password reset attempts.
+ */
+ *control_for_response = pav;
+
+ c = ldb_request_get_control(req, DSDB_CONTROL_PASSWORD_CHANGE_OLD_PW_CHECKED_OID);
+ if (c != NULL) {
+ pav->pwd_reset = false;
+
+ /*
+ * The "DSDB_CONTROL_PASSWORD_CHANGE_OLD_PW_CHECKED_OID" control means that we
+ * have a user password change and not a set as the message
+ * looks like. In it's value blob it contains the NT and/or LM
+ * hash of the old password specified by the user. This control
+ * is used by the SAMR and "kpasswd" password change mechanisms.
+ *
+ * This control can't be used by real LDAP clients,
+ * the only caller is samdb_set_password_internal(),
+ * so we don't have to strict verification of the input.
+ */
+ ret = acl_check_extended_right(tmp_ctx,
+ module,
+ req,
+ objectclass,
+ sd,
+ acl_user_token(module),
+ GUID_DRS_USER_CHANGE_PASSWORD,
+ SEC_ADS_CONTROL_ACCESS,
+ sid);
+ goto checked;
+ }
+
+ c = ldb_request_get_control(req, DSDB_CONTROL_PASSWORD_HASH_VALUES_OID);
+ if (c != NULL) {
+ pav->pwd_reset = true;
+
+ /*
+ * The "DSDB_CONTROL_PASSWORD_HASH_VALUES_OID" control, without
+ * "DSDB_CONTROL_PASSWORD_CHANGE_OLD_PW_CHECKED_OID" control means that we
+ * have a force password set.
+ * This control is used by the SAMR/NETLOGON/LSA password
+ * reset mechanisms.
+ *
+ * This control can't be used by real LDAP clients,
+ * the only caller is samdb_set_password_internal(),
+ * so we don't have to strict verification of the input.
+ */
+ ret = acl_check_extended_right(tmp_ctx,
+ module,
+ req,
+ objectclass,
+ sd,
+ acl_user_token(module),
+ GUID_DRS_FORCE_CHANGE_PASSWORD,
+ SEC_ADS_CONTROL_ACCESS,
+ sid);
+ goto checked;
+ }
+
+ el = ldb_msg_find_element(req->op.mod.message, "dBCSPwd");
+ if (el != NULL) {
+ /*
+ * dBCSPwd is only allowed with a control.
+ */
+ talloc_free(tmp_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ msg = ldb_msg_copy_shallow(tmp_ctx, req->op.mod.message);
+ if (msg == NULL) {
+ return ldb_module_oom(module);
+ }
+ for (l = passwordAttrs; *l != NULL; l++) {
+ if ((!userPassword) && (ldb_attr_cmp(*l, "userPassword") == 0)) {
+ continue;
+ }
+
+ while ((el = ldb_msg_find_element(msg, *l)) != NULL) {
+ if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE) {
+ ++del_attr_cnt;
+ del_val_cnt += el->num_values;
+ }
+ if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_ADD) {
+ ++add_attr_cnt;
+ add_val_cnt += el->num_values;
+ }
+ if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_REPLACE) {
+ ++rep_attr_cnt;
+ rep_val_cnt += el->num_values;
+ }
+ ldb_msg_remove_element(msg, el);
+ }
+ }
+
+ /* single deletes will be handled by the "password_hash" LDB module
+ * later in the stack, so we let it though here */
+ if ((del_attr_cnt > 0) && (add_attr_cnt == 0) && (rep_attr_cnt == 0)) {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+
+ if (rep_attr_cnt > 0) {
+ pav->pwd_reset = true;
+
+ ret = acl_check_extended_right(tmp_ctx,
+ module,
+ req,
+ objectclass,
+ sd,
+ acl_user_token(module),
+ GUID_DRS_FORCE_CHANGE_PASSWORD,
+ SEC_ADS_CONTROL_ACCESS,
+ sid);
+ goto checked;
+ }
+
+ if (add_attr_cnt != del_attr_cnt) {
+ pav->pwd_reset = true;
+
+ ret = acl_check_extended_right(tmp_ctx,
+ module,
+ req,
+ objectclass,
+ sd,
+ acl_user_token(module),
+ GUID_DRS_FORCE_CHANGE_PASSWORD,
+ SEC_ADS_CONTROL_ACCESS,
+ sid);
+ goto checked;
+ }
+
+ if (add_val_cnt == 1 && del_val_cnt == 1) {
+ pav->pwd_reset = false;
+
+ ret = acl_check_extended_right(tmp_ctx,
+ module,
+ req,
+ objectclass,
+ sd,
+ acl_user_token(module),
+ GUID_DRS_USER_CHANGE_PASSWORD,
+ SEC_ADS_CONTROL_ACCESS,
+ sid);
+ /* Very strange, but we get constraint violation in this case */
+ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ goto checked;
+ }
+
+ if (add_val_cnt == 1 && del_val_cnt == 0) {
+ pav->pwd_reset = true;
+
+ ret = acl_check_extended_right(tmp_ctx,
+ module,
+ req,
+ objectclass,
+ sd,
+ acl_user_token(module),
+ GUID_DRS_FORCE_CHANGE_PASSWORD,
+ SEC_ADS_CONTROL_ACCESS,
+ sid);
+ /* Very strange, but we get constraint violation in this case */
+ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ goto checked;
+ }
+
+ /*
+ * Everything else is handled by the password_hash module where it will
+ * fail, but with the correct error code when the module is again
+ * checking the attributes. As the change request will lack the
+ * DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID control, we can be sure that
+ * any modification attempt that went this way will be rejected.
+ */
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+
+checked:
+ if (ret != LDB_SUCCESS) {
+ dsdb_acl_debug(sd, acl_user_token(module),
+ req->op.mod.message->dn,
+ true,
+ 10);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = ldb_request_add_control(req,
+ DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID, false, pav);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_ERROR,
+ "Unable to register ACL validation control!\n");
+ return ret;
+ }
+ return LDB_SUCCESS;
+}
+
+/*
+ * Context needed by acl_callback
+ */
+struct acl_callback_context {
+ struct ldb_request *request;
+ struct ldb_module *module;
+};
+
+/*
+ * @brief Copy the password validation control to the reply.
+ *
+ * Copy the dsdb_control_password_acl_validation control from the request,
+ * to the reply. The control is used by the audit_log module to identify
+ * password rests.
+ *
+ * @param req the ldb request.
+ * @param ares the result, updated with the control.
+ */
+static void copy_password_acl_validation_control(
+ struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct ldb_control *pav_ctrl = NULL;
+ struct dsdb_control_password_acl_validation *pav = NULL;
+
+ pav_ctrl = ldb_request_get_control(
+ discard_const(req),
+ DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID);
+ if (pav_ctrl == NULL) {
+ return;
+ }
+
+ pav = talloc_get_type_abort(
+ pav_ctrl->data,
+ struct dsdb_control_password_acl_validation);
+ if (pav == NULL) {
+ return;
+ }
+ ldb_reply_add_control(
+ ares,
+ DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID,
+ false,
+ pav);
+}
+/*
+ * @brief call back function for acl_modify.
+ *
+ * Calls acl_copy to copy the dsdb_control_password_acl_validation from
+ * the request to the reply.
+ *
+ * @param req the ldb_request.
+ * @param ares the operation result.
+ *
+ * @return the LDB_STATUS
+ */
+static int acl_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct acl_callback_context *ac = NULL;
+
+ ac = talloc_get_type(req->context, struct acl_callback_context);
+
+ if (!ares) {
+ return ldb_module_done(
+ ac->request,
+ NULL,
+ NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* pass on to the callback */
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ return ldb_module_send_entry(
+ ac->request,
+ ares->message,
+ ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(
+ ac->request,
+ ares->referral);
+
+ case LDB_REPLY_DONE:
+ /*
+ * Copy the ACL control from the request to the response
+ */
+ copy_password_acl_validation_control(req, ares);
+ return ldb_module_done(
+ ac->request,
+ ares->controls,
+ ares->response,
+ ares->error);
+
+ default:
+ /* Can't happen */
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+}
+
+static int acl_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const struct dsdb_schema *schema;
+ unsigned int i;
+ const struct dsdb_class *objectclass;
+ struct ldb_result *acl_res;
+ struct security_descriptor *sd;
+ struct dom_sid *sid = NULL;
+ struct ldb_control *as_system;
+ struct ldb_control *is_undelete;
+ struct ldb_control *implicit_validated_write_control = NULL;
+ bool userPassword;
+ bool password_rights_checked = false;
+ TALLOC_CTX *tmp_ctx;
+ const struct ldb_message *msg = req->op.mod.message;
+ static const char *acl_attrs[] = {
+ "nTSecurityDescriptor",
+ "objectClass",
+ "objectSid",
+ NULL
+ };
+ struct acl_callback_context *context = NULL;
+ struct ldb_request *new_req = NULL;
+ struct dsdb_control_password_acl_validation *pav = NULL;
+ struct ldb_control **controls = NULL;
+
+ if (ldb_dn_is_special(msg->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID);
+ if (as_system != NULL) {
+ as_system->critical = 0;
+ }
+
+ is_undelete = ldb_request_get_control(req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID);
+
+ implicit_validated_write_control = ldb_request_get_control(
+ req, DSDB_CONTROL_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE_OID);
+ if (implicit_validated_write_control != NULL) {
+ implicit_validated_write_control->critical = 0;
+ }
+
+ /* Don't print this debug statement if elements[0].name is going to be NULL */
+ if (msg->num_elements > 0) {
+ DEBUG(10, ("ldb:acl_modify: %s\n", msg->elements[0].name));
+ }
+ if (dsdb_module_am_system(module) || as_system) {
+ return ldb_next_request(module, req);
+ }
+
+ tmp_ctx = talloc_new(req);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &acl_res, msg->dn,
+ acl_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ req);
+
+ if (ret != LDB_SUCCESS) {
+ goto fail;
+ }
+
+ userPassword = dsdb_user_password_support(module, req, req);
+
+ schema = dsdb_get_schema(ldb, tmp_ctx);
+ if (!schema) {
+ talloc_free(tmp_ctx);
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "acl_modify: Error obtaining schema.");
+ }
+
+ ret = dsdb_get_sd_from_ldb_message(ldb, tmp_ctx, acl_res->msgs[0], &sd);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "acl_modify: Error retrieving security descriptor.");
+ }
+ /* Theoretically we pass the check if the object has no sd */
+ if (!sd) {
+ goto success;
+ }
+
+ objectclass = dsdb_get_structural_oc_from_msg(schema, acl_res->msgs[0]);
+ if (!objectclass) {
+ talloc_free(tmp_ctx);
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "acl_modify: Error retrieving object class for GUID.");
+ }
+ sid = samdb_result_dom_sid(req, acl_res->msgs[0], "objectSid");
+ for (i=0; i < msg->num_elements; i++) {
+ const struct ldb_message_element *el = &msg->elements[i];
+ const struct dsdb_attribute *attr;
+
+ /*
+ * This basic attribute existence check with the right errorcode
+ * is needed since this module is the first one which requests
+ * schema attribute information.
+ * The complete attribute checking is done in the
+ * "objectclass_attrs" module behind this one.
+ *
+ * NOTE: "clearTextPassword" is not defined in the schema.
+ */
+ attr = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (!attr && ldb_attr_cmp("clearTextPassword", el->name) != 0) {
+ ldb_asprintf_errstring(ldb, "acl_modify: attribute '%s' "
+ "on entry '%s' was not found in the schema!",
+ req->op.mod.message->elements[i].name,
+ ldb_dn_get_linearized(req->op.mod.message->dn));
+ ret = LDB_ERR_NO_SUCH_ATTRIBUTE;
+ goto fail;
+ }
+
+ if (ldb_attr_cmp("nTSecurityDescriptor", el->name) == 0) {
+ uint32_t sd_flags = dsdb_request_sd_flags(req, NULL);
+ uint32_t access_mask = 0;
+
+ if (sd_flags & (SECINFO_OWNER|SECINFO_GROUP)) {
+ access_mask |= SEC_STD_WRITE_OWNER;
+ }
+ if (sd_flags & SECINFO_DACL) {
+ access_mask |= SEC_STD_WRITE_DAC;
+ }
+ if (sd_flags & SECINFO_SACL) {
+ access_mask |= SEC_FLAG_SYSTEM_SECURITY;
+ }
+
+ ret = acl_check_access_on_attribute(module,
+ tmp_ctx,
+ sd,
+ sid,
+ access_mask,
+ attr,
+ objectclass);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "Object %s has no write dacl access\n",
+ ldb_dn_get_linearized(msg->dn));
+ dsdb_acl_debug(sd,
+ acl_user_token(module),
+ msg->dn,
+ true,
+ 10);
+ ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ goto fail;
+ }
+ } else if (ldb_attr_cmp("member", el->name) == 0) {
+ ret = acl_check_self_membership(tmp_ctx,
+ module,
+ req,
+ sd,
+ sid,
+ attr,
+ objectclass);
+ if (ret != LDB_SUCCESS) {
+ goto fail;
+ }
+ } else if (ldb_attr_cmp("dBCSPwd", el->name) == 0) {
+ /* this one is not affected by any rights, we should let it through
+ so that passwords_hash returns the correct error */
+ continue;
+ } else if (ldb_attr_cmp("unicodePwd", el->name) == 0 ||
+ (userPassword && ldb_attr_cmp("userPassword", el->name) == 0) ||
+ ldb_attr_cmp("clearTextPassword", el->name) == 0) {
+ /*
+ * Ideally we would do the acl_check_password_rights
+ * before we checked the other attributes, i.e. in a
+ * loop before the current one.
+ * Have not done this as yet in order to limit the size
+ * of the change. To limit the possibility of breaking
+ * the ACL logic.
+ */
+ if (password_rights_checked) {
+ continue;
+ }
+ ret = acl_check_password_rights(tmp_ctx,
+ module,
+ req,
+ sd,
+ sid,
+ objectclass,
+ userPassword,
+ &pav);
+ if (ret != LDB_SUCCESS) {
+ goto fail;
+ }
+ password_rights_checked = true;
+ } else if (ldb_attr_cmp("servicePrincipalName", el->name) == 0) {
+ ret = acl_check_spn(tmp_ctx,
+ module,
+ req,
+ el,
+ sd,
+ sid,
+ attr,
+ objectclass,
+ implicit_validated_write_control);
+ if (ret != LDB_SUCCESS) {
+ goto fail;
+ }
+ } else if (ldb_attr_cmp("dnsHostName", el->name) == 0) {
+ ret = acl_check_dns_host_name(tmp_ctx,
+ module,
+ req,
+ el,
+ sd,
+ sid,
+ attr,
+ objectclass,
+ implicit_validated_write_control);
+ if (ret != LDB_SUCCESS) {
+ goto fail;
+ }
+ } else if (is_undelete != NULL && (ldb_attr_cmp("isDeleted", el->name) == 0)) {
+ /*
+ * in case of undelete op permissions on
+ * isDeleted are irrelevant and
+ * distinguishedName is removed by the
+ * tombstone_reanimate module
+ */
+ continue;
+ } else if (implicit_validated_write_control != NULL) {
+ /* Allow the update. */
+ continue;
+ } else {
+ ret = acl_check_access_on_attribute(module,
+ tmp_ctx,
+ sd,
+ sid,
+ SEC_ADS_WRITE_PROP,
+ attr,
+ objectclass);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "Object %s has no write property access\n",
+ ldb_dn_get_linearized(msg->dn));
+ dsdb_acl_debug(sd,
+ acl_user_token(module),
+ msg->dn,
+ true,
+ 10);
+ ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ goto fail;
+ }
+ }
+ }
+
+success:
+ talloc_free(tmp_ctx);
+ context = talloc_zero(req, struct acl_callback_context);
+
+ if (context == NULL) {
+ return ldb_oom(ldb);
+ }
+ context->request = req;
+ context->module = module;
+ ret = ldb_build_mod_req(
+ &new_req,
+ ldb,
+ req,
+ req->op.mod.message,
+ req->controls,
+ context,
+ acl_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(module, new_req);
+fail:
+ talloc_free(tmp_ctx);
+ /*
+ * We copy the pav into the result, so that the password reset
+ * logging code in audit_log can log failed password reset attempts.
+ */
+ if (pav) {
+ struct ldb_control *control = NULL;
+
+ controls = talloc_zero_array(req, struct ldb_control *, 2);
+ if (controls == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ control = talloc(controls, struct ldb_control);
+
+ if (control == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ control->oid= talloc_strdup(
+ control,
+ DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID);
+ if (control->oid == NULL) {
+ return ldb_oom(ldb);
+ }
+ control->critical = false;
+ control->data = pav;
+ *controls = control;
+ }
+ return ldb_module_done(req, controls, NULL, ret);
+}
+
+/* similar to the modify for the time being.
+ * We need to consider the special delete tree case, though - TODO */
+static int acl_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ struct ldb_dn *parent;
+ struct ldb_context *ldb;
+ struct ldb_dn *nc_root;
+ struct ldb_control *as_system;
+ const struct dsdb_schema *schema;
+ const struct dsdb_class *objectclass;
+ struct security_descriptor *sd = NULL;
+ struct dom_sid *sid = NULL;
+ struct ldb_result *acl_res;
+ static const char *acl_attrs[] = {
+ "nTSecurityDescriptor",
+ "objectClass",
+ "objectSid",
+ NULL
+ };
+
+ if (ldb_dn_is_special(req->op.del.dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID);
+ if (as_system != NULL) {
+ as_system->critical = 0;
+ }
+
+ if (dsdb_module_am_system(module) || as_system) {
+ return ldb_next_request(module, req);
+ }
+
+ DEBUG(10, ("ldb:acl_delete: %s\n", ldb_dn_get_linearized(req->op.del.dn)));
+
+ ldb = ldb_module_get_ctx(module);
+
+ parent = ldb_dn_get_parent(req, req->op.del.dn);
+ if (parent == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /* Make sure we aren't deleting a NC */
+
+ ret = dsdb_find_nc_root(ldb, req, req->op.del.dn, &nc_root);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (ldb_dn_compare(nc_root, req->op.del.dn) == 0) {
+ talloc_free(nc_root);
+ DEBUG(10,("acl:deleting a NC\n"));
+ /* Windows returns "ERR_UNWILLING_TO_PERFORM */
+ return ldb_module_done(req, NULL, NULL,
+ LDB_ERR_UNWILLING_TO_PERFORM);
+ }
+ talloc_free(nc_root);
+
+ ret = dsdb_module_search_dn(module, req, &acl_res,
+ req->op.del.dn, acl_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED, req);
+ /* we sould be able to find the parent */
+ if (ret != LDB_SUCCESS) {
+ DEBUG(10,("acl: failed to find object %s\n",
+ ldb_dn_get_linearized(req->op.rename.olddn)));
+ return ret;
+ }
+
+ ret = dsdb_get_sd_from_ldb_message(ldb, req, acl_res->msgs[0], &sd);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+ if (!sd) {
+ return ldb_operr(ldb);
+ }
+
+ schema = dsdb_get_schema(ldb, req);
+ if (!schema) {
+ return ldb_operr(ldb);
+ }
+
+ sid = samdb_result_dom_sid(req, acl_res->msgs[0], "objectSid");
+
+ objectclass = dsdb_get_structural_oc_from_msg(schema, acl_res->msgs[0]);
+ if (!objectclass) {
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "acl_modify: Error retrieving object class for GUID.");
+ }
+
+ if (ldb_request_get_control(req, LDB_CONTROL_TREE_DELETE_OID)) {
+ ret = acl_check_access_on_objectclass(module, req, sd, sid,
+ SEC_ADS_DELETE_TREE,
+ objectclass);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, req);
+ }
+
+ /* First check if we have delete object right */
+ ret = acl_check_access_on_objectclass(module, req, sd, sid,
+ SEC_STD_DELETE,
+ objectclass);
+ if (ret == LDB_SUCCESS) {
+ return ldb_next_request(module, req);
+ }
+
+ /* Nope, we don't have delete object. Lets check if we have delete
+ * child on the parent */
+ ret = dsdb_module_check_access_on_dn(module, req, parent,
+ SEC_ADS_DELETE_CHILD,
+ &objectclass->schemaIDGUID,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, req);
+}
+static int acl_check_reanimate_tombstone(TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ struct ldb_request *req,
+ struct ldb_dn *nc_root)
+{
+ int ret;
+ struct ldb_result *acl_res;
+ struct security_descriptor *sd = NULL;
+ struct dom_sid *sid = NULL;
+ const struct dsdb_schema *schema = NULL;
+ const struct dsdb_class *objectclass = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ static const char *acl_attrs[] = {
+ "nTSecurityDescriptor",
+ "objectClass",
+ "objectSid",
+ NULL
+ };
+
+ ret = dsdb_module_search_dn(module, mem_ctx, &acl_res,
+ nc_root, acl_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED, req);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(10,("acl: failed to find object %s\n",
+ ldb_dn_get_linearized(nc_root)));
+ return ret;
+ }
+
+ ret = dsdb_get_sd_from_ldb_message(mem_ctx, req, acl_res->msgs[0], &sd);
+ sid = samdb_result_dom_sid(mem_ctx, acl_res->msgs[0], "objectSid");
+ schema = dsdb_get_schema(ldb, req);
+ if (!schema) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ objectclass = dsdb_get_structural_oc_from_msg(schema, acl_res->msgs[0]);
+ if (ret != LDB_SUCCESS || !sd) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+ return acl_check_extended_right(mem_ctx,
+ module,
+ req,
+ objectclass,
+ sd,
+ acl_user_token(module),
+ GUID_DRS_REANIMATE_TOMBSTONE,
+ SEC_ADS_CONTROL_ACCESS, sid);
+}
+
+static int acl_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ struct ldb_dn *oldparent;
+ struct ldb_dn *newparent;
+ const struct dsdb_schema *schema;
+ const struct dsdb_class *objectclass;
+ const struct dsdb_attribute *attr = NULL;
+ struct ldb_context *ldb;
+ struct security_descriptor *sd = NULL;
+ struct dom_sid *sid = NULL;
+ struct ldb_result *acl_res;
+ struct ldb_dn *nc_root;
+ struct ldb_control *as_system;
+ struct ldb_control *is_undelete;
+ TALLOC_CTX *tmp_ctx;
+ const char *rdn_name;
+ static const char *acl_attrs[] = {
+ "nTSecurityDescriptor",
+ "objectClass",
+ "objectSid",
+ NULL
+ };
+
+ if (ldb_dn_is_special(req->op.rename.olddn)) {
+ return ldb_next_request(module, req);
+ }
+
+ as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID);
+ if (as_system != NULL) {
+ as_system->critical = 0;
+ }
+
+ DEBUG(10, ("ldb:acl_rename: %s\n", ldb_dn_get_linearized(req->op.rename.olddn)));
+ if (dsdb_module_am_system(module) || as_system) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ tmp_ctx = talloc_new(req);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ oldparent = ldb_dn_get_parent(tmp_ctx, req->op.rename.olddn);
+ if (oldparent == NULL) {
+ return ldb_oom(ldb);
+ }
+ newparent = ldb_dn_get_parent(tmp_ctx, req->op.rename.newdn);
+ if (newparent == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /* Make sure we aren't renaming/moving a NC */
+
+ ret = dsdb_find_nc_root(ldb, req, req->op.rename.olddn, &nc_root);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (ldb_dn_compare(nc_root, req->op.rename.olddn) == 0) {
+ talloc_free(nc_root);
+ DEBUG(10,("acl:renaming/moving a NC\n"));
+ /* Windows returns "ERR_UNWILLING_TO_PERFORM */
+ return ldb_module_done(req, NULL, NULL,
+ LDB_ERR_UNWILLING_TO_PERFORM);
+ }
+
+ /* special check for undelete operation */
+ is_undelete = ldb_request_get_control(req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID);
+ if (is_undelete != NULL) {
+ is_undelete->critical = 0;
+ ret = acl_check_reanimate_tombstone(tmp_ctx, module, req, nc_root);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+ talloc_free(nc_root);
+
+ /* Look for the parent */
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &acl_res,
+ req->op.rename.olddn, acl_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED, req);
+ /* we sould be able to find the parent */
+ if (ret != LDB_SUCCESS) {
+ DEBUG(10,("acl: failed to find object %s\n",
+ ldb_dn_get_linearized(req->op.rename.olddn)));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_get_sd_from_ldb_message(ldb, req, acl_res->msgs[0], &sd);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+ if (!sd) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ schema = dsdb_get_schema(ldb, acl_res);
+ if (!schema) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ sid = samdb_result_dom_sid(req, acl_res->msgs[0], "objectSid");
+
+ objectclass = dsdb_get_structural_oc_from_msg(schema, acl_res->msgs[0]);
+ if (!objectclass) {
+ talloc_free(tmp_ctx);
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "acl_modify: Error retrieving object class for GUID.");
+ }
+
+ attr = dsdb_attribute_by_lDAPDisplayName(schema, "name");
+ if (attr == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ ret = acl_check_access_on_attribute(module, tmp_ctx, sd, sid,
+ SEC_ADS_WRITE_PROP,
+ attr, objectclass);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "Object %s has no wp on %s\n",
+ ldb_dn_get_linearized(req->op.rename.olddn),
+ attr->lDAPDisplayName);
+ dsdb_acl_debug(sd,
+ acl_user_token(module),
+ req->op.rename.olddn,
+ true,
+ 10);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+
+ rdn_name = ldb_dn_get_rdn_name(req->op.rename.olddn);
+ if (rdn_name == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ attr = dsdb_attribute_by_lDAPDisplayName(schema, rdn_name);
+ if (attr == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ ret = acl_check_access_on_attribute(module, tmp_ctx, sd, sid,
+ SEC_ADS_WRITE_PROP,
+ attr, objectclass);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "Object %s has no wp on %s\n",
+ ldb_dn_get_linearized(req->op.rename.olddn),
+ attr->lDAPDisplayName);
+ dsdb_acl_debug(sd,
+ acl_user_token(module),
+ req->op.rename.olddn,
+ true,
+ 10);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+
+ if (ldb_dn_compare(oldparent, newparent) == 0) {
+ /* regular rename, not move, nothing more to do */
+ talloc_free(tmp_ctx);
+ return ldb_next_request(module, req);
+ }
+
+ /* new parent should have create child */
+ ret = dsdb_module_check_access_on_dn(module, req, newparent,
+ SEC_ADS_CREATE_CHILD,
+ &objectclass->schemaIDGUID, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "acl:access_denied renaming %s",
+ ldb_dn_get_linearized(req->op.rename.olddn));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* do we have delete object on the object? */
+ /* this access is not necessary for undelete ops */
+ if (is_undelete == NULL) {
+ ret = acl_check_access_on_objectclass(module, tmp_ctx, sd, sid,
+ SEC_STD_DELETE,
+ objectclass);
+ if (ret == LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ldb_next_request(module, req);
+ }
+ /* what about delete child on the current parent */
+ ret = dsdb_module_check_access_on_dn(module, req, oldparent,
+ SEC_ADS_DELETE_CHILD,
+ &objectclass->schemaIDGUID,
+ req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "acl:access_denied renaming %s", ldb_dn_get_linearized(req->op.rename.olddn));
+ talloc_free(tmp_ctx);
+ return ldb_module_done(req, NULL, NULL, ret);
+ }
+ }
+ talloc_free(tmp_ctx);
+
+ return ldb_next_request(module, req);
+}
+
+static int acl_search_update_confidential_attrs(struct acl_context *ac,
+ struct acl_private *data)
+{
+ struct dsdb_attribute *a;
+ uint32_t n = 0;
+
+ if (data->acl_search) {
+ /*
+ * If acl:search is activated, the acl_read module
+ * protects confidential attributes.
+ */
+ return LDB_SUCCESS;
+ }
+
+ if ((ac->schema == data->cached_schema_ptr) &&
+ (ac->schema->metadata_usn == data->cached_schema_metadata_usn))
+ {
+ return LDB_SUCCESS;
+ }
+
+ data->cached_schema_ptr = NULL;
+ data->cached_schema_loaded_usn = 0;
+ data->cached_schema_metadata_usn = 0;
+ TALLOC_FREE(data->confidential_attrs);
+
+ if (ac->schema == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ for (a = ac->schema->attributes; a; a = a->next) {
+ const char **attrs = data->confidential_attrs;
+
+ if (!(a->searchFlags & SEARCH_FLAG_CONFIDENTIAL)) {
+ continue;
+ }
+
+ attrs = talloc_realloc(data, attrs, const char *, n + 2);
+ if (attrs == NULL) {
+ TALLOC_FREE(data->confidential_attrs);
+ return ldb_module_oom(ac->module);
+ }
+
+ attrs[n] = a->lDAPDisplayName;
+ attrs[n+1] = NULL;
+ n++;
+
+ data->confidential_attrs = attrs;
+ }
+
+ data->cached_schema_ptr = ac->schema;
+ data->cached_schema_metadata_usn = ac->schema->metadata_usn;
+
+ return LDB_SUCCESS;
+}
+
+static int acl_search_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct acl_context *ac;
+ struct acl_private *data;
+ struct ldb_result *acl_res;
+ static const char *acl_attrs[] = {
+ "objectClass",
+ "nTSecurityDescriptor",
+ "objectSid",
+ NULL
+ };
+ int ret;
+ unsigned int i;
+
+ ac = talloc_get_type(req->context, struct acl_context);
+ data = talloc_get_type(ldb_module_get_private(ac->module), struct acl_private);
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ if (ac->constructed_attrs) {
+ ret = dsdb_module_search_dn(ac->module, ac, &acl_res, ares->message->dn,
+ acl_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ if (ac->allowedAttributes || ac->allowedAttributesEffective) {
+ ret = acl_allowedAttributes(ac->module, ac->schema,
+ acl_res->msgs[0],
+ ares->message, ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ if (ac->allowedChildClasses) {
+ ret = acl_childClasses(ac->module, ac->schema,
+ acl_res->msgs[0],
+ ares->message,
+ "allowedChildClasses");
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ if (ac->allowedChildClassesEffective) {
+ ret = acl_childClassesEffective(ac->module, ac->schema,
+ acl_res->msgs[0],
+ ares->message, ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ if (ac->sDRightsEffective) {
+ ret = acl_sDRightsEffective(ac->module,
+ acl_res->msgs[0],
+ ares->message, ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ if (data == NULL) {
+ return ldb_module_send_entry(ac->req, ares->message,
+ ares->controls);
+ }
+
+ if (ac->am_system) {
+ return ldb_module_send_entry(ac->req, ares->message,
+ ares->controls);
+ }
+
+ if (ac->am_administrator) {
+ return ldb_module_send_entry(ac->req, ares->message,
+ ares->controls);
+ }
+
+ if (data->confidential_attrs != NULL) {
+ for (i = 0; data->confidential_attrs[i]; i++) {
+ ldb_msg_remove_attr(ares->message,
+ data->confidential_attrs[i]);
+ }
+ }
+
+ return ldb_module_send_entry(ac->req, ares->message, ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(ac->req, ares->referral);
+
+ case LDB_REPLY_DONE:
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, LDB_SUCCESS);
+
+ }
+ return LDB_SUCCESS;
+}
+
+static int acl_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct acl_context *ac;
+ struct ldb_parse_tree *down_tree = req->op.search.tree;
+ struct ldb_request *down_req;
+ struct acl_private *data;
+ int ret;
+ unsigned int i;
+ bool modify_search = true;
+
+ if (ldb_dn_is_special(req->op.search.base)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = talloc_zero(req, struct acl_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+ data = talloc_get_type(ldb_module_get_private(module), struct acl_private);
+
+ ac->module = module;
+ ac->req = req;
+ ac->am_system = dsdb_module_am_system(module);
+ ac->am_administrator = dsdb_module_am_administrator(module);
+ ac->constructed_attrs = false;
+ 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");
+ ac->sDRightsEffective = ldb_attr_in_list(req->op.search.attrs, "sDRightsEffective");
+ ac->schema = dsdb_get_schema(ldb, ac);
+
+ ac->constructed_attrs |= ac->allowedAttributes;
+ ac->constructed_attrs |= ac->allowedChildClasses;
+ ac->constructed_attrs |= ac->allowedChildClassesEffective;
+ ac->constructed_attrs |= ac->allowedAttributesEffective;
+ ac->constructed_attrs |= ac->sDRightsEffective;
+
+ if (data == NULL) {
+ modify_search = false;
+ }
+ if (ac->am_system) {
+ modify_search = false;
+ }
+
+ if (!ac->constructed_attrs && !modify_search) {
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+
+ data = talloc_get_type(ldb_module_get_private(ac->module), struct acl_private);
+ if (data == NULL) {
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "acl_private data is missing");
+ }
+
+ if (!ac->am_system && !ac->am_administrator) {
+ ret = acl_search_update_confidential_attrs(ac, data);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (data->confidential_attrs != NULL) {
+ down_tree = ldb_parse_tree_copy_shallow(ac, req->op.search.tree);
+ if (down_tree == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ for (i = 0; data->confidential_attrs[i]; i++) {
+ ldb_parse_tree_attr_replace(down_tree,
+ data->confidential_attrs[i],
+ "kludgeACLredactedattribute");
+ }
+ }
+ }
+
+ ret = ldb_build_search_req_ex(&down_req,
+ ldb, ac,
+ req->op.search.base,
+ req->op.search.scope,
+ down_tree,
+ req->op.search.attrs,
+ req->controls,
+ ac, acl_search_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ /* perform the search */
+ return ldb_next_request(module, down_req);
+}
+
+static int acl_extended(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_control *as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID);
+
+ /* allow everybody to read the sequence number */
+ if (strcmp(req->op.extended.oid,
+ LDB_EXTENDED_SEQUENCE_NUMBER) == 0) {
+ return ldb_next_request(module, req);
+ }
+
+ if (dsdb_module_am_system(module) ||
+ dsdb_module_am_administrator(module) || as_system) {
+ return ldb_next_request(module, req);
+ } else {
+ ldb_asprintf_errstring(ldb,
+ "acl_extended: "
+ "attempted database modify not permitted. "
+ "User %s is not SYSTEM or an administrator",
+ acl_user_name(req, module));
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+}
+
+static const struct ldb_module_ops ldb_acl_module_ops = {
+ .name = "acl",
+ .search = acl_search,
+ .add = acl_add,
+ .modify = acl_modify,
+ .del = acl_delete,
+ .rename = acl_rename,
+ .extended = acl_extended,
+ .init_context = acl_module_init
+};
+
+int ldb_acl_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_acl_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/acl_read.c b/source4/dsdb/samdb/ldb_modules/acl_read.c
new file mode 100644
index 0000000..21f72fb
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/acl_read.c
@@ -0,0 +1,1293 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2006-2008
+ Copyright (C) Nadezhda Ivanova 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb ACL Read module
+ *
+ * Description: Module that performs authorisation access checks on read requests
+ * Only DACL checks implemented at this point
+ *
+ * Author: Nadezhda Ivanova
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "auth/auth.h"
+#include "libcli/security/security.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "param/param.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "lib/util/binsearch.h"
+
+#undef strcasecmp
+
+struct ldb_attr_vec {
+ const char** attrs;
+ size_t len;
+ size_t capacity;
+};
+
+struct aclread_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+ const struct dsdb_schema *schema;
+ uint32_t sd_flags;
+ bool added_nTSecurityDescriptor;
+ bool added_instanceType;
+ bool added_objectSid;
+ bool added_objectClass;
+
+ bool do_list_object_initialized;
+ bool do_list_object;
+ bool base_invisible;
+ uint64_t num_entries;
+
+ /* cache on the last parent we checked in this search */
+ struct ldb_dn *last_parent_dn;
+ int last_parent_check_ret;
+
+ bool am_administrator;
+
+ bool got_tree_attrs;
+ struct ldb_attr_vec tree_attrs;
+};
+
+struct aclread_private {
+ bool enabled;
+
+ /* cache of the last SD we read during any search */
+ struct security_descriptor *sd_cached;
+ struct ldb_val sd_cached_blob;
+ const char **password_attrs;
+ size_t num_password_attrs;
+};
+
+struct access_check_context {
+ struct security_descriptor *sd;
+ struct dom_sid sid_buf;
+ const struct dom_sid *sid;
+ const struct dsdb_class *objectclass;
+};
+
+static void acl_element_mark_access_checked(struct ldb_message_element *el)
+{
+ el->flags |= LDB_FLAG_INTERNAL_ACCESS_CHECKED;
+}
+
+static bool acl_element_is_access_checked(const struct ldb_message_element *el)
+{
+ return (el->flags & LDB_FLAG_INTERNAL_ACCESS_CHECKED) != 0;
+}
+
+static bool attr_in_vec(const struct ldb_attr_vec *vec, const char *attr)
+{
+ const char **found = NULL;
+
+ if (vec == NULL) {
+ return false;
+ }
+
+ BINARY_ARRAY_SEARCH_V(vec->attrs,
+ vec->len,
+ attr,
+ ldb_attr_cmp,
+ found);
+ return found != NULL;
+}
+
+static int acl_attr_cmp_fn(const char *a, const char **b)
+{
+ return ldb_attr_cmp(a, *b);
+}
+
+static int attr_vec_add_unique(TALLOC_CTX *mem_ctx,
+ struct ldb_attr_vec *vec,
+ const char *attr)
+{
+ const char **exact = NULL;
+ const char **next = NULL;
+ size_t next_idx = 0;
+
+ BINARY_ARRAY_SEARCH_GTE(vec->attrs,
+ vec->len,
+ attr,
+ acl_attr_cmp_fn,
+ exact,
+ next);
+ if (exact != NULL) {
+ return LDB_SUCCESS;
+ }
+
+ if (vec->len == SIZE_MAX) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (next != NULL) {
+ next_idx = next - vec->attrs;
+ }
+
+ if (vec->len >= vec->capacity) {
+ const char **attrs = NULL;
+
+ if (vec->capacity == 0) {
+ vec->capacity = 4;
+ } else {
+ if (vec->capacity > SIZE_MAX / 2) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ vec->capacity *= 2;
+ }
+
+ attrs = talloc_realloc(mem_ctx, vec->attrs, const char *, vec->capacity);
+ if (attrs == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ vec->attrs = attrs;
+ }
+ SMB_ASSERT(vec->len < vec->capacity);
+
+ if (next == NULL) {
+ vec->attrs[vec->len++] = attr;
+ } else {
+ size_t count = (vec->len - next_idx) * sizeof (vec->attrs[0]);
+ memmove(&vec->attrs[next_idx + 1],
+ &vec->attrs[next_idx],
+ count);
+
+ vec->attrs[next_idx] = attr;
+ ++vec->len;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static bool ldb_attr_always_present(const char *attr)
+{
+ static const char * const attrs_always_present[] = {
+ "objectClass",
+ "distinguishedName",
+ "name",
+ "objectGUID",
+ NULL
+ };
+
+ return ldb_attr_in_list(attrs_always_present, attr);
+}
+
+static bool ldb_attr_always_visible(const char *attr)
+{
+ static const char * const attrs_always_visible[] = {
+ "isDeleted",
+ "isRecycled",
+ NULL
+ };
+
+ return ldb_attr_in_list(attrs_always_visible, attr);
+}
+
+/* Collect a list of attributes required to match a given parse tree. */
+static int ldb_parse_tree_collect_acl_attrs(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_attr_vec *attrs,
+ const struct ldb_parse_tree *tree)
+{
+ const char *attr = NULL;
+ unsigned int i;
+ int ret;
+
+ if (tree == NULL) {
+ return 0;
+ }
+
+ switch (tree->operation) {
+ case LDB_OP_OR:
+ case LDB_OP_AND: /* attributes stored in list of subtrees */
+ for (i = 0; i < tree->u.list.num_elements; i++) {
+ ret = ldb_parse_tree_collect_acl_attrs(module, mem_ctx,
+ attrs, tree->u.list.elements[i]);
+ if (ret) {
+ return ret;
+ }
+ }
+ return 0;
+
+ case LDB_OP_NOT: /* attributes stored in single subtree */
+ return ldb_parse_tree_collect_acl_attrs(module, mem_ctx, attrs, tree->u.isnot.child);
+
+ case LDB_OP_PRESENT:
+ /*
+ * If the search filter is checking for an attribute's presence,
+ * and the attribute is always present, we can skip access
+ * rights checks. Every object has these attributes, and so
+ * there's no security reason to hide their presence.
+ * Note: the acl.py tests (e.g. test_search1()) rely on this
+ * exception. I.e. even if we lack Read Property (RP) rights
+ * for a child object, it should still appear as a visible
+ * object in 'objectClass=*' searches, so long as we have List
+ * Contents (LC) rights for the object.
+ */
+ if (ldb_attr_always_present(tree->u.present.attr)) {
+ /* No need to check this attribute. */
+ return 0;
+ }
+
+ FALL_THROUGH;
+ case LDB_OP_EQUALITY:
+ if (ldb_attr_always_visible(tree->u.present.attr)) {
+ /* No need to check this attribute. */
+ return 0;
+ }
+
+ FALL_THROUGH;
+ default: /* single attribute in tree */
+ attr = ldb_parse_tree_get_attr(tree);
+ return attr_vec_add_unique(mem_ctx, attrs, attr);
+ }
+}
+
+/*
+ * the object has a parent, so we have to check for visibility
+ *
+ * This helper function uses a per-search cache to avoid checking the
+ * parent object for each of many possible children. This is likely
+ * to help on SCOPE_ONE searches and on typical tree structures for
+ * SCOPE_SUBTREE, where an OU has many users as children.
+ *
+ * We rely for safety on the DB being locked for reads during the full
+ * search.
+ */
+static int aclread_check_parent(struct aclread_context *ac,
+ struct ldb_message *msg,
+ struct ldb_request *req)
+{
+ int ret;
+ struct ldb_dn *parent_dn = NULL;
+
+ /* We may have a cached result from earlier in this search */
+ if (ac->last_parent_dn != NULL) {
+ /*
+ * We try the no-allocation ldb_dn_compare_base()
+ * first however it will not tell parents and
+ * grand-parents apart
+ */
+ int cmp_base = ldb_dn_compare_base(ac->last_parent_dn,
+ msg->dn);
+ if (cmp_base == 0) {
+ /* Now check if it is a direct parent */
+ parent_dn = ldb_dn_get_parent(ac, msg->dn);
+ if (parent_dn == NULL) {
+ return ldb_oom(ldb_module_get_ctx(ac->module));
+ }
+ if (ldb_dn_compare(ac->last_parent_dn,
+ parent_dn) == 0) {
+ TALLOC_FREE(parent_dn);
+
+ /*
+ * If we checked the same parent last
+ * time, then return the cached
+ * result.
+ *
+ * The cache is valid as long as the
+ * search as the DB is read locked and
+ * the session_info (connected user)
+ * is constant.
+ */
+ return ac->last_parent_check_ret;
+ }
+ }
+ }
+
+ {
+ TALLOC_CTX *frame = NULL;
+ frame = talloc_stackframe();
+
+ /*
+ * This may have been set in the block above, don't
+ * re-parse
+ */
+ if (parent_dn == NULL) {
+ parent_dn = ldb_dn_get_parent(ac, msg->dn);
+ if (parent_dn == NULL) {
+ TALLOC_FREE(frame);
+ return ldb_oom(ldb_module_get_ctx(ac->module));
+ }
+ }
+ ret = dsdb_module_check_access_on_dn(ac->module,
+ frame,
+ parent_dn,
+ SEC_ADS_LIST,
+ NULL, req);
+ talloc_unlink(ac, ac->last_parent_dn);
+ ac->last_parent_dn = parent_dn;
+ ac->last_parent_check_ret = ret;
+
+ TALLOC_FREE(frame);
+ }
+ return ret;
+}
+
+static int aclread_check_object_visible(struct aclread_context *ac,
+ struct ldb_message *msg,
+ struct ldb_request *req)
+{
+ uint32_t instanceType;
+ int ret;
+
+ /* get the object instance type */
+ instanceType = ldb_msg_find_attr_as_uint(msg,
+ "instanceType", 0);
+ if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
+ /*
+ * NC_HEAD objects are always visible
+ */
+ return LDB_SUCCESS;
+ }
+
+ ret = aclread_check_parent(ac, msg, req);
+ if (ret == LDB_SUCCESS) {
+ /*
+ * SEC_ADS_LIST (List Children) alone
+ * on the parent is enough to make the
+ * object visible.
+ */
+ return LDB_SUCCESS;
+ }
+ if (ret != LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ return ret;
+ }
+
+ if (!ac->do_list_object_initialized) {
+ /*
+ * We only call dsdb_do_list_object() once
+ * and only when needed in order to
+ * check the dSHeuristics for fDoListObject.
+ */
+ ac->do_list_object = dsdb_do_list_object(ac->module, ac, req);
+ ac->do_list_object_initialized = true;
+ }
+
+ if (ac->do_list_object) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct ldb_dn *parent_dn = NULL;
+
+ /*
+ * Here we're in "List Object" mode (fDoListObject=true).
+ *
+ * If SEC_ADS_LIST (List Children) is not
+ * granted on the parent, we need to check if
+ * SEC_ADS_LIST_OBJECT (List Object) is granted
+ * on the parent and also on the object itself.
+ *
+ * We could optimize this similar to aclread_check_parent(),
+ * but that would require quite a bit of restructuring,
+ * so that we cache the granted access bits instead
+ * of just the result for 'SEC_ADS_LIST (List Children)'.
+ *
+ * But as this is the uncommon case and
+ * 'SEC_ADS_LIST (List Children)' is most likely granted
+ * on most of the objects, we'll just implement what
+ * we have to.
+ */
+
+ parent_dn = ldb_dn_get_parent(frame, msg->dn);
+ if (parent_dn == NULL) {
+ TALLOC_FREE(frame);
+ return ldb_oom(ldb_module_get_ctx(ac->module));
+ }
+ ret = dsdb_module_check_access_on_dn(ac->module,
+ frame,
+ parent_dn,
+ SEC_ADS_LIST_OBJECT,
+ NULL, req);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ ret = dsdb_module_check_access_on_dn(ac->module,
+ frame,
+ msg->dn,
+ SEC_ADS_LIST_OBJECT,
+ NULL, req);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ TALLOC_FREE(frame);
+ return LDB_SUCCESS;
+ }
+
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+}
+
+/*
+ * The sd returned from this function is valid until the next call on
+ * this module context
+ *
+ * This helper function uses a cache on the module private data to
+ * speed up repeated use of the same SD.
+ */
+
+static int aclread_get_sd_from_ldb_message(struct aclread_context *ac,
+ const struct ldb_message *acl_res,
+ struct security_descriptor **sd)
+{
+ struct ldb_message_element *sd_element;
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct aclread_private *private_data
+ = talloc_get_type_abort(ldb_module_get_private(ac->module),
+ struct aclread_private);
+ enum ndr_err_code ndr_err;
+
+ sd_element = ldb_msg_find_element(acl_res, "nTSecurityDescriptor");
+ if (sd_element == NULL) {
+ return ldb_error(ldb, LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS,
+ "nTSecurityDescriptor is missing");
+ }
+
+ if (sd_element->num_values != 1) {
+ return ldb_operr(ldb);
+ }
+
+ /*
+ * The time spent in ndr_pull_security_descriptor() is quite
+ * expensive, so we check if this is the same binary blob as last
+ * time, and if so return the memory tree from that previous parse.
+ */
+
+ if (private_data->sd_cached != NULL &&
+ private_data->sd_cached_blob.data != NULL &&
+ ldb_val_equal_exact(&sd_element->values[0],
+ &private_data->sd_cached_blob)) {
+ *sd = private_data->sd_cached;
+ return LDB_SUCCESS;
+ }
+
+ *sd = talloc(private_data, struct security_descriptor);
+ if(!*sd) {
+ return ldb_oom(ldb);
+ }
+ ndr_err = ndr_pull_struct_blob(&sd_element->values[0], *sd, *sd,
+ (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ TALLOC_FREE(*sd);
+ return ldb_operr(ldb);
+ }
+
+ talloc_unlink(private_data, private_data->sd_cached_blob.data);
+ private_data->sd_cached_blob = ldb_val_dup(private_data,
+ &sd_element->values[0]);
+ if (private_data->sd_cached_blob.data == NULL) {
+ TALLOC_FREE(*sd);
+ return ldb_operr(ldb);
+ }
+
+ talloc_unlink(private_data, private_data->sd_cached);
+ private_data->sd_cached = *sd;
+
+ return LDB_SUCCESS;
+}
+
+/* Check whether the attribute is a password attribute. */
+static bool attr_is_secret(const char *attr, const struct aclread_private *private_data)
+{
+ const char **found = NULL;
+
+ if (private_data->password_attrs == NULL) {
+ return false;
+ }
+
+ BINARY_ARRAY_SEARCH_V(private_data->password_attrs,
+ private_data->num_password_attrs,
+ attr,
+ ldb_attr_cmp,
+ found);
+ return found != NULL;
+}
+
+/*
+ * Returns the access mask required to read a given attribute
+ */
+static uint32_t get_attr_access_mask(const struct dsdb_attribute *attr,
+ uint32_t sd_flags)
+{
+
+ uint32_t access_mask = 0;
+ bool is_sd;
+
+ /* nTSecurityDescriptor is a special case */
+ is_sd = (ldb_attr_cmp("nTSecurityDescriptor",
+ attr->lDAPDisplayName) == 0);
+
+ if (is_sd) {
+ if (sd_flags & (SECINFO_OWNER|SECINFO_GROUP)) {
+ access_mask |= SEC_STD_READ_CONTROL;
+ }
+ if (sd_flags & SECINFO_DACL) {
+ access_mask |= SEC_STD_READ_CONTROL;
+ }
+ if (sd_flags & SECINFO_SACL) {
+ access_mask |= SEC_FLAG_SYSTEM_SECURITY;
+ }
+ } else {
+ access_mask = SEC_ADS_READ_PROP;
+ }
+
+ if (attr->searchFlags & SEARCH_FLAG_CONFIDENTIAL) {
+ access_mask |= SEC_ADS_CONTROL_ACCESS;
+ }
+
+ return access_mask;
+}
+
+/*
+ * Checks that the user has sufficient access rights to view an attribute, else
+ * marks it as inaccessible.
+ */
+static int acl_redact_attr(TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *el,
+ struct aclread_context *ac,
+ const struct aclread_private *private_data,
+ const struct ldb_message *msg,
+ const struct dsdb_schema *schema,
+ const struct security_descriptor *sd,
+ const struct dom_sid *sid,
+ const struct dsdb_class *objectclass)
+{
+ int ret;
+ const struct dsdb_attribute *attr = NULL;
+ uint32_t access_mask;
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+
+ if (attr_is_secret(el->name, private_data)) {
+ ldb_msg_element_mark_inaccessible(el);
+ return LDB_SUCCESS;
+ }
+
+ /* Look up the attribute in the schema. */
+ attr = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (!attr) {
+ ldb_debug_set(ldb,
+ LDB_DEBUG_FATAL,
+ "acl_read: %s cannot find attr[%s] in schema\n",
+ ldb_dn_get_linearized(msg->dn), el->name);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ access_mask = get_attr_access_mask(attr, ac->sd_flags);
+ if (access_mask == 0) {
+ DBG_ERR("Could not determine access mask for attribute %s\n",
+ el->name);
+ ldb_msg_element_mark_inaccessible(el);
+ return LDB_SUCCESS;
+ }
+
+ /* We must check whether the user has rights to view the attribute. */
+
+ ret = acl_check_access_on_attribute(ac->module, mem_ctx, sd, sid,
+ access_mask, attr, objectclass);
+
+ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ ldb_msg_element_mark_inaccessible(el);
+ } else if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "acl_read: %s check attr[%s] gives %s - %s\n",
+ ldb_dn_get_linearized(msg->dn), el->name,
+ ldb_strerror(ret), ldb_errstring(ldb));
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_access_check_context(struct aclread_context *ac,
+ const struct ldb_message *msg,
+ struct access_check_context *ctx)
+{
+ int ret;
+
+ /*
+ * Fetch the schema so we can check which attributes are
+ * considered confidential.
+ */
+ if (ac->schema == NULL) {
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+
+ /* Cache the schema for later use. */
+ ac->schema = dsdb_get_schema(ldb, ac);
+
+ if (ac->schema == NULL) {
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "aclread_callback: Error obtaining schema.");
+ }
+ }
+
+ /* Fetch the object's security descriptor. */
+ ret = aclread_get_sd_from_ldb_message(ac, msg, &ctx->sd);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb_module_get_ctx(ac->module), LDB_DEBUG_FATAL,
+ "acl_read: cannot get descriptor of %s: %s\n",
+ ldb_dn_get_linearized(msg->dn), ldb_strerror(ret));
+ return LDB_ERR_OPERATIONS_ERROR;
+ } else if (ctx->sd == NULL) {
+ ldb_debug_set(ldb_module_get_ctx(ac->module), LDB_DEBUG_FATAL,
+ "acl_read: cannot get descriptor of %s (attribute not found)\n",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /*
+ * Get the most specific structural object class for the ACL check
+ */
+ ctx->objectclass = dsdb_get_structural_oc_from_msg(ac->schema, msg);
+ if (ctx->objectclass == NULL) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ac->module),
+ "acl_read: Failed to find a structural class for %s",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Fetch the object's SID. */
+ ret = samdb_result_dom_sid_buf(msg, "objectSid", &ctx->sid_buf);
+ if (ret == LDB_SUCCESS) {
+ ctx->sid = &ctx->sid_buf;
+ } else if (ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
+ /* This is expected. */
+ ctx->sid = NULL;
+ } else {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ac->module),
+ "acl_read: Failed to parse objectSid as dom_sid for %s",
+ ldb_dn_get_linearized(msg->dn));
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * Whether this attribute was added to perform access checks and must be
+ * removed.
+ */
+static bool should_remove_attr(const char *attr, const struct aclread_context *ac)
+{
+ if (ac->added_nTSecurityDescriptor &&
+ ldb_attr_cmp("nTSecurityDescriptor", attr) == 0)
+ {
+ return true;
+ }
+
+ if (ac->added_objectSid &&
+ ldb_attr_cmp("objectSid", attr) == 0)
+ {
+ return true;
+ }
+
+ if (ac->added_instanceType &&
+ ldb_attr_cmp("instanceType", attr) == 0)
+ {
+ return true;
+ }
+
+ if (ac->added_objectClass &&
+ ldb_attr_cmp("objectClass", attr) == 0)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+static int aclread_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct aclread_context *ac;
+ struct aclread_private *private_data = NULL;
+ struct ldb_message *msg;
+ int ret;
+ unsigned int i;
+ struct access_check_context acl_ctx;
+
+ ac = talloc_get_type_abort(req->context, struct aclread_context);
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR );
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ msg = ares->message;
+
+ if (!ldb_dn_is_null(msg->dn)) {
+ /*
+ * this is a real object, so we have
+ * to check for visibility
+ */
+ ret = aclread_check_object_visible(ac, msg, req);
+ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ return LDB_SUCCESS;
+ } else if (ret != LDB_SUCCESS) {
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "acl_read: %s check parent %s - %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_strerror(ret),
+ ldb_errstring(ldb));
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ /* for every element in the message check RP */
+ for (i = 0; i < msg->num_elements; ++i) {
+ struct ldb_message_element *el = &msg->elements[i];
+
+ /* Remove attributes added to perform access checks. */
+ if (should_remove_attr(el->name, ac)) {
+ ldb_msg_element_mark_inaccessible(el);
+ continue;
+ }
+
+ if (acl_element_is_access_checked(el)) {
+ /* We will have already checked this attribute. */
+ continue;
+ }
+
+ /*
+ * We need to fetch the security descriptor to check
+ * this attribute.
+ */
+ break;
+ }
+
+ if (i == msg->num_elements) {
+ /* All elements have been checked. */
+ goto reply_entry_done;
+ }
+
+ ret = setup_access_check_context(ac, msg, &acl_ctx);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ private_data = talloc_get_type_abort(ldb_module_get_private(ac->module),
+ struct aclread_private);
+
+ for (/* begin where we left off */; i < msg->num_elements; ++i) {
+ struct ldb_message_element *el = &msg->elements[i];
+
+ /* Remove attributes added to perform access checks. */
+ if (should_remove_attr(el->name, ac)) {
+ ldb_msg_element_mark_inaccessible(el);
+ continue;
+ }
+
+ if (acl_element_is_access_checked(el)) {
+ /* We will have already checked this attribute. */
+ continue;
+ }
+
+ /*
+ * We need to check whether the attribute is secret,
+ * confidential, or access-controlled.
+ */
+ ret = acl_redact_attr(ac,
+ el,
+ ac,
+ private_data,
+ msg,
+ ac->schema,
+ acl_ctx.sd,
+ acl_ctx.sid,
+ acl_ctx.objectclass);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ reply_entry_done:
+ ldb_msg_remove_inaccessible(msg);
+
+ ac->num_entries++;
+ return ldb_module_send_entry(ac->req, msg, ares->controls);
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(ac->req, ares->referral);
+ case LDB_REPLY_DONE:
+ if (ac->base_invisible && ac->num_entries == 0) {
+ /*
+ * If the base is invisible and we didn't
+ * returned any object, we need to return
+ * NO_SUCH_OBJECT.
+ */
+ return ldb_module_done(ac->req,
+ NULL, NULL,
+ LDB_ERR_NO_SUCH_OBJECT);
+ }
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, LDB_SUCCESS);
+
+ }
+ return LDB_SUCCESS;
+}
+
+
+static int aclread_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ int ret;
+ struct aclread_context *ac;
+ struct ldb_request *down_req;
+ bool am_system;
+ struct ldb_result *res;
+ struct aclread_private *p;
+ bool need_sd = false;
+ bool explicit_sd_flags = false;
+ bool is_untrusted = ldb_req_is_untrusted(req);
+ static const char * const _all_attrs[] = { "*", NULL };
+ bool all_attrs = false;
+ const char * const *attrs = NULL;
+ static const char *acl_attrs[] = {
+ "instanceType",
+ NULL
+ };
+
+ ldb = ldb_module_get_ctx(module);
+ p = talloc_get_type(ldb_module_get_private(module), struct aclread_private);
+
+ am_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID) != NULL;
+ if (!am_system) {
+ am_system = dsdb_module_am_system(module);
+ }
+
+ /* skip access checks if we are system or system control is supplied
+ * or this is not LDAP server request */
+ if (!p || !p->enabled ||
+ am_system ||
+ !is_untrusted) {
+ return ldb_next_request(module, req);
+ }
+ /* no checks on special dn */
+ if (ldb_dn_is_special(req->op.search.base)) {
+ return ldb_next_request(module, req);
+ }
+
+ ac = talloc_zero(req, struct aclread_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->module = module;
+ ac->req = req;
+
+ attrs = req->op.search.attrs;
+ if (attrs == NULL) {
+ all_attrs = true;
+ attrs = _all_attrs;
+ } else if (ldb_attr_in_list(attrs, "*")) {
+ all_attrs = true;
+ }
+
+ /*
+ * In theory we should also check for the SD control but control verification is
+ * expensive so we'd better had the ntsecuritydescriptor to the list of
+ * searched attribute and then remove it !
+ */
+ ac->sd_flags = dsdb_request_sd_flags(ac->req, &explicit_sd_flags);
+
+ if (ldb_attr_in_list(attrs, "nTSecurityDescriptor")) {
+ need_sd = false;
+ } else if (explicit_sd_flags && all_attrs) {
+ need_sd = false;
+ } else {
+ need_sd = true;
+ }
+
+ if (!all_attrs) {
+ if (!ldb_attr_in_list(attrs, "instanceType")) {
+ attrs = ldb_attr_list_copy_add(ac, attrs, "instanceType");
+ if (attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->added_instanceType = true;
+ }
+ if (!ldb_attr_in_list(req->op.search.attrs, "objectSid")) {
+ attrs = ldb_attr_list_copy_add(ac, attrs, "objectSid");
+ if (attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->added_objectSid = true;
+ }
+ if (!ldb_attr_in_list(req->op.search.attrs, "objectClass")) {
+ attrs = ldb_attr_list_copy_add(ac, attrs, "objectClass");
+ if (attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->added_objectClass = true;
+ }
+ }
+
+ if (need_sd) {
+ attrs = ldb_attr_list_copy_add(ac, attrs, "nTSecurityDescriptor");
+ if (attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->added_nTSecurityDescriptor = true;
+ }
+
+ ac->am_administrator = dsdb_module_am_administrator(module);
+
+ /* check accessibility of base */
+ if (!ldb_dn_is_null(req->op.search.base)) {
+ ret = dsdb_module_search_dn(module, req, &res, req->op.search.base,
+ acl_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_error(ldb, ret,
+ "acl_read: Error retrieving instanceType for base.");
+ }
+ ret = aclread_check_object_visible(ac, res->msgs[0], req);
+ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ if (req->op.search.scope == LDB_SCOPE_BASE) {
+ return ldb_module_done(req, NULL, NULL,
+ LDB_ERR_NO_SUCH_OBJECT);
+ }
+ /*
+ * Defer LDB_ERR_NO_SUCH_OBJECT,
+ * we may return sub objects
+ */
+ ac->base_invisible = true;
+ } else if (ret != LDB_SUCCESS) {
+ return ldb_module_done(req, NULL, NULL, ret);
+ }
+ }
+
+ ret = ldb_build_search_req_ex(&down_req,
+ ldb, ac,
+ req->op.search.base,
+ req->op.search.scope,
+ req->op.search.tree,
+ attrs,
+ req->controls,
+ ac, aclread_callback,
+ req);
+
+ if (ret != LDB_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * We provide 'ac' as the control value, which is then used by the
+ * callback to avoid double-work.
+ */
+ ret = ldb_request_add_control(down_req, DSDB_CONTROL_ACL_READ_OID, false, ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_error(ldb, ret,
+ "acl_read: Error adding acl_read control.");
+ }
+
+ return ldb_next_request(module, down_req);
+}
+
+/*
+ * Here we mark inaccessible attributes known to be looked for in the
+ * filter. This only redacts attributes found in the search expression. If any
+ * extended attribute match rules examine different attributes without their own
+ * access control checks, a security bypass is possible.
+ */
+static int acl_redact_msg_for_filter(struct ldb_module *module, struct ldb_request *req, struct ldb_message *msg)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const struct aclread_private *private_data = NULL;
+ struct ldb_control *control = NULL;
+ struct aclread_context *ac = NULL;
+ struct access_check_context acl_ctx;
+ int ret;
+ unsigned i;
+
+ /*
+ * The private data contains a list of attributes which are to be
+ * considered secret.
+ */
+ private_data = talloc_get_type(ldb_module_get_private(module), struct aclread_private);
+ if (private_data == NULL) {
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "aclread_private data is missing");
+ }
+ if (!private_data->enabled) {
+ return LDB_SUCCESS;
+ }
+
+ control = ldb_request_get_control(req, DSDB_CONTROL_ACL_READ_OID);
+ if (control == NULL) {
+ /*
+ * We've bypassed the acl_read module for this request, and
+ * should skip redaction in this case.
+ */
+ return LDB_SUCCESS;
+ }
+
+ ac = talloc_get_type_abort(control->data, struct aclread_context);
+
+ if (!ac->got_tree_attrs) {
+ ret = ldb_parse_tree_collect_acl_attrs(module, ac, &ac->tree_attrs, req->op.search.tree);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ac->got_tree_attrs = true;
+ }
+
+ for (i = 0; i < msg->num_elements; ++i) {
+ struct ldb_message_element *el = &msg->elements[i];
+
+ /* Is the attribute mentioned in the search expression? */
+ if (attr_in_vec(&ac->tree_attrs, el->name)) {
+ /*
+ * We need to fetch the security descriptor to check
+ * this element.
+ */
+ break;
+ }
+
+ /*
+ * This attribute is not in the search filter, so we can leave
+ * handling it till aclread_callback(), by which time we know
+ * this object is a match. This saves work checking ACLs if the
+ * search is unindexed and most objects don't match the filter.
+ */
+ }
+
+ if (i == msg->num_elements) {
+ /* All elements have been checked. */
+ return LDB_SUCCESS;
+ }
+
+ ret = setup_access_check_context(ac, msg, &acl_ctx);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* For every element in the message and the parse tree, check RP. */
+
+ for (/* begin where we left off */; i < msg->num_elements; ++i) {
+ struct ldb_message_element *el = &msg->elements[i];
+
+ /* Is the attribute mentioned in the search expression? */
+ if (!attr_in_vec(&ac->tree_attrs, el->name)) {
+ /*
+ * If not, leave it for later and check the next
+ * attribute.
+ */
+ continue;
+ }
+
+ /*
+ * We need to check whether the attribute is secret,
+ * confidential, or access-controlled.
+ */
+ ret = acl_redact_attr(ac,
+ el,
+ ac,
+ private_data,
+ msg,
+ ac->schema,
+ acl_ctx.sd,
+ acl_ctx.sid,
+ acl_ctx.objectclass);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ acl_element_mark_access_checked(el);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int ldb_attr_cmp_fn(const void *_a, const void *_b)
+{
+ const char * const *a = _a;
+ const char * const *b = _b;
+
+ return ldb_attr_cmp(*a, *b);
+}
+
+static int aclread_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ unsigned int i, n, j;
+ TALLOC_CTX *mem_ctx = NULL;
+ int ret;
+ bool userPassword_support;
+ static const char * const attrs[] = { "passwordAttribute", NULL };
+ static const char * const secret_attrs[] = {
+ DSDB_SECRET_ATTRIBUTES
+ };
+ struct ldb_result *res;
+ struct ldb_message *msg;
+ struct ldb_message_element *password_attributes;
+ struct aclread_private *p = talloc_zero(module, struct aclread_private);
+ if (p == NULL) {
+ return ldb_module_oom(module);
+ }
+ p->enabled = lpcfg_parm_bool(ldb_get_opaque(ldb, "loadparm"), NULL, "acl", "search", true);
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_SD_FLAGS_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "acl_module_init: Unable to register sd_flags control with rootdse!\n");
+ return ldb_operr(ldb);
+ }
+
+ ldb_module_set_private(module, p);
+
+ mem_ctx = talloc_new(module);
+ if (!mem_ctx) {
+ return ldb_oom(ldb);
+ }
+
+ ret = dsdb_module_search_dn(module, mem_ctx, &res,
+ ldb_dn_new(mem_ctx, ldb, "@KLUDGEACL"),
+ attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ goto done;
+ }
+ 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;
+ }
+ p->password_attrs = talloc_array(p, const char *,
+ password_attributes->num_values +
+ ARRAY_SIZE(secret_attrs));
+ if (!p->password_attrs) {
+ talloc_free(mem_ctx);
+ return ldb_oom(ldb);
+ }
+
+ n = 0;
+ for (i=0; i < password_attributes->num_values; i++) {
+ p->password_attrs[n] = (const char *)password_attributes->values[i].data;
+ talloc_steal(p->password_attrs, password_attributes->values[i].data);
+ n++;
+ }
+
+ for (i=0; i < ARRAY_SIZE(secret_attrs); i++) {
+ bool found = false;
+
+ for (j=0; j < n; j++) {
+ if (strcasecmp(p->password_attrs[j], secret_attrs[i]) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ continue;
+ }
+
+ p->password_attrs[n] = talloc_strdup(p->password_attrs,
+ secret_attrs[i]);
+ if (p->password_attrs[n] == NULL) {
+ talloc_free(mem_ctx);
+ return ldb_oom(ldb);
+ }
+ n++;
+ }
+ p->num_password_attrs = n;
+
+ /* Sort the password attributes so we can use binary search. */
+ TYPESAFE_QSORT(p->password_attrs, p->num_password_attrs, ldb_attr_cmp_fn);
+
+ ret = ldb_register_redact_callback(ldb, acl_redact_msg_for_filter, module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+done:
+ talloc_free(mem_ctx);
+ ret = ldb_next_init(module);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (p->password_attrs != NULL) {
+ /*
+ * Check this after the modules have be initialised so we can
+ * actually read the backend DB.
+ */
+ userPassword_support = dsdb_user_password_support(module,
+ module,
+ NULL);
+ if (!userPassword_support) {
+ const char **found = NULL;
+
+ /*
+ * Remove the userPassword attribute, as it is not
+ * considered secret.
+ */
+ BINARY_ARRAY_SEARCH_V(p->password_attrs,
+ p->num_password_attrs,
+ "userPassword",
+ ldb_attr_cmp,
+ found);
+ if (found != NULL) {
+ size_t found_idx = found - p->password_attrs;
+
+ /* Shift following elements backwards by one. */
+ for (i = found_idx; i < p->num_password_attrs - 1; ++i) {
+ p->password_attrs[i] = p->password_attrs[i + 1];
+ }
+ --p->num_password_attrs;
+ }
+ }
+ }
+ return ret;
+}
+
+static const struct ldb_module_ops ldb_aclread_module_ops = {
+ .name = "aclread",
+ .search = aclread_search,
+ .init_context = aclread_init
+};
+
+int ldb_aclread_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_aclread_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/acl_util.c b/source4/dsdb/samdb/ldb_modules/acl_util.c
new file mode 100644
index 0000000..56aa4bd
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/acl_util.c
@@ -0,0 +1,356 @@
+/*
+ ACL utility functions
+
+ Copyright (C) Nadezhda Ivanova 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: acl_util
+ *
+ * Component: ldb ACL modules
+ *
+ * Description: Some auxiliary functions used for access checking
+ *
+ * Author: Nadezhda Ivanova
+ */
+#include "includes.h"
+#include "ldb_module.h"
+#include "auth/auth.h"
+#include "libcli/security/security.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "param/param.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+struct security_token *acl_user_token(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct auth_session_info *session_info
+ = (struct auth_session_info *)ldb_get_opaque(
+ ldb,
+ DSDB_SESSION_INFO);
+ if(!session_info) {
+ return NULL;
+ }
+ return session_info->security_token;
+}
+
+/* performs an access check from inside the module stack
+ * given the dn of the object to be checked, the required access
+ * guid is either the guid of the extended right, or NULL
+ */
+
+int dsdb_module_check_access_on_dn(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn,
+ uint32_t access_mask,
+ const struct GUID *guid,
+ struct ldb_request *parent)
+{
+ int ret;
+ struct ldb_result *acl_res;
+ static const char *acl_attrs[] = {
+ "nTSecurityDescriptor",
+ "objectSid",
+ NULL
+ };
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct auth_session_info *session_info
+ = (struct auth_session_info *)ldb_get_opaque(
+ ldb,
+ DSDB_SESSION_INFO);
+ if(!session_info) {
+ return ldb_operr(ldb);
+ }
+ ret = dsdb_module_search_dn(module, mem_ctx, &acl_res, dn,
+ acl_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "access_check: failed to find object %s\n",
+ ldb_dn_get_linearized(dn));
+ return ret;
+ }
+ return dsdb_check_access_on_dn_internal(ldb, acl_res,
+ mem_ctx,
+ session_info->security_token,
+ dn,
+ access_mask,
+ guid);
+}
+
+int acl_check_access_on_attribute(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ const struct security_descriptor *sd,
+ const struct dom_sid *rp_sid,
+ uint32_t access_mask,
+ const struct dsdb_attribute *attr,
+ const struct dsdb_class *objectclass)
+{
+ int ret;
+ NTSTATUS status;
+ uint32_t access_granted;
+ struct object_tree *root = NULL;
+ struct object_tree *new_node = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ struct security_token *token = acl_user_token(module);
+
+ if (!insert_in_object_tree(tmp_ctx,
+ &objectclass->schemaIDGUID,
+ access_mask, NULL,
+ &root)) {
+ DEBUG(10, ("acl_search: cannot add to object tree class schemaIDGUID\n"));
+ goto fail;
+ }
+ new_node = root;
+
+ if (!GUID_all_zero(&attr->attributeSecurityGUID)) {
+ if (!insert_in_object_tree(tmp_ctx,
+ &attr->attributeSecurityGUID,
+ access_mask, new_node,
+ &new_node)) {
+ DEBUG(10, ("acl_search: cannot add to object tree securityGUID\n"));
+ goto fail;
+ }
+ }
+
+ if (!insert_in_object_tree(tmp_ctx,
+ &attr->schemaIDGUID,
+ access_mask, new_node,
+ &new_node)) {
+ DEBUG(10, ("acl_search: cannot add to object tree attributeGUID\n"));
+ goto fail;
+ }
+
+ status = sec_access_check_ds(sd, token,
+ access_mask,
+ &access_granted,
+ root,
+ rp_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+ else {
+ ret = LDB_SUCCESS;
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+fail:
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb_module_get_ctx(module));
+}
+
+int acl_check_access_on_objectclass(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor *sd,
+ struct dom_sid *rp_sid,
+ uint32_t access_mask,
+ const struct dsdb_class *objectclass)
+{
+ int ret;
+ NTSTATUS status;
+ uint32_t access_granted;
+ struct object_tree *root = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ struct security_token *token = acl_user_token(module);
+
+ if (!insert_in_object_tree(tmp_ctx,
+ &objectclass->schemaIDGUID,
+ access_mask, NULL,
+ &root)) {
+ DEBUG(10, ("acl_search: cannot add to object tree class schemaIDGUID\n"));
+ goto fail;
+ }
+
+ status = sec_access_check_ds(sd, token,
+ access_mask,
+ &access_granted,
+ root,
+ rp_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ } else {
+ ret = LDB_SUCCESS;
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+fail:
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb_module_get_ctx(module));
+}
+
+/* checks for validated writes */
+int acl_check_extended_right(TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ struct ldb_request *req,
+ const struct dsdb_class *objectclass,
+ struct security_descriptor *sd,
+ struct security_token *token,
+ const char *ext_right,
+ uint32_t right_type,
+ struct dom_sid *sid)
+{
+ struct GUID right;
+ NTSTATUS status;
+ uint32_t access_granted;
+ struct object_tree *root = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ static const char *no_attrs[] = { NULL };
+ struct ldb_result *extended_rights_res = NULL;
+ struct ldb_dn *extended_rights_dn = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret = 0;
+
+ /*
+ * Find the extended right and check if applies to
+ * the objectclass of the object
+ */
+ extended_rights_dn = samdb_extended_rights_dn(ldb, req);
+ if (!extended_rights_dn) {
+ ldb_set_errstring(ldb,
+ "access_check: CN=Extended-Rights dn could not be generated!");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Note: we are checking only the structural object class. */
+ ret = dsdb_module_search(module, req, &extended_rights_res,
+ extended_rights_dn, LDB_SCOPE_ONELEVEL,
+ no_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM,
+ req,
+ "(&(rightsGuid=%s)(appliesTo=%s))",
+ ext_right,
+ GUID_string(tmp_ctx,
+ &(objectclass->schemaIDGUID)));
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ } else if (extended_rights_res->count == 0 ) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "acl_check_extended_right: Could not find appliesTo for %s\n",
+ ext_right);
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+
+ GUID_from_string(ext_right, &right);
+
+ if (!insert_in_object_tree(tmp_ctx, &right, right_type,
+ NULL, &root)) {
+ DEBUG(10, ("acl_ext_right: cannot add to object tree\n"));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ status = sec_access_check_ds(sd, token,
+ right_type,
+ &access_granted,
+ root,
+ sid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+const char *acl_user_name(TALLOC_CTX *mem_ctx, struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct auth_session_info *session_info
+ = (struct auth_session_info *)ldb_get_opaque(
+ ldb,
+ DSDB_SESSION_INFO);
+ if (!session_info) {
+ return "UNKNOWN (NULL)";
+ }
+
+ return talloc_asprintf(mem_ctx, "%s\\%s",
+ session_info->info->domain_name,
+ session_info->info->account_name);
+}
+
+uint32_t dsdb_request_sd_flags(struct ldb_request *req, bool *explicit)
+{
+ struct ldb_control *sd_control;
+ uint32_t sd_flags = 0;
+
+ if (explicit) {
+ *explicit = false;
+ }
+
+ sd_control = ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID);
+ if (sd_control != NULL && sd_control->data != NULL) {
+ struct ldb_sd_flags_control *sdctr = talloc_get_type_abort(sd_control->data, struct ldb_sd_flags_control);
+
+ sd_flags = sdctr->secinfo_flags;
+
+ if (explicit) {
+ *explicit = true;
+ }
+
+ /* mark it as handled */
+ sd_control->critical = 0;
+ }
+
+ /* we only care for the last 4 bits */
+ sd_flags &= 0x0000000F;
+
+ /*
+ * MS-ADTS 3.1.1.3.4.1.11 says that no bits
+ * equals all 4 bits
+ */
+ if (sd_flags == 0) {
+ sd_flags = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL | SECINFO_SACL;
+ }
+
+ return sd_flags;
+}
+
+int dsdb_module_schedule_sd_propagation(struct ldb_module *module,
+ struct ldb_dn *nc_root,
+ struct GUID guid,
+ struct GUID parent_guid,
+ bool include_self)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct dsdb_extended_sec_desc_propagation_op *op;
+ int ret;
+
+ op = talloc_zero(module, struct dsdb_extended_sec_desc_propagation_op);
+ if (op == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ op->nc_root = nc_root;
+ op->guid = guid;
+ op->include_self = include_self;
+ op->parent_guid = parent_guid;
+
+ ret = dsdb_module_extended(module, op, NULL,
+ DSDB_EXTENDED_SEC_DESC_PROPAGATION_OID,
+ op,
+ DSDB_FLAG_TOP_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_FLAG_TRUSTED,
+ NULL);
+ TALLOC_FREE(op);
+ return ret;
+}
diff --git a/source4/dsdb/samdb/ldb_modules/anr.c b/source4/dsdb/samdb/ldb_modules/anr.c
new file mode 100644
index 0000000..e083f5d
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/anr.c
@@ -0,0 +1,436 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007
+ Copyright (C) Simo Sorce <idra@samba.org> 2008
+ 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_module.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+#undef strcasecmp
+
+/**
+ * Make a and 'and' or 'or' tree from the two supplied elements
+ */
+static 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_context *ldb;
+ struct ldb_parse_tree *list;
+
+ ldb = ldb_module_get_ctx(module);
+
+ list = talloc(mem_ctx, struct ldb_parse_tree);
+ if (list == NULL){
+ ldb_oom(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(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
+ */
+static struct ldb_parse_tree *make_match_tree(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ enum ldb_parse_op op,
+ const char *attr,
+ struct ldb_val *match)
+{
+ struct ldb_context *ldb;
+ struct ldb_parse_tree *match_tree;
+
+ ldb = ldb_module_get_ctx(module);
+
+ 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){
+ talloc_free(match_tree);
+ ldb_oom(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;
+ default:
+ talloc_free(match_tree);
+ return NULL;
+ }
+ return match_tree;
+}
+
+struct anr_context {
+ bool found_anr;
+ struct ldb_module *module;
+ struct ldb_request *req;
+};
+
+/**
+ * Given the match for an 'ambigious name resolution' query, create a
+ * parse tree with an 'or' of all the anr attributes in the schema.
+ */
+
+/**
+ * Callback function to do the heavy lifting for the parse tree walker
+ */
+static int anr_replace_value(struct anr_context *ac,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_val *match,
+ struct ldb_parse_tree **ntree)
+{
+ struct ldb_parse_tree *tree = NULL;
+ struct ldb_module *module = ac->module;
+ struct ldb_parse_tree *match_tree;
+ struct dsdb_attribute *cur;
+ const struct dsdb_schema *schema;
+ struct ldb_context *ldb;
+ uint8_t *p;
+ enum ldb_parse_op op;
+
+ ldb = ldb_module_get_ctx(module);
+
+ schema = dsdb_get_schema(ldb, ac);
+ if (!schema) {
+ ldb_asprintf_errstring(ldb, "no schema with which to construct anr filter");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (match->length > 1 && match->data[0] == '=') {
+ struct ldb_val *match2 = talloc(mem_ctx, struct ldb_val);
+ if (match2 == NULL){
+ return ldb_oom(ldb);
+ }
+ *match2 = data_blob_const(match->data+1, match->length - 1);
+ 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) {
+ return ldb_oom(ldb);
+ }
+ } 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;
+ struct ldb_val *first_match = talloc(tree, struct ldb_val);
+ struct ldb_val *second_match = talloc(tree, struct ldb_val);
+ if (!first_match || !second_match) {
+ return ldb_oom(ldb);
+ }
+ *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, ac, LDB_OP_AND, match_tree_1, match_tree_2);
+ if (first_split_filter == NULL){
+ return ldb_oom(ldb);
+ }
+
+ 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, ac, LDB_OP_AND, match_tree_1, match_tree_2);
+ if (second_split_filter == NULL){
+ return ldb_oom(ldb);
+ }
+
+ split_filters = make_parse_list(module, mem_ctx, LDB_OP_OR,
+ first_split_filter, second_split_filter);
+ if (split_filters == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ 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;
+ }
+ }
+ *ntree = tree;
+ return LDB_SUCCESS;
+}
+
+/*
+ replace any occurances of an attribute with a new, generated attribute tree
+*/
+static int anr_replace_subtrees(struct anr_context *ac,
+ struct ldb_parse_tree *tree,
+ const char *attr,
+ struct ldb_parse_tree **ntree)
+{
+ int ret;
+ unsigned int i;
+
+ switch (tree->operation) {
+ case LDB_OP_AND:
+ case LDB_OP_OR:
+ for (i=0;i<tree->u.list.num_elements;i++) {
+ ret = anr_replace_subtrees(ac, tree->u.list.elements[i],
+ attr, &tree->u.list.elements[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ *ntree = tree;
+ }
+ break;
+ case LDB_OP_NOT:
+ ret = anr_replace_subtrees(ac, tree->u.isnot.child, attr, &tree->u.isnot.child);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ *ntree = tree;
+ break;
+ case LDB_OP_EQUALITY:
+ if (ldb_attr_cmp(tree->u.equality.attr, attr) == 0) {
+ ret = anr_replace_value(ac, tree, &tree->u.equality.value, ntree);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ 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) {
+ ret = anr_replace_value(ac, tree, tree->u.substring.chunks[0], ntree);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return LDB_SUCCESS;
+}
+
+struct anr_present_ctx {
+ bool found_anr;
+ const char *attr;
+};
+
+/*
+ callback to determine if ANR is in use at all
+ */
+static int parse_tree_anr_present(struct ldb_parse_tree *tree, void *private_context)
+{
+ struct anr_present_ctx *ctx = private_context;
+ switch (tree->operation) {
+ case LDB_OP_EQUALITY:
+ case LDB_OP_GREATER:
+ case LDB_OP_LESS:
+ case LDB_OP_APPROX:
+ if (ldb_attr_cmp(tree->u.equality.attr, ctx->attr) == 0) {
+ ctx->found_anr = true;
+ }
+ break;
+ case LDB_OP_SUBSTRING:
+ if (ldb_attr_cmp(tree->u.substring.attr, ctx->attr) == 0) {
+ ctx->found_anr = true;
+ }
+ break;
+ case LDB_OP_PRESENT:
+ if (ldb_attr_cmp(tree->u.present.attr, ctx->attr) == 0) {
+ ctx->found_anr = true;
+ }
+ break;
+ case LDB_OP_EXTENDED:
+ if (tree->u.extended.attr &&
+ ldb_attr_cmp(tree->u.extended.attr, ctx->attr) == 0) {
+ ctx->found_anr = true;
+ }
+ break;
+ default:
+ break;
+ }
+ return LDB_SUCCESS;
+}
+
+
+static int anr_search_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct anr_context *ac;
+
+ ac = talloc_get_type(req->context, struct anr_context);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ return ldb_module_send_entry(ac->req, ares->message, ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(ac->req, ares->referral);
+
+ case LDB_REPLY_DONE:
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, LDB_SUCCESS);
+
+ }
+ return LDB_SUCCESS;
+}
+
+/* search */
+static int anr_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct ldb_parse_tree *anr_tree;
+ struct ldb_request *down_req;
+ struct anr_context *ac;
+ struct anr_present_ctx ctx;
+ const char *attr = "anr";
+ int ret;
+
+ ctx.found_anr = false;
+ ctx.attr = attr;
+
+ ldb_parse_tree_walk(req->op.search.tree,
+ parse_tree_anr_present,
+ &ctx);
+
+ if (!ctx.found_anr) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = talloc(req, struct anr_context);
+ if (!ac) {
+ return ldb_oom(ldb);
+ }
+
+ ac->module = module;
+ ac->req = req;
+
+#if 0
+ printf("oldanr : %s\n", ldb_filter_from_tree (0, req->op.search.tree));
+#endif
+
+ /* First make a copy, so we don't overwrite caller memory */
+
+ anr_tree = ldb_parse_tree_copy_shallow(ac, req->op.search.tree);
+
+ if (anr_tree == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* Now expand 'anr' out */
+ ret = anr_replace_subtrees(ac, anr_tree, attr, &anr_tree);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ ret = ldb_build_search_req_ex(&down_req,
+ ldb, ac,
+ req->op.search.base,
+ req->op.search.scope,
+ anr_tree,
+ req->op.search.attrs,
+ req->controls,
+ ac, anr_search_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+ talloc_steal(down_req, anr_tree);
+
+ return ldb_next_request(module, down_req);
+}
+
+static const struct ldb_module_ops ldb_anr_module_ops = {
+ .name = "anr",
+ .search = anr_search
+};
+
+int ldb_anr_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_anr_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/audit_log.c b/source4/dsdb/samdb/ldb_modules/audit_log.c
new file mode 100644
index 0000000..7cc3ff6
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/audit_log.c
@@ -0,0 +1,1913 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+
+ 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/>.
+*/
+
+/*
+ * Provide an audit log of changes made to the database and at a
+ * higher level details of any password changes and resets.
+ *
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "lib/audit_logging/audit_logging.h"
+
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "dsdb/samdb/ldb_modules/audit_util_proto.h"
+#include "libcli/security/dom_sid.h"
+#include "auth/common_auth.h"
+#include "param/param.h"
+#include "librpc/gen_ndr/windows_event_ids.h"
+
+#define OPERATION_JSON_TYPE "dsdbChange"
+#define OPERATION_HR_TAG "DSDB Change"
+#define OPERATION_MAJOR 1
+#define OPERATION_MINOR 0
+#define OPERATION_LOG_LVL 5
+
+#define PASSWORD_JSON_TYPE "passwordChange"
+#define PASSWORD_HR_TAG "Password Change"
+#define PASSWORD_MAJOR 1
+#define PASSWORD_MINOR 1
+#define PASSWORD_LOG_LVL 5
+
+#define TRANSACTION_JSON_TYPE "dsdbTransaction"
+#define TRANSACTION_HR_TAG "DSDB Transaction"
+#define TRANSACTION_MAJOR 1
+#define TRANSACTION_MINOR 0
+#define TRANSACTION_LOG_FAILURE_LVL 5
+#define TRANSACTION_LOG_COMPLETION_LVL 10
+
+#define REPLICATION_JSON_TYPE "replicatedUpdate"
+#define REPLICATION_HR_TAG "Replicated Update"
+#define REPLICATION_MAJOR 1
+#define REPLICATION_MINOR 0
+#define REPLICATION_LOG_LVL 5
+/*
+ * Attribute values are truncated in the logs if they are longer than
+ * MAX_LENGTH
+ */
+#define MAX_LENGTH 1024
+
+#define min(a, b) (((a)>(b))?(b):(a))
+
+/*
+ * Private data for the module, stored in the ldb_module private data
+ */
+struct audit_private {
+ /*
+ * Should details of database operations be sent over the
+ * messaging bus.
+ */
+ bool send_samdb_events;
+ /*
+ * Should details of password changes and resets be sent over
+ * the messaging bus.
+ */
+ bool send_password_events;
+ /*
+ * The messaging context to send the messages over. Will only
+ * be set if send_samdb_events or send_password_events are
+ * true.
+ */
+ struct imessaging_context *msg_ctx;
+ /*
+ * Unique transaction id for the current transaction
+ */
+ struct GUID transaction_guid;
+ /*
+ * Transaction start time, used to calculate the transaction
+ * duration.
+ */
+ struct timeval transaction_start;
+};
+
+/*
+ * @brief Has the password changed.
+ *
+ * Does the message contain a change to one of the password attributes? The
+ * password attributes are defined in DSDB_PASSWORD_ATTRIBUTES
+ *
+ * @return true if the message contains a password attribute
+ *
+ */
+static bool has_password_changed(const struct ldb_message *message)
+{
+ unsigned int i;
+ if (message == NULL) {
+ return false;
+ }
+ for (i=0;i<message->num_elements;i++) {
+ if (dsdb_audit_is_password_attribute(
+ message->elements[i].name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * @brief get the password change windows event id
+ *
+ * Get the Windows Event Id for the action being performed on the user password.
+ *
+ * This routine assumes that the request contains password attributes and that the
+ * password ACL checks have been performed by acl.c
+ *
+ * @param request the ldb_request to inspect
+ * @param reply the ldb_reply, will contain the password controls
+ *
+ * @return The windows event code.
+ */
+static enum event_id_type get_password_windows_event_id(
+ const struct ldb_request *request,
+ const struct ldb_reply *reply)
+{
+ if(request->operation == LDB_ADD) {
+ return EVT_ID_PASSWORD_RESET;
+ } else {
+ struct ldb_control *pav_ctrl = NULL;
+ struct dsdb_control_password_acl_validation *pav = NULL;
+
+ pav_ctrl = ldb_reply_get_control(
+ discard_const(reply),
+ DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID);
+ if (pav_ctrl == NULL) {
+ return EVT_ID_PASSWORD_RESET;
+ }
+
+ pav = talloc_get_type_abort(
+ pav_ctrl->data,
+ struct dsdb_control_password_acl_validation);
+
+ if (pav->pwd_reset) {
+ return EVT_ID_PASSWORD_RESET;
+ } else {
+ return EVT_ID_PASSWORD_CHANGE;
+ }
+ }
+}
+/*
+ * @brief Is the request a password "Change" or a "Reset"
+ *
+ * Get a description of the action being performed on the user password. This
+ * routine assumes that the request contains password attributes and that the
+ * password ACL checks have been performed by acl.c
+ *
+ * @param request the ldb_request to inspect
+ * @param reply the ldb_reply, will contain the password controls
+ *
+ * @return "Change" if the password is being changed.
+ * "Reset" if the password is being reset.
+ */
+static const char *get_password_action(
+ const struct ldb_request *request,
+ const struct ldb_reply *reply)
+{
+ if(request->operation == LDB_ADD) {
+ return "Reset";
+ } else {
+ struct ldb_control *pav_ctrl = NULL;
+ struct dsdb_control_password_acl_validation *pav = NULL;
+
+ pav_ctrl = ldb_reply_get_control(
+ discard_const(reply),
+ DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID);
+ if (pav_ctrl == NULL) {
+ return "Reset";
+ }
+
+ pav = talloc_get_type_abort(
+ pav_ctrl->data,
+ struct dsdb_control_password_acl_validation);
+
+ if (pav->pwd_reset) {
+ return "Reset";
+ } else {
+ return "Change";
+ }
+ }
+}
+
+/*
+ * @brief generate a JSON object detailing an ldb operation.
+ *
+ * Generate a JSON object detailing an ldb operation.
+ *
+ * @param module the ldb module
+ * @param request the request
+ * @param reply the result of the operation.
+ *
+ * @return the generated JSON object, should be freed with json_free.
+ *
+ *
+ */
+static struct json_object operation_json(
+ struct ldb_module *module,
+ const struct ldb_request *request,
+ const struct ldb_reply *reply)
+{
+ struct ldb_context *ldb = NULL;
+ const struct dom_sid *sid = NULL;
+ bool as_system = false;
+ struct json_object wrapper = json_empty_object;
+ struct json_object audit = json_empty_object;
+ const struct tsocket_address *remote = NULL;
+ const char *dn = NULL;
+ const char* operation = NULL;
+ const struct GUID *unique_session_token = NULL;
+ const struct ldb_message *message = NULL;
+ struct audit_private *audit_private
+ = talloc_get_type_abort(ldb_module_get_private(module),
+ struct audit_private);
+ int rc = 0;
+
+ ldb = ldb_module_get_ctx(module);
+
+ remote = dsdb_audit_get_remote_address(ldb);
+ if (remote != NULL && dsdb_audit_is_system_session(module)) {
+ as_system = true;
+ sid = dsdb_audit_get_actual_sid(ldb);
+ unique_session_token =
+ dsdb_audit_get_actual_unique_session_token(ldb);
+ } else {
+ sid = dsdb_audit_get_user_sid(module);
+ unique_session_token =
+ dsdb_audit_get_unique_session_token(module);
+ }
+ dn = dsdb_audit_get_primary_dn(request);
+ operation = dsdb_audit_get_operation_name(request);
+
+ audit = json_new_object();
+ if (json_is_invalid(&audit)) {
+ goto failure;
+ }
+ rc = json_add_version(&audit, OPERATION_MAJOR, OPERATION_MINOR);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_int(&audit, "statusCode", reply->error);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "status", ldb_strerror(reply->error));
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "operation", operation);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_address(&audit, "remoteAddress", remote);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_bool(&audit, "performedAsSystem", as_system);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_sid(&audit, "userSid", sid);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "dn", dn);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_guid(
+ &audit, "transactionId", &audit_private->transaction_guid);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_guid(&audit, "sessionId", unique_session_token);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ message = dsdb_audit_get_message(request);
+ if (message != NULL) {
+ struct json_object attributes =
+ dsdb_audit_attributes_json(
+ request->operation,
+ message);
+ if (json_is_invalid(&attributes)) {
+ goto failure;
+ }
+ rc = json_add_object(&audit, "attributes", &attributes);
+ if (rc != 0) {
+ goto failure;
+ }
+ }
+
+ wrapper = json_new_object();
+ if (json_is_invalid(&wrapper)) {
+ goto failure;
+ }
+ rc = json_add_timestamp(&wrapper);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&wrapper, "type", OPERATION_JSON_TYPE);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_object(&wrapper, OPERATION_JSON_TYPE, &audit);
+ if (rc != 0) {
+ goto failure;
+ }
+ return wrapper;
+
+failure:
+ /*
+ * On a failure audit will not have been added to wrapper so it
+ * needs to free it to avoid a leak.
+ *
+ * wrapper is freed to invalidate it as it will have only been
+ * partially constructed and may be inconsistent.
+ *
+ * All the json manipulation routines handle a freed object correctly
+ */
+ json_free(&audit);
+ json_free(&wrapper);
+ DBG_ERR("Unable to create ldb operation JSON audit message\n");
+ return wrapper;
+}
+
+/*
+ * @brief generate a JSON object detailing a replicated update.
+ *
+ * Generate a JSON object detailing a replicated update
+ *
+ * @param module the ldb module
+ * @param request the request
+ * @paran reply the result of the operation
+ *
+ * @return the generated JSON object, should be freed with json_free.
+ * NULL if there was an error generating the message.
+ *
+ */
+static struct json_object replicated_update_json(
+ struct ldb_module *module,
+ const struct ldb_request *request,
+ const struct ldb_reply *reply)
+{
+ struct json_object wrapper = json_empty_object;
+ struct json_object audit = json_empty_object;
+ struct audit_private *audit_private
+ = talloc_get_type_abort(ldb_module_get_private(module),
+ struct audit_private);
+ struct dsdb_extended_replicated_objects *ro = talloc_get_type(
+ request->op.extended.data,
+ struct dsdb_extended_replicated_objects);
+ const char *partition_dn = NULL;
+ const char *error = NULL;
+ int rc = 0;
+
+ partition_dn = ldb_dn_get_linearized(ro->partition_dn);
+ error = get_friendly_werror_msg(ro->error);
+
+ audit = json_new_object();
+ if (json_is_invalid(&audit)) {
+ goto failure;
+ }
+ rc = json_add_version(&audit, REPLICATION_MAJOR, REPLICATION_MINOR);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_int(&audit, "statusCode", reply->error);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "status", ldb_strerror(reply->error));
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_guid(
+ &audit, "transactionId", &audit_private->transaction_guid);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_int(&audit, "objectCount", ro->num_objects);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_int(&audit, "linkCount", ro->linked_attributes_count);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "partitionDN", partition_dn);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "error", error);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_int(&audit, "errorCode", W_ERROR_V(ro->error));
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_guid(
+ &audit, "sourceDsa", &ro->source_dsa->source_dsa_obj_guid);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_guid(
+ &audit, "invocationId", &ro->source_dsa->source_dsa_invocation_id);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ wrapper = json_new_object();
+ if (json_is_invalid(&wrapper)) {
+ goto failure;
+ }
+ rc = json_add_timestamp(&wrapper);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&wrapper, "type", REPLICATION_JSON_TYPE);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_object(&wrapper, REPLICATION_JSON_TYPE, &audit);
+ if (rc != 0) {
+ goto failure;
+ }
+ return wrapper;
+failure:
+ /*
+ * On a failure audit will not have been added to wrapper so it
+ * needs to be freed it to avoid a leak.
+ *
+ * wrapper is freed to invalidate it as it will have only been
+ * partially constructed and may be inconsistent.
+ *
+ * All the json manipulation routines handle a freed object correctly
+ */
+ json_free(&audit);
+ json_free(&wrapper);
+ DBG_ERR("Unable to create replicated update JSON audit message\n");
+ return wrapper;
+}
+
+/*
+ * @brief generate a JSON object detailing a password change.
+ *
+ * Generate a JSON object detailing a password change.
+ *
+ * @param module the ldb module
+ * @param request the request
+ * @param reply the result/response
+ * @param status the status code returned for the underlying ldb operation.
+ *
+ * @return the generated JSON object.
+ *
+ */
+static struct json_object password_change_json(
+ struct ldb_module *module,
+ const struct ldb_request *request,
+ const struct ldb_reply *reply)
+{
+ struct ldb_context *ldb = NULL;
+ const struct dom_sid *sid = NULL;
+ const char *dn = NULL;
+ struct json_object wrapper = json_empty_object;
+ struct json_object audit = json_empty_object;
+ const struct tsocket_address *remote = NULL;
+ const char* action = NULL;
+ const struct GUID *unique_session_token = NULL;
+ struct audit_private *audit_private
+ = talloc_get_type_abort(ldb_module_get_private(module),
+ struct audit_private);
+ int rc = 0;
+ enum event_id_type event_id;
+
+ ldb = ldb_module_get_ctx(module);
+
+ remote = dsdb_audit_get_remote_address(ldb);
+ sid = dsdb_audit_get_user_sid(module);
+ dn = dsdb_audit_get_primary_dn(request);
+ action = get_password_action(request, reply);
+ unique_session_token = dsdb_audit_get_unique_session_token(module);
+ event_id = get_password_windows_event_id(request, reply);
+
+ audit = json_new_object();
+ if (json_is_invalid(&audit)) {
+ goto failure;
+ }
+ rc = json_add_version(&audit, PASSWORD_MAJOR, PASSWORD_MINOR);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_int(&audit, "eventId", event_id);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_int(&audit, "statusCode", reply->error);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "status", ldb_strerror(reply->error));
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_address(&audit, "remoteAddress", remote);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_sid(&audit, "userSid", sid);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "dn", dn);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "action", action);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_guid(
+ &audit, "transactionId", &audit_private->transaction_guid);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_guid(&audit, "sessionId", unique_session_token);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ wrapper = json_new_object();
+ if (json_is_invalid(&wrapper)) {
+ goto failure;
+ }
+ rc = json_add_timestamp(&wrapper);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&wrapper, "type", PASSWORD_JSON_TYPE);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_object(&wrapper, PASSWORD_JSON_TYPE, &audit);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ return wrapper;
+failure:
+ /*
+ * On a failure audit will not have been added to wrapper so it
+ * needs to free it to avoid a leak.
+ *
+ * wrapper is freed to invalidate it as it will have only been
+ * partially constructed and may be inconsistent.
+ *
+ * All the json manipulation routines handle a freed object correctly
+ */
+ json_free(&wrapper);
+ json_free(&audit);
+ DBG_ERR("Unable to create password change JSON audit message\n");
+ return wrapper;
+}
+
+
+/*
+ * @brief create a JSON object containing details of a transaction event.
+ *
+ * Create a JSON object detailing a transaction transaction life cycle events,
+ * i.e. begin, commit, roll back
+ *
+ * @param action a one word description of the event/action
+ * @param transaction_id the GUID identifying the current transaction.
+ * @param status the status code returned by the operation
+ * @param duration the duration of the operation.
+ *
+ * @return a JSON object detailing the event
+ */
+static struct json_object transaction_json(
+ const char *action,
+ struct GUID *transaction_id,
+ const int64_t duration)
+{
+ struct json_object wrapper = json_empty_object;
+ struct json_object audit = json_empty_object;
+ int rc = 0;
+
+ audit = json_new_object();
+ if (json_is_invalid(&audit)) {
+ goto failure;
+ }
+
+ rc = json_add_version(&audit, TRANSACTION_MAJOR, TRANSACTION_MINOR);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "action", action);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_guid(&audit, "transactionId", transaction_id);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_int(&audit, "duration", duration);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ wrapper = json_new_object();
+ if (json_is_invalid(&wrapper)) {
+ goto failure;
+ }
+ rc = json_add_timestamp(&wrapper);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&wrapper, "type", TRANSACTION_JSON_TYPE);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_object(&wrapper, TRANSACTION_JSON_TYPE, &audit);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ return wrapper;
+failure:
+ /*
+ * On a failure audit will not have been added to wrapper so it
+ * needs to free it to avoid a leak.
+ *
+ * wrapper is freed to invalidate it as it will have only been
+ * partially constructed and may be inconsistent.
+ *
+ * All the json manipulation routines handle a freed object correctly
+ */
+ json_free(&wrapper);
+ json_free(&audit);
+ DBG_ERR("Unable to create transaction JSON audit message\n");
+ return wrapper;
+}
+
+
+/*
+ * @brief generate a JSON object detailing a commit failure.
+ *
+ * Generate a JSON object containing details of a commit failure.
+ *
+ * @param action the commit action, "commit" or "prepare"
+ * @param status the status code returned by commit
+ * @param reason any extra failure information/reason available
+ * @param transaction_id the GUID identifying the current transaction.
+ */
+static struct json_object commit_failure_json(
+ const char *action,
+ const int64_t duration,
+ int status,
+ const char *reason,
+ struct GUID *transaction_id)
+{
+ struct json_object wrapper = json_empty_object;
+ struct json_object audit = json_empty_object;
+ int rc = 0;
+
+ audit = json_new_object();
+ if (json_is_invalid(&audit)) {
+ goto failure;
+ }
+ rc = json_add_version(&audit, TRANSACTION_MAJOR, TRANSACTION_MINOR);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "action", action);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_guid(&audit, "transactionId", transaction_id);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_int(&audit, "duration", duration);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_int(&audit, "statusCode", status);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "status", ldb_strerror(status));
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "reason", reason);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ wrapper = json_new_object();
+ if (json_is_invalid(&wrapper)) {
+ goto failure;
+ }
+ rc = json_add_timestamp(&wrapper);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&wrapper, "type", TRANSACTION_JSON_TYPE);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_object(&wrapper, TRANSACTION_JSON_TYPE, &audit);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ return wrapper;
+failure:
+ /*
+ * On a failure audit will not have been added to wrapper so it
+ * needs to free it to avoid a leak.
+ *
+ * wrapper is freed to invalidate it as it will have only been
+ * partially constructed and may be inconsistent.
+ *
+ * All the json manipulation routines handle a freed object correctly
+ */
+ json_free(&audit);
+ json_free(&wrapper);
+ DBG_ERR("Unable to create commit failure JSON audit message\n");
+ return wrapper;
+}
+
+/*
+ * @brief Print a human readable log line for a password change event.
+ *
+ * Generate a human readable log line detailing a password change.
+ *
+ * @param mem_ctx The talloc context that will own the generated log line.
+ * @param module the ldb module
+ * @param request the request
+ * @param reply the result/response
+ * @param status the status code returned for the underlying ldb operation.
+ *
+ * @return the generated log line.
+ */
+static char *password_change_human_readable(
+ TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ const struct ldb_request *request,
+ const struct ldb_reply *reply)
+{
+ struct ldb_context *ldb = NULL;
+ const char *remote_host = NULL;
+ const struct dom_sid *sid = NULL;
+ struct dom_sid_buf user_sid;
+ const char *timestamp = NULL;
+ char *log_entry = NULL;
+ const char *action = NULL;
+ const char *dn = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_module_get_ctx(module);
+
+ remote_host = dsdb_audit_get_remote_host(ldb, ctx);
+ sid = dsdb_audit_get_user_sid(module);
+ timestamp = audit_get_timestamp(ctx);
+ action = get_password_action(request, reply);
+ dn = dsdb_audit_get_primary_dn(request);
+
+ log_entry = talloc_asprintf(
+ mem_ctx,
+ "[%s] at [%s] status [%s] "
+ "remote host [%s] SID [%s] DN [%s]",
+ action,
+ timestamp,
+ ldb_strerror(reply->error),
+ remote_host,
+ dom_sid_str_buf(sid, &user_sid),
+ dn);
+ TALLOC_FREE(ctx);
+ return log_entry;
+}
+/*
+ * @brief Generate a human readable string, detailing attributes in a message
+ *
+ * For modify operations each attribute is prefixed with the action.
+ * Normal values are enclosed in []
+ * Base64 values are enclosed in {}
+ * Truncated values are indicated by three trailing dots "..."
+ *
+ * @param ldb The ldb_context
+ * @param buffer The attributes will be appended to the buffer.
+ * assumed to have been allocated via talloc.
+ * @param operation The operation type
+ * @param message the message to process
+ *
+ */
+static char *log_attributes(
+ struct ldb_context *ldb,
+ char *buffer,
+ enum ldb_request_type operation,
+ const struct ldb_message *message)
+{
+ size_t i, j;
+ for (i=0;i<message->num_elements;i++) {
+ if (i > 0) {
+ buffer = talloc_asprintf_append_buffer(buffer, " ");
+ }
+
+ if (message->elements[i].name == NULL) {
+ ldb_debug(
+ ldb,
+ LDB_DEBUG_ERROR,
+ "Error: Invalid element name (NULL) at "
+ "position %zu", i);
+ return NULL;
+ }
+
+ if (operation == LDB_MODIFY) {
+ const char *action =NULL;
+ action = dsdb_audit_get_modification_action(
+ message->elements[i].flags);
+ buffer = talloc_asprintf_append_buffer(
+ buffer,
+ "%s: %s ",
+ action,
+ message->elements[i].name);
+ } else {
+ buffer = talloc_asprintf_append_buffer(
+ buffer,
+ "%s ",
+ message->elements[i].name);
+ }
+
+ if (dsdb_audit_redact_attribute(message->elements[i].name)) {
+ /*
+ * Do not log the value of any secret or password
+ * attributes
+ */
+ buffer = talloc_asprintf_append_buffer(
+ buffer,
+ "[REDACTED SECRET ATTRIBUTE]");
+ continue;
+ }
+
+ for (j=0;j<message->elements[i].num_values;j++) {
+ struct ldb_val v;
+ bool use_b64_encode = false;
+ size_t length;
+ if (j > 0) {
+ buffer = talloc_asprintf_append_buffer(
+ buffer,
+ " ");
+ }
+
+ v = message->elements[i].values[j];
+ length = min(MAX_LENGTH, v.length);
+ use_b64_encode = ldb_should_b64_encode(ldb, &v);
+ if (use_b64_encode) {
+ const char *encoded = ldb_base64_encode(
+ buffer,
+ (char *)v.data,
+ length);
+ buffer = talloc_asprintf_append_buffer(
+ buffer,
+ "{%s%s}",
+ encoded,
+ (v.length > MAX_LENGTH ? "..." : ""));
+ } else {
+ buffer = talloc_asprintf_append_buffer(
+ buffer,
+ "[%*.*s%s]",
+ (int)length,
+ (int)length,
+ (char *)v.data,
+ (v.length > MAX_LENGTH ? "..." : ""));
+ }
+ }
+ }
+ return buffer;
+}
+
+/*
+ * @brief generate a human readable log entry detailing an ldb operation.
+ *
+ * Generate a human readable log entry detailing an ldb operation.
+ *
+ * @param mem_ctx The talloc context owning the returned string.
+ * @param module the ldb module
+ * @param request the request
+ * @param reply the result of the operation
+ *
+ * @return the log entry.
+ *
+ */
+static char *operation_human_readable(
+ TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ const struct ldb_request *request,
+ const struct ldb_reply *reply)
+{
+ struct ldb_context *ldb = NULL;
+ const char *remote_host = NULL;
+ const struct tsocket_address *remote = NULL;
+ const struct dom_sid *sid = NULL;
+ struct dom_sid_buf user_sid;
+ const char *timestamp = NULL;
+ const char *op_name = NULL;
+ char *log_entry = NULL;
+ const char *dn = NULL;
+ const char *new_dn = NULL;
+ const struct ldb_message *message = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_module_get_ctx(module);
+
+ remote_host = dsdb_audit_get_remote_host(ldb, ctx);
+ remote = dsdb_audit_get_remote_address(ldb);
+ if (remote != NULL && dsdb_audit_is_system_session(module)) {
+ sid = dsdb_audit_get_actual_sid(ldb);
+ } else {
+ sid = dsdb_audit_get_user_sid(module);
+ }
+ timestamp = audit_get_timestamp(ctx);
+ op_name = dsdb_audit_get_operation_name(request);
+ dn = dsdb_audit_get_primary_dn(request);
+ new_dn = dsdb_audit_get_secondary_dn(request);
+
+ message = dsdb_audit_get_message(request);
+
+ log_entry = talloc_asprintf(
+ mem_ctx,
+ "[%s] at [%s] status [%s] "
+ "remote host [%s] SID [%s] DN [%s]",
+ op_name,
+ timestamp,
+ ldb_strerror(reply->error),
+ remote_host,
+ dom_sid_str_buf(sid, &user_sid),
+ dn);
+ if (new_dn != NULL) {
+ log_entry = talloc_asprintf_append_buffer(
+ log_entry,
+ " New DN [%s]",
+ new_dn);
+ }
+ if (message != NULL) {
+ log_entry = talloc_asprintf_append_buffer(log_entry,
+ " attributes [");
+ log_entry = log_attributes(ldb,
+ log_entry,
+ request->operation,
+ message);
+ log_entry = talloc_asprintf_append_buffer(log_entry, "]");
+ }
+ TALLOC_FREE(ctx);
+ return log_entry;
+}
+
+/*
+ * @brief generate a human readable log entry detailing a replicated update
+ * operation.
+ *
+ * Generate a human readable log entry detailing a replicated update operation
+ *
+ * @param mem_ctx The talloc context owning the returned string.
+ * @param module the ldb module
+ * @param request the request
+ * @param reply the result of the operation.
+ *
+ * @return the log entry.
+ *
+ */
+static char *replicated_update_human_readable(
+ TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ const struct ldb_request *request,
+ const struct ldb_reply *reply)
+{
+ struct dsdb_extended_replicated_objects *ro = talloc_get_type(
+ request->op.extended.data,
+ struct dsdb_extended_replicated_objects);
+ const char *partition_dn = NULL;
+ const char *error = NULL;
+ char *log_entry = NULL;
+ char *timestamp = NULL;
+ struct GUID_txt_buf object_buf;
+ const char *object = NULL;
+ struct GUID_txt_buf invocation_buf;
+ const char *invocation = NULL;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ timestamp = audit_get_timestamp(ctx);
+ error = get_friendly_werror_msg(ro->error);
+ partition_dn = ldb_dn_get_linearized(ro->partition_dn);
+ object = GUID_buf_string(
+ &ro->source_dsa->source_dsa_obj_guid,
+ &object_buf);
+ invocation = GUID_buf_string(
+ &ro->source_dsa->source_dsa_invocation_id,
+ &invocation_buf);
+
+
+ log_entry = talloc_asprintf(
+ mem_ctx,
+ "at [%s] status [%s] error [%s] partition [%s] objects [%d] "
+ "links [%d] object [%s] invocation [%s]",
+ timestamp,
+ ldb_strerror(reply->error),
+ error,
+ partition_dn,
+ ro->num_objects,
+ ro->linked_attributes_count,
+ object,
+ invocation);
+
+ TALLOC_FREE(ctx);
+ return log_entry;
+}
+/*
+ * @brief create a human readable log entry detailing a transaction event.
+ *
+ * Create a human readable log entry detailing a transaction event.
+ * i.e. begin, commit, roll back
+ *
+ * @param mem_ctx The talloc context owning the returned string.
+ * @param action a one word description of the event/action
+ * @param duration the duration of the transaction.
+ *
+ * @return the log entry
+ */
+static char *transaction_human_readable(
+ TALLOC_CTX *mem_ctx,
+ const char* action,
+ const int64_t duration)
+{
+ const char *timestamp = NULL;
+ char *log_entry = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ timestamp = audit_get_timestamp(ctx);
+
+ log_entry = talloc_asprintf(
+ mem_ctx,
+ "[%s] at [%s] duration [%"PRIi64"]",
+ action,
+ timestamp,
+ duration);
+
+ TALLOC_FREE(ctx);
+ return log_entry;
+}
+
+/*
+ * @brief generate a human readable log entry detailing a commit failure.
+ *
+ * Generate generate a human readable log entry detailing a commit failure.
+ *
+ * @param mem_ctx The talloc context owning the returned string.
+ * @param action the commit action, "prepare" or "commit"
+ * @param status the status code returned by commit
+ * @param reason any extra failure information/reason available
+ *
+ * @return the log entry
+ */
+static char *commit_failure_human_readable(
+ TALLOC_CTX *mem_ctx,
+ const char *action,
+ const int64_t duration,
+ int status,
+ const char *reason)
+{
+ const char *timestamp = NULL;
+ char *log_entry = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ timestamp = audit_get_timestamp(ctx);
+
+ log_entry = talloc_asprintf(
+ mem_ctx,
+ "[%s] at [%s] duration [%"PRIi64"] status [%d] reason [%s]",
+ action,
+ timestamp,
+ duration,
+ status,
+ reason);
+
+ TALLOC_FREE(ctx);
+ return log_entry;
+}
+
+/*
+ * @brief log details of a standard ldb operation.
+ *
+ * Log the details of an ldb operation in JSON and or human readable format
+ * and send over the message bus.
+ *
+ * @param module the ldb_module
+ * @param request the operation request.
+ * @param reply the operation result.
+ * @param the status code returned for the operation.
+ *
+ */
+static void log_standard_operation(
+ struct ldb_module *module,
+ const struct ldb_request *request,
+ const struct ldb_reply *reply)
+{
+
+ const struct ldb_message *message = dsdb_audit_get_message(request);
+ bool password_changed = has_password_changed(message);
+ struct audit_private *audit_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct audit_private);
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_AUDIT, OPERATION_LOG_LVL)) {
+ char *entry = NULL;
+ entry = operation_human_readable(
+ ctx,
+ module,
+ request,
+ reply);
+ audit_log_human_text(
+ OPERATION_HR_TAG,
+ entry,
+ DBGC_DSDB_AUDIT,
+ OPERATION_LOG_LVL);
+ TALLOC_FREE(entry);
+ }
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_PWD_AUDIT, PASSWORD_LOG_LVL)) {
+ if (password_changed) {
+ char *entry = NULL;
+ entry = password_change_human_readable(
+ ctx,
+ module,
+ request,
+ reply);
+ audit_log_human_text(
+ PASSWORD_HR_TAG,
+ entry,
+ DBGC_DSDB_PWD_AUDIT,
+ PASSWORD_LOG_LVL);
+ TALLOC_FREE(entry);
+ }
+ }
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_AUDIT_JSON, OPERATION_LOG_LVL) ||
+ (audit_private->msg_ctx
+ && audit_private->send_samdb_events)) {
+ struct json_object json;
+ json = operation_json(module, request, reply);
+ audit_log_json(
+ &json,
+ DBGC_DSDB_AUDIT_JSON,
+ OPERATION_LOG_LVL);
+ if (audit_private->msg_ctx
+ && audit_private->send_samdb_events) {
+ audit_message_send(
+ audit_private->msg_ctx,
+ DSDB_EVENT_NAME,
+ MSG_DSDB_LOG,
+ &json);
+ }
+ json_free(&json);
+ }
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_PWD_AUDIT_JSON, PASSWORD_LOG_LVL) ||
+ (audit_private->msg_ctx
+ && audit_private->send_password_events)) {
+ if (password_changed) {
+ struct json_object json;
+ json = password_change_json(module, request, reply);
+ audit_log_json(
+ &json,
+ DBGC_DSDB_PWD_AUDIT_JSON,
+ PASSWORD_LOG_LVL);
+ if (audit_private->send_password_events) {
+ audit_message_send(
+ audit_private->msg_ctx,
+ DSDB_PWD_EVENT_NAME,
+ MSG_DSDB_PWD_LOG,
+ &json);
+ }
+ json_free(&json);
+ }
+ }
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * @brief log details of a replicated update.
+ *
+ * Log the details of a replicated update in JSON and or human readable
+ * format and send over the message bus.
+ *
+ * @param module the ldb_module
+ * @param request the operation request
+ * @param reply the result of the operation.
+ *
+ */
+static void log_replicated_operation(
+ struct ldb_module *module,
+ const struct ldb_request *request,
+ const struct ldb_reply *reply)
+{
+
+ struct audit_private *audit_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct audit_private);
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_AUDIT, REPLICATION_LOG_LVL)) {
+ char *entry = NULL;
+ entry = replicated_update_human_readable(
+ ctx,
+ module,
+ request,
+ reply);
+ audit_log_human_text(
+ REPLICATION_HR_TAG,
+ entry,
+ DBGC_DSDB_AUDIT,
+ REPLICATION_LOG_LVL);
+ TALLOC_FREE(entry);
+ }
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_AUDIT_JSON, REPLICATION_LOG_LVL) ||
+ (audit_private->msg_ctx && audit_private->send_samdb_events)) {
+ struct json_object json;
+ json = replicated_update_json(module, request, reply);
+ audit_log_json(
+ &json,
+ DBGC_DSDB_AUDIT_JSON,
+ REPLICATION_LOG_LVL);
+ if (audit_private->send_samdb_events) {
+ audit_message_send(
+ audit_private->msg_ctx,
+ DSDB_EVENT_NAME,
+ MSG_DSDB_LOG,
+ &json);
+ }
+ json_free(&json);
+ }
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * @brief log details of an ldb operation.
+ *
+ * Log the details of an ldb operation in JSON and or human readable format
+ * and send over the message bus.
+ *
+ * @param module the ldb_module
+ * @param request the operation request
+ * @part reply the result of the operation
+ *
+ */
+static void log_operation(
+ struct ldb_module *module,
+ const struct ldb_request *request,
+ const struct ldb_reply *reply)
+{
+
+ if (request->operation == LDB_EXTENDED) {
+ if (strcmp(
+ request->op.extended.oid,
+ DSDB_EXTENDED_REPLICATED_OBJECTS_OID) != 0) {
+
+ log_replicated_operation(module, request, reply);
+ }
+ } else {
+ log_standard_operation(module, request, reply);
+ }
+}
+
+/*
+ * @brief log details of a transaction event.
+ *
+ * Log the details of a transaction event in JSON and or human readable format
+ * and send over the message bus.
+ *
+ * @param module the ldb_module
+ * @param action the transaction event i.e. begin, commit, roll back.
+ * @param log_level the logging level
+ *
+ */
+static void log_transaction(
+ struct ldb_module *module,
+ const char *action,
+ int log_level)
+{
+
+ struct audit_private *audit_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct audit_private);
+ const struct timeval now = timeval_current();
+ const int64_t duration = usec_time_diff(&now, &audit_private->transaction_start);
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_TXN_AUDIT, log_level)) {
+ char* entry = NULL;
+ entry = transaction_human_readable(ctx, action, duration);
+ audit_log_human_text(
+ TRANSACTION_HR_TAG,
+ entry,
+ DBGC_DSDB_TXN_AUDIT,
+ log_level);
+ TALLOC_FREE(entry);
+ }
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_TXN_AUDIT_JSON, log_level) ||
+ (audit_private->msg_ctx && audit_private->send_samdb_events)) {
+ struct json_object json;
+ json = transaction_json(
+ action,
+ &audit_private->transaction_guid,
+ duration);
+ audit_log_json(
+ &json,
+ DBGC_DSDB_TXN_AUDIT_JSON,
+ log_level);
+ if (audit_private->send_samdb_events) {
+ audit_message_send(
+ audit_private->msg_ctx,
+ DSDB_EVENT_NAME,
+ MSG_DSDB_LOG,
+ &json);
+ }
+ json_free(&json);
+ }
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * @brief log details of a commit failure.
+ *
+ * Log the details of a commit failure in JSON and or human readable
+ * format and send over the message bus.
+ *
+ * @param module the ldb_module
+ * @param action the commit action "prepare" or "commit"
+ * @param status the ldb status code returned by prepare commit.
+ *
+ */
+static void log_commit_failure(
+ struct ldb_module *module,
+ const char *action,
+ int status)
+{
+
+ struct audit_private *audit_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct audit_private);
+ const char* reason = dsdb_audit_get_ldb_error_string(module, status);
+ const int log_level = TRANSACTION_LOG_FAILURE_LVL;
+ const struct timeval now = timeval_current();
+ const int64_t duration = usec_time_diff(&now,
+ &audit_private->transaction_start);
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_TXN_AUDIT, log_level)) {
+
+ char* entry = NULL;
+ entry = commit_failure_human_readable(
+ ctx,
+ action,
+ duration,
+ status,
+ reason);
+ audit_log_human_text(
+ TRANSACTION_HR_TAG,
+ entry,
+ DBGC_DSDB_TXN_AUDIT,
+ TRANSACTION_LOG_FAILURE_LVL);
+ TALLOC_FREE(entry);
+ }
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_TXN_AUDIT_JSON, log_level) ||
+ (audit_private->msg_ctx
+ && audit_private->send_samdb_events)) {
+ struct json_object json;
+ json = commit_failure_json(
+ action,
+ duration,
+ status,
+ reason,
+ &audit_private->transaction_guid);
+ audit_log_json(
+ &json,
+ DBGC_DSDB_TXN_AUDIT_JSON,
+ log_level);
+ if (audit_private->send_samdb_events) {
+ audit_message_send(audit_private->msg_ctx,
+ DSDB_EVENT_NAME,
+ MSG_DSDB_LOG,
+ &json);
+ }
+ json_free(&json);
+ }
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * Context needed by audit_callback
+ */
+struct audit_callback_context {
+ struct ldb_request *request;
+ struct ldb_module *module;
+};
+
+/*
+ * @brief call back function for the ldb_operations.
+ *
+ * As the LDB operations are async, and we wish to examine the results of
+ * the operations, a callback needs to be registered to process the results
+ * of the LDB operations.
+ *
+ * @param req the ldb request
+ * @param res the result of the operation
+ *
+ * @return the LDB_STATUS
+ */
+static int audit_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct audit_callback_context *ac = NULL;
+
+ ac = talloc_get_type(
+ req->context,
+ struct audit_callback_context);
+
+ if (!ares) {
+ return ldb_module_done(
+ ac->request,
+ NULL,
+ NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* pass on to the callback */
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ return ldb_module_send_entry(
+ ac->request,
+ ares->message,
+ ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(
+ ac->request,
+ ares->referral);
+
+ case LDB_REPLY_DONE:
+ /*
+ * Log the operation once DONE
+ */
+ log_operation(ac->module, ac->request, ares);
+ return ldb_module_done(
+ ac->request,
+ ares->controls,
+ ares->response,
+ ares->error);
+
+ default:
+ /* Can't happen */
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+}
+
+/*
+ * @brief Add the current transaction identifier to the request.
+ *
+ * Add the current transaction identifier in the module private data,
+ * to the request as a control.
+ *
+ * @param module
+ * @param req the request.
+ *
+ * @return an LDB_STATUS code, LDB_SUCCESS if successful.
+ */
+static int add_transaction_id(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct audit_private *audit_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct audit_private);
+ struct dsdb_control_transaction_identifier *transaction_id;
+ int ret;
+
+ transaction_id = talloc_zero(
+ req,
+ struct dsdb_control_transaction_identifier);
+ if (transaction_id == NULL) {
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ return ldb_oom(ldb);
+ }
+ transaction_id->transaction_guid = audit_private->transaction_guid;
+ ret = ldb_request_add_control(req,
+ DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID,
+ false,
+ transaction_id);
+ return ret;
+
+}
+
+/*
+ * @brief log details of an add operation.
+ *
+ * Log the details of an add operation.
+ *
+ * @param module the ldb_module
+ * @param req the ldb_request
+ *
+ * @return ldb status code
+ */
+static int log_add(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct audit_callback_context *context = NULL;
+ struct ldb_request *new_req = NULL;
+ struct ldb_context *ldb = NULL;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+ context = talloc_zero(req, struct audit_callback_context);
+
+ if (context == NULL) {
+ return ldb_oom(ldb);
+ }
+ context->request = req;
+ context->module = module;
+ /*
+ * We want to log the return code status, so we need to register
+ * a callback function to get the actual result.
+ * We need to take a new copy so that we don't alter the callers copy
+ */
+ ret = ldb_build_add_req(
+ &new_req,
+ ldb,
+ req,
+ req->op.add.message,
+ req->controls,
+ context,
+ audit_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = add_transaction_id(module, new_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(module, new_req);
+}
+
+/*
+ * @brief log details of an delete operation.
+ *
+ * Log the details of an delete operation.
+ *
+ * @param module the ldb_module
+ * @param req the ldb_request
+ *
+ * @return ldb status code
+ */
+static int log_delete(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct audit_callback_context *context = NULL;
+ struct ldb_request *new_req = NULL;
+ struct ldb_context *ldb = NULL;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+ context = talloc_zero(req, struct audit_callback_context);
+
+ if (context == NULL) {
+ return ldb_oom(ldb);
+ }
+ context->request = req;
+ context->module = module;
+ /*
+ * We want to log the return code status, so we need to register
+ * a callback function to get the actual result.
+ * We need to take a new copy so that we don't alter the callers copy
+ */
+ ret = ldb_build_del_req(&new_req,
+ ldb,
+ req,
+ req->op.del.dn,
+ req->controls,
+ context,
+ audit_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = add_transaction_id(module, new_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(module, new_req);
+}
+
+/*
+ * @brief log details of a modify operation.
+ *
+ * Log the details of a modify operation.
+ *
+ * @param module the ldb_module
+ * @param req the ldb_request
+ *
+ * @return ldb status code
+ */
+static int log_modify(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct audit_callback_context *context = NULL;
+ struct ldb_request *new_req = NULL;
+ struct ldb_context *ldb = NULL;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+ context = talloc_zero(req, struct audit_callback_context);
+
+ if (context == NULL) {
+ return ldb_oom(ldb);
+ }
+ context->request = req;
+ context->module = module;
+ /*
+ * We want to log the return code status, so we need to register
+ * a callback function to get the actual result.
+ * We need to take a new copy so that we don't alter the callers copy
+ */
+ ret = ldb_build_mod_req(
+ & new_req,
+ ldb,
+ req,
+ req->op.mod.message,
+ req->controls,
+ context,
+ audit_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = add_transaction_id(module, new_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(module, new_req);
+}
+
+/*
+ * @brief process a transaction start.
+ *
+ * process a transaction start, as we don't currently log transaction starts
+ * just generate the new transaction_id.
+ *
+ * @param module the ldb_module
+ * @param req the ldb_request
+ *
+ * @return ldb status code
+ */
+static int log_start_transaction(struct ldb_module *module)
+{
+ struct audit_private *audit_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct audit_private);
+
+ /*
+ * We do not log transaction begins
+ * however we do generate a new transaction_id and record the start
+ * time so that we can log the transaction duration.
+ *
+ */
+ audit_private->transaction_guid = GUID_random();
+ audit_private->transaction_start = timeval_current();
+ return ldb_next_start_trans(module);
+}
+
+/*
+ * @brief log details of a prepare commit.
+ *
+ * Log the details of a prepare commit, currently only details of
+ * failures are logged.
+ *
+ * @param module the ldb_module
+ * @param req the ldb_request
+ *
+ * @return ldb status code
+ */
+static int log_prepare_commit(struct ldb_module *module)
+{
+
+ int ret = ldb_next_prepare_commit(module);
+ if (ret != LDB_SUCCESS) {
+ /*
+ * We currently only log prepare commit failures
+ */
+ log_commit_failure(module, "prepare", ret);
+ }
+ return ret;
+}
+
+/*
+ * @brief process a transaction end aka commit.
+ *
+ * process a transaction end, as we don't currently log transaction ends
+ * just clear transaction_id.
+ *
+ * @param module the ldb_module
+ * @param req the ldb_request
+ *
+ * @return ldb status code
+ */
+static int log_end_transaction(struct ldb_module *module)
+{
+ struct audit_private *audit_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct audit_private);
+ int ret = 0;
+
+
+ ret = ldb_next_end_trans(module);
+ if (ret == LDB_SUCCESS) {
+ log_transaction(
+ module,
+ "commit",
+ TRANSACTION_LOG_COMPLETION_LVL);
+ } else {
+ log_commit_failure(module, "commit", ret);
+ }
+ /*
+ * Clear the transaction id inserted by log_start_transaction
+ */
+ audit_private->transaction_guid = GUID_zero();
+ return ret;
+}
+
+/*
+ * @brief log details of a transaction delete aka roll back.
+ *
+ * Log details of a transaction roll back.
+ *
+ * @param module the ldb_module
+ * @param req the ldb_request
+ *
+ * @return ldb status code
+ */
+static int log_del_transaction(struct ldb_module *module)
+{
+ struct audit_private *audit_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct audit_private);
+
+ log_transaction(module, "rollback", TRANSACTION_LOG_FAILURE_LVL);
+ audit_private->transaction_guid = GUID_zero();
+ return ldb_next_del_trans(module);
+}
+
+/*
+ * @brief log details of an extended operation.
+ *
+ * Log the details of an extended operation.
+ *
+ * @param module the ldb_module
+ * @param req the ldb_request
+ *
+ * @return ldb status code
+ */
+static int log_extended(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct audit_callback_context *context = NULL;
+ struct ldb_request *new_req = NULL;
+ struct ldb_context *ldb = NULL;
+ int ret;
+
+ /*
+ * Currently we only log replication extended operations
+ */
+ if (strcmp(
+ req->op.extended.oid,
+ DSDB_EXTENDED_REPLICATED_OBJECTS_OID) != 0) {
+
+ return ldb_next_request(module, req);
+ }
+ ldb = ldb_module_get_ctx(module);
+ context = talloc_zero(req, struct audit_callback_context);
+
+ if (context == NULL) {
+ return ldb_oom(ldb);
+ }
+ context->request = req;
+ context->module = module;
+ /*
+ * We want to log the return code status, so we need to register
+ * a callback function to get the actual result.
+ * We need to take a new copy so that we don't alter the callers copy
+ */
+ ret = ldb_build_extended_req(
+ &new_req,
+ ldb,
+ req,
+ req->op.extended.oid,
+ req->op.extended.data,
+ req->controls,
+ context,
+ audit_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = add_transaction_id(module, new_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(module, new_req);
+}
+
+/*
+ * @brief module initialisation
+ */
+static int log_init(struct ldb_module *module)
+{
+
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct audit_private *audit_private = NULL;
+ struct loadparm_context *lp_ctx
+ = talloc_get_type_abort(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+ struct tevent_context *ev = ldb_get_event_context(ldb);
+ bool sdb_events = false;
+ bool pwd_events = false;
+
+ audit_private = talloc_zero(module, struct audit_private);
+ if (audit_private == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ if (lp_ctx != NULL) {
+ sdb_events = lpcfg_dsdb_event_notification(lp_ctx);
+ pwd_events = lpcfg_dsdb_password_event_notification(lp_ctx);
+ }
+ if (sdb_events || pwd_events) {
+ audit_private->send_samdb_events = sdb_events;
+ audit_private->send_password_events = pwd_events;
+ audit_private->msg_ctx
+ = imessaging_client_init(audit_private,
+ lp_ctx,
+ ev);
+ }
+
+ ldb_module_set_private(module, audit_private);
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_audit_log_module_ops = {
+ .name = "audit_log",
+ .init_context = log_init,
+ .add = log_add,
+ .modify = log_modify,
+ .del = log_delete,
+ .start_transaction = log_start_transaction,
+ .prepare_commit = log_prepare_commit,
+ .end_transaction = log_end_transaction,
+ .del_transaction = log_del_transaction,
+ .extended = log_extended,
+};
+
+int ldb_audit_log_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_audit_log_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/audit_util.c b/source4/dsdb/samdb/ldb_modules/audit_util.c
new file mode 100644
index 0000000..f251025
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/audit_util.c
@@ -0,0 +1,697 @@
+/*
+ ldb database module utility library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+
+ 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/>.
+*/
+
+/*
+ * Common utility functions for SamDb audit logging.
+ *
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "lib/audit_logging/audit_logging.h"
+
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "libcli/security/dom_sid.h"
+#include "libcli/security/security_token.h"
+#include "auth/common_auth.h"
+#include "param/param.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "dsdb/samdb/ldb_modules/audit_util_proto.h"
+
+#define MAX_LENGTH 1024
+
+#define min(a, b) (((a)>(b))?(b):(a))
+
+/*
+ * List of attributes considered secret or confidential the values of these
+ * attributes should not be displayed in log messages.
+ */
+static const char * const secret_attributes[] = {
+ DSDB_SECRET_ATTRIBUTES,
+ NULL};
+/*
+ * List of attributes that contain a password, used to detect password changes
+ */
+static const char * const password_attributes[] = {
+ DSDB_PASSWORD_ATTRIBUTES,
+ NULL};
+
+/*
+ * @brief Should the value of the specified value be redacted.
+ *
+ * The values of secret or password attributes should not be displayed.
+ *
+ * @param name The attributes name.
+ *
+ * @return True if the attribute should be redacted
+ */
+bool dsdb_audit_redact_attribute(const char * name)
+{
+
+ if (ldb_attr_in_list(secret_attributes, name)) {
+ return true;
+ }
+
+ if (ldb_attr_in_list(password_attributes, name)) {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * @brief is the attribute a password attribute?
+ *
+ * Is the attribute a password attribute.
+ *
+ * @return True if the attribute is a "Password" attribute.
+ */
+bool dsdb_audit_is_password_attribute(const char * name)
+{
+
+ bool is_password = ldb_attr_in_list(password_attributes, name);
+ return is_password;
+}
+
+/*
+ * @brief Get the remote address from the ldb context.
+ *
+ * The remote address is stored in the ldb opaque value "remoteAddress"
+ * it is the responsibility of the higher level code to ensure that this
+ * value is set.
+ *
+ * @param ldb the ldb_context.
+ *
+ * @return the remote address if known, otherwise NULL.
+ */
+const struct tsocket_address *dsdb_audit_get_remote_address(
+ struct ldb_context *ldb)
+{
+ void *opaque_remote_address = NULL;
+ struct tsocket_address *remote_address;
+
+ opaque_remote_address = ldb_get_opaque(ldb,
+ "remoteAddress");
+ if (opaque_remote_address == NULL) {
+ return NULL;
+ }
+
+ remote_address = talloc_get_type(opaque_remote_address,
+ struct tsocket_address);
+ return remote_address;
+}
+
+/*
+ * @brief Get the actual user SID from ldb context.
+ *
+ * The actual user SID is stored in the ldb opaque value "networkSessionInfo"
+ * it is the responsibility of the higher level code to ensure that this
+ * value is set.
+ *
+ * @param ldb the ldb_context.
+ *
+ * @return the users actual sid.
+ */
+const struct dom_sid *dsdb_audit_get_actual_sid(struct ldb_context *ldb)
+{
+ void *opaque_session = NULL;
+ struct auth_session_info *session = NULL;
+ struct security_token *user_token = NULL;
+
+ opaque_session = ldb_get_opaque(ldb, DSDB_NETWORK_SESSION_INFO);
+ if (opaque_session == NULL) {
+ return NULL;
+ }
+
+ session = talloc_get_type(opaque_session, struct auth_session_info);
+ if (session == NULL) {
+ return NULL;
+ }
+
+ user_token = session->security_token;
+ if (user_token == NULL) {
+ return NULL;
+ }
+ return &user_token->sids[0];
+}
+/*
+ * @brief get the ldb error string.
+ *
+ * Get the ldb error string if set, otherwise get the generic error code
+ * for the status code.
+ *
+ * @param ldb the ldb_context.
+ * @param status the ldb_status code.
+ *
+ * @return a string describing the error.
+ */
+const char *dsdb_audit_get_ldb_error_string(
+ struct ldb_module *module,
+ int status)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const char *err_string = ldb_errstring(ldb);
+
+ if (err_string == NULL) {
+ return ldb_strerror(status);
+ }
+ return err_string;
+}
+
+/*
+ * @brief get the SID of the user performing the operation.
+ *
+ * Get the SID of the user performing the operation.
+ *
+ * @param module the ldb_module.
+ *
+ * @return the SID of the currently logged on user.
+ */
+const struct dom_sid *dsdb_audit_get_user_sid(const struct ldb_module *module)
+{
+ struct security_token *user_token = NULL;
+
+ /*
+ * acl_user_token does not alter module so it's safe
+ * to discard the const.
+ */
+ user_token = acl_user_token(discard_const(module));
+ if (user_token == NULL) {
+ return NULL;
+ }
+ return &user_token->sids[0];
+
+}
+
+/*
+ * @brief is operation being performed using the system session.
+ *
+ * Is the operation being performed using the system session.
+ *
+ * @param module the ldb_module.
+ *
+ * @return true if the operation is being performed using the system session.
+ */
+bool dsdb_audit_is_system_session(const struct ldb_module *module)
+{
+ struct security_token *user_token = NULL;
+
+ /*
+ * acl_user_token does not alter module and security_token_is_system
+ * does not alter the security token so it's safe to discard the const.
+ */
+ user_token = acl_user_token(discard_const(module));
+ if (user_token == NULL) {
+ return false;
+ }
+ return security_token_is_system(user_token);;
+
+}
+
+/*
+ * @brief get the session identifier GUID
+ *
+ * Get the GUID that uniquely identifies the current authenticated session.
+ *
+ * @param module the ldb_module.
+ *
+ * @return the unique session GUID
+ */
+const struct GUID *dsdb_audit_get_unique_session_token(
+ const struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(discard_const(module));
+ struct auth_session_info *session_info
+ = (struct auth_session_info *)ldb_get_opaque(
+ ldb,
+ DSDB_SESSION_INFO);
+ if(!session_info) {
+ return NULL;
+ }
+ return &session_info->unique_session_token;
+}
+
+/*
+ * @brief get the actual user session identifier
+ *
+ * Get the GUID that uniquely identifies the current authenticated session.
+ * This is the session of the connected user, as it may differ from the
+ * session the operation is being performed as, i.e. for operations performed
+ * under the system session.
+ *
+ * @param context the ldb_context.
+ *
+ * @return the unique session GUID
+ */
+const struct GUID *dsdb_audit_get_actual_unique_session_token(
+ struct ldb_context *ldb)
+{
+ struct auth_session_info *session_info
+ = (struct auth_session_info *)ldb_get_opaque(
+ ldb,
+ DSDB_NETWORK_SESSION_INFO);
+ if(!session_info) {
+ return NULL;
+ }
+ return &session_info->unique_session_token;
+}
+
+/*
+ * @brief Get a printable string value for the remote host address.
+ *
+ * Get a printable string representation of the remote host, for display in the
+ * the audit logs.
+ *
+ * @param ldb the ldb context.
+ * @param mem_ctx the talloc memory context that will own the returned string.
+ *
+ * @return A string representation of the remote host address or "Unknown"
+ *
+ */
+char *dsdb_audit_get_remote_host(struct ldb_context *ldb, TALLOC_CTX *mem_ctx)
+{
+ const struct tsocket_address *remote_address;
+ char* remote_host = NULL;
+
+ remote_address = dsdb_audit_get_remote_address(ldb);
+ if (remote_address == NULL) {
+ remote_host = talloc_asprintf(mem_ctx, "Unknown");
+ return remote_host;
+ }
+
+ remote_host = tsocket_address_string(remote_address, mem_ctx);
+ return remote_host;
+}
+
+/*
+ * @brief get a printable representation of the primary DN.
+ *
+ * Get a printable representation of the primary DN. The primary DN is the
+ * DN of the object being added, deleted, modified or renamed.
+ *
+ * @param the ldb_request.
+ *
+ * @return a printable and linearized DN
+ */
+const char* dsdb_audit_get_primary_dn(const struct ldb_request *request)
+{
+ struct ldb_dn *dn = NULL;
+ switch (request->operation) {
+ case LDB_ADD:
+ if (request->op.add.message != NULL) {
+ dn = request->op.add.message->dn;
+ }
+ break;
+ case LDB_MODIFY:
+ if (request->op.mod.message != NULL) {
+ dn = request->op.mod.message->dn;
+ }
+ break;
+ case LDB_DELETE:
+ dn = request->op.del.dn;
+ break;
+ case LDB_RENAME:
+ dn = request->op.rename.olddn;
+ break;
+ default:
+ dn = NULL;
+ break;
+ }
+ if (dn == NULL) {
+ return NULL;
+ }
+ return ldb_dn_get_linearized(dn);
+}
+
+/*
+ * @brief Get the ldb_message from a request.
+ *
+ * Get the ldb_message for the request, returns NULL is there is no
+ * associated ldb_message
+ *
+ * @param The request
+ *
+ * @return the message associated with this request, or NULL
+ */
+const struct ldb_message *dsdb_audit_get_message(
+ const struct ldb_request *request)
+{
+ switch (request->operation) {
+ case LDB_ADD:
+ return request->op.add.message;
+ case LDB_MODIFY:
+ return request->op.mod.message;
+ default:
+ return NULL;
+ }
+}
+
+/*
+ * @brief get the secondary dn, i.e. the target dn for a rename.
+ *
+ * Get the secondary dn, i.e. the target for a rename. This is only applicable
+ * got a rename operation, for the non rename operations this function returns
+ * NULL.
+ *
+ * @param request the ldb_request.
+ *
+ * @return the secondary dn in a printable and linearized form.
+ */
+const char *dsdb_audit_get_secondary_dn(const struct ldb_request *request)
+{
+ switch (request->operation) {
+ case LDB_RENAME:
+ return ldb_dn_get_linearized(request->op.rename.newdn);
+ default:
+ return NULL;
+ }
+}
+
+/*
+ * @brief Map the request operation to a description.
+ *
+ * Get a description of the operation for logging
+ *
+ * @param request the ldb_request
+ *
+ * @return a string describing the operation, or "Unknown" if the operation
+ * is not known.
+ */
+const char *dsdb_audit_get_operation_name(const struct ldb_request *request)
+{
+ switch (request->operation) {
+ case LDB_SEARCH:
+ return "Search";
+ case LDB_ADD:
+ return "Add";
+ case LDB_MODIFY:
+ return "Modify";
+ case LDB_DELETE:
+ return "Delete";
+ case LDB_RENAME:
+ return "Rename";
+ case LDB_EXTENDED:
+ return "Extended";
+ case LDB_REQ_REGISTER_CONTROL:
+ return "Register Control";
+ case LDB_REQ_REGISTER_PARTITION:
+ return "Register Partition";
+ default:
+ return "Unknown";
+ }
+}
+
+/*
+ * @brief get a description of a modify action for logging.
+ *
+ * Get a brief description of the modification action suitable for logging.
+ *
+ * @param flags the ldb_attributes flags.
+ *
+ * @return a brief description, or "unknown".
+ */
+const char *dsdb_audit_get_modification_action(unsigned int flags)
+{
+ switch (LDB_FLAG_MOD_TYPE(flags)) {
+ case LDB_FLAG_MOD_ADD:
+ return "add";
+ case LDB_FLAG_MOD_DELETE:
+ return "delete";
+ case LDB_FLAG_MOD_REPLACE:
+ return "replace";
+ default:
+ return "unknown";
+ }
+}
+
+/*
+ * @brief Add an ldb_value to a json object array
+ *
+ * Convert the current ldb_value to a JSON object and append it to array.
+ * {
+ * "value":"xxxxxxxx",
+ * "base64":true
+ * "truncated":true
+ * }
+ *
+ * value is the JSON string representation of the ldb_val,
+ * will be null if the value is zero length. The value will be
+ * truncated if it is more than MAX_LENGTH bytes long. It will also
+ * be base64 encoded if it contains any non printable characters.
+ *
+ * base64 Indicates that the value is base64 encoded, will be absent if the
+ * value is not encoded.
+ *
+ * truncated Indicates that the length of the value exceeded MAX_LENGTH and was
+ * truncated. Note that vales are truncated and then base64 encoded.
+ * so an encoded value can be longer than MAX_LENGTH.
+ *
+ * @param array the JSON array to append the value to.
+ * @param lv the ldb_val to convert and append to the array.
+ *
+ */
+static int dsdb_audit_add_ldb_value(struct json_object *array,
+ const struct ldb_val lv)
+{
+ bool base64;
+ int len;
+ struct json_object value = json_empty_object;
+ int rc = 0;
+
+ json_assert_is_array(array);
+ if (json_is_invalid(array)) {
+ return -1;
+ }
+
+ if (lv.length == 0 || lv.data == NULL) {
+ rc = json_add_object(array, NULL, NULL);
+ if (rc != 0) {
+ goto failure;
+ }
+ return 0;
+ }
+
+ base64 = ldb_should_b64_encode(NULL, &lv);
+ len = min(lv.length, MAX_LENGTH);
+ value = json_new_object();
+ if (json_is_invalid(&value)) {
+ goto failure;
+ }
+
+ if (lv.length > MAX_LENGTH) {
+ rc = json_add_bool(&value, "truncated", true);
+ if (rc != 0) {
+ goto failure;
+ }
+ }
+ if (base64) {
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ char *encoded = ldb_base64_encode(
+ ctx,
+ (char*) lv.data,
+ len);
+
+ if (ctx == NULL) {
+ goto failure;
+ }
+
+ rc = json_add_bool(&value, "base64", true);
+ if (rc != 0) {
+ TALLOC_FREE(ctx);
+ goto failure;
+ }
+ rc = json_add_string(&value, "value", encoded);
+ if (rc != 0) {
+ TALLOC_FREE(ctx);
+ goto failure;
+ }
+ TALLOC_FREE(ctx);
+ } else {
+ rc = json_add_stringn(&value, "value", (char *)lv.data, len);
+ if (rc != 0) {
+ goto failure;
+ }
+ }
+ /*
+ * As array is a JSON array the element name is NULL
+ */
+ rc = json_add_object(array, NULL, &value);
+ if (rc != 0) {
+ goto failure;
+ }
+ return 0;
+failure:
+ /*
+ * In the event of a failure value will not have been added to array
+ * so it needs to be freed to prevent a leak.
+ */
+ json_free(&value);
+ DBG_ERR("unable to add ldb value to JSON audit message");
+ return -1;
+}
+
+/*
+ * @brief Build a JSON object containing the attributes in an ldb_message.
+ *
+ * Build a JSON object containing all the attributes in an ldb_message.
+ * The attributes are keyed by attribute name, the values of "secret attributes"
+ * are supressed.
+ *
+ * {
+ * "password":{
+ * "redacted":true,
+ * "action":"delete"
+ * },
+ * "name":{
+ * "values": [
+ * {
+ * "value":"xxxxxxxx",
+ * "base64":true
+ * "truncated":true
+ * },
+ * ],
+ * "action":"add",
+ * }
+ * }
+ *
+ * values is an array of json objects generated by add_ldb_value.
+ * redacted indicates that the attribute is secret.
+ * action is only set for modification operations.
+ *
+ * @param operation the ldb operation being performed
+ * @param message the ldb_message to process.
+ *
+ * @return A populated json object.
+ *
+ */
+struct json_object dsdb_audit_attributes_json(
+ enum ldb_request_type operation,
+ const struct ldb_message* message)
+{
+
+ unsigned int i, j;
+ struct json_object attributes = json_new_object();
+
+ if (json_is_invalid(&attributes)) {
+ goto failure;
+ }
+ for (i=0;i<message->num_elements;i++) {
+ struct json_object actions = json_empty_object;
+ struct json_object attribute = json_empty_object;
+ struct json_object action = json_empty_object;
+ const char *name = message->elements[i].name;
+ int rc = 0;
+
+ action = json_new_object();
+ if (json_is_invalid(&action)) {
+ goto failure;
+ }
+
+ /*
+ * If this is a modify operation tag the attribute with
+ * the modification action.
+ */
+ if (operation == LDB_MODIFY) {
+ const char *act = NULL;
+ const int flags = message->elements[i].flags;
+ act = dsdb_audit_get_modification_action(flags);
+ rc = json_add_string(&action, "action", act);
+ if (rc != 0) {
+ json_free(&action);
+ goto failure;
+ }
+ }
+ if (operation == LDB_ADD) {
+ rc = json_add_string(&action, "action", "add");
+ if (rc != 0) {
+ json_free(&action);
+ goto failure;
+ }
+ }
+
+ /*
+ * If the attribute is a secret attribute, tag it as redacted
+ * and don't include the values
+ */
+ if (dsdb_audit_redact_attribute(name)) {
+ rc = json_add_bool(&action, "redacted", true);
+ if (rc != 0) {
+ json_free(&action);
+ goto failure;
+ }
+ } else {
+ struct json_object values;
+ /*
+ * Add the values for the action
+ */
+ values = json_new_array();
+ if (json_is_invalid(&values)) {
+ json_free(&action);
+ goto failure;
+ }
+
+ for (j=0;j<message->elements[i].num_values;j++) {
+ rc = dsdb_audit_add_ldb_value(
+ &values, message->elements[i].values[j]);
+ if (rc != 0) {
+ json_free(&values);
+ json_free(&action);
+ goto failure;
+ }
+ }
+ rc = json_add_object(&action, "values", &values);
+ if (rc != 0) {
+ json_free(&values);
+ json_free(&action);
+ goto failure;
+ }
+ }
+ attribute = json_get_object(&attributes, name);
+ if (json_is_invalid(&attribute)) {
+ json_free(&action);
+ goto failure;
+ }
+ actions = json_get_array(&attribute, "actions");
+ if (json_is_invalid(&actions)) {
+ json_free(&action);
+ goto failure;
+ }
+ rc = json_add_object(&actions, NULL, &action);
+ if (rc != 0) {
+ json_free(&action);
+ goto failure;
+ }
+ rc = json_add_object(&attribute, "actions", &actions);
+ if (rc != 0) {
+ json_free(&actions);
+ goto failure;
+ }
+ rc = json_add_object(&attributes, name, &attribute);
+ if (rc != 0) {
+ json_free(&attribute);
+ goto failure;
+ }
+ }
+ return attributes;
+failure:
+ json_free(&attributes);
+ DBG_ERR("Unable to create ldb attributes JSON audit message\n");
+ return attributes;
+}
diff --git a/source4/dsdb/samdb/ldb_modules/count_attrs.c b/source4/dsdb/samdb/ldb_modules/count_attrs.c
new file mode 100644
index 0000000..c72898a
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/count_attrs.c
@@ -0,0 +1,644 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2019
+
+ 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/>.
+*/
+
+/*
+ * Count how often different attributes are searched for, for performance
+ * analysis. The counts are stored in tdb files in the 'debug' subdirectory of
+ * Samba installation's private directory, and can be read using
+ * script/attr_count_read.
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "param/param.h"
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include "system/filesys.h"
+
+#define NULL_ATTRS "__null_attrs__"
+#define EMPTY_ATTRS "__empty_attrs__"
+#define UNKNOWN_ATTR "__unknown_attribute__"
+#define STAR_ATTR "*"
+
+#define NULL_REQ_PSEUDO_N -2LL;
+#define STAR_REQ_PSEUDO_N -4LL;
+
+#undef strcasecmp
+
+struct count_attrs_private {
+ struct tdb_wrap *requested;
+ struct tdb_wrap *duplicates;
+ struct tdb_wrap *found;
+ struct tdb_wrap *not_found;
+ struct tdb_wrap *unwanted;
+ struct tdb_wrap *star_match;
+ struct tdb_wrap *null_req;
+ struct tdb_wrap *empty_req;
+ struct tdb_wrap *req_vs_found;
+};
+
+
+struct count_attrs_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+ bool has_star;
+ bool is_null;
+ const char **requested_attrs;
+ size_t n_attrs;
+};
+
+
+static int add_key(struct tdb_context *tdb,
+ struct TDB_DATA key)
+{
+ int ret;
+ uint32_t one = 1;
+ struct TDB_DATA value = {
+ .dptr = (uint8_t *)&one,
+ .dsize = sizeof(one)
+ };
+ ret = tdb_store(tdb,
+ key,
+ value,
+ 0);
+ return ret;
+}
+
+static int increment_attr_count(struct tdb_context *tdb,
+ const char *attr)
+{
+ /*
+ * Note that as we don't lock the database, there is a small window
+ * between the fetch and store in which identical updates from
+ * separate processes can race to clobber each other. If this happens
+ * the stored count will be one less than it should be.
+ *
+ * We don't worry about that because it should be quite rare and
+ * agnostic as to which counts are affected, meaning the overall
+ * statistical truth is preserved.
+ */
+ int ret;
+ uint32_t *val;
+ TDB_DATA key = {
+ .dptr = discard_const(attr),
+ .dsize = strlen(attr)
+ };
+
+ TDB_DATA data = tdb_fetch(tdb, key);
+ if (data.dptr == NULL) {
+ ret = tdb_error(tdb);
+ if (ret != TDB_ERR_NOEXIST) {
+ const char *errstr = tdb_errorstr(tdb);
+ DBG_ERR("tdb fetch error: %s\n", errstr);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* this key is unknown. We'll add it and get out of here. */
+ ret = add_key(tdb, key);
+ if (ret != 0) {
+ DBG_ERR("could not add %s: %d\n", attr, ret);
+ }
+ return ret;
+ }
+
+ val = (uint32_t *)data.dptr;
+ (*val)++;
+
+ ret = tdb_store(tdb,
+ key,
+ data,
+ 0);
+
+ if (ret != 0) {
+ const char *errstr = tdb_errorstr(tdb);
+ DBG_ERR("tdb store error: %s\n", errstr);
+ free(data.dptr);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ free(data.dptr);
+ return LDB_SUCCESS;
+}
+
+
+static int increment_req_vs_found(struct tdb_context *tdb,
+ struct count_attrs_context *ac,
+ size_t n_found)
+{
+ /*
+ * Here we record the number of elements in each reply along with the
+ * number of attributes in the corresponding request. Requests for
+ * NULL and "*" are arbitrarily given the attribute counts -2 and -4
+ * respectively. This leads them to be plotted as two stacks on the
+ * left hand side of the scatter plot.
+ */
+ int ret;
+ ssize_t k[2];
+ uint32_t *val = NULL;
+ TDB_DATA key = {
+ .dptr = (unsigned char *)k,
+ .dsize = sizeof(k)
+ };
+ TDB_DATA data = {0};
+ ssize_t n_req = ac->n_attrs;
+ if (ac->is_null) {
+ n_req = NULL_REQ_PSEUDO_N;
+ } else if (ac->has_star) {
+ n_req = STAR_REQ_PSEUDO_N;
+ }
+ k[0] = n_req;
+ k[1] = n_found;
+
+ data = tdb_fetch(tdb, key);
+ if (data.dptr == NULL) {
+ ret = tdb_error(tdb);
+ if (ret != TDB_ERR_NOEXIST) {
+ const char *errstr = tdb_errorstr(tdb);
+ DBG_ERR("req vs found fetch error: %s\n", errstr);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /* unknown key */
+ ret = add_key(tdb, key);
+ if (ret != 0) {
+ DBG_ERR("could not add req vs found %zu:%zu: %d\n",
+ n_req, n_found, ret);
+ }
+ return ret;
+ }
+
+ val = (uint32_t *)data.dptr;
+ (*val)++;
+
+ ret = tdb_store(tdb, key, data, 0);
+ if (ret != 0) {
+ const char *errstr = tdb_errorstr(tdb);
+ DBG_ERR("req vs found store error: %s\n", errstr);
+ free(data.dptr);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ free(data.dptr);
+ return LDB_SUCCESS;
+}
+
+
+static int strcasecmp_ptr(const char **a, const char **b)
+{
+ return strcasecmp(*a, *b);
+}
+
+
+static const char **get_sorted_attrs(TALLOC_CTX *mem_ctx,
+ const char * const *unsorted_attrs,
+ size_t n_attrs)
+{
+ size_t i;
+ const char **attrs = talloc_array(mem_ctx,
+ const char *,
+ n_attrs);
+
+ if (attrs == NULL) {
+ return NULL;
+ }
+ for (i = 0; i < n_attrs; i++) {
+ const char *a = unsorted_attrs[i];
+ if (a == NULL) {
+ DBG_ERR("attrs have disappeared! "
+ "wanted %zu; got %zu\n",
+ n_attrs, i);
+ talloc_free(attrs);
+ return NULL;
+ }
+ attrs[i] = a;
+ }
+
+ qsort(attrs, n_attrs, sizeof(char *), QSORT_CAST strcasecmp_ptr);
+ return attrs;
+}
+
+
+
+static int count_attrs_search_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct count_attrs_private *priv = NULL;
+ struct ldb_message *msg = NULL;
+ size_t i, j;
+ int ret;
+
+ struct count_attrs_context *ac = \
+ talloc_get_type(req->context,
+ struct count_attrs_context);
+
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+
+ priv = talloc_get_type_abort(ldb_module_get_private(ac->module),
+ struct count_attrs_private);
+
+ if (ares == NULL) {
+ DBG_ERR("ares is NULL\n");
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ DBG_INFO("ares error %d\n", ares->error);
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(ac->req, ares->referral);
+
+ case LDB_REPLY_DONE:
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, LDB_SUCCESS);
+
+ case LDB_REPLY_ENTRY:
+ msg = ares->message;
+ if (ac->is_null || ac->n_attrs == 0) {
+ struct tdb_context *tdb = NULL;
+ /*
+ * Note when attributes are found when the requested
+ * list was empty or NULL
+ */
+ if (ac->is_null) {
+ tdb = priv->null_req->tdb;
+ } else {
+ tdb = priv->empty_req->tdb;
+ }
+ for (i = 0; i < msg->num_elements; i++) {
+ const char *name = msg->elements[i].name;
+ ret = increment_attr_count(tdb, name);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ares);
+ DBG_ERR("inc failed\n");
+ return ret;
+ }
+ }
+ } else {
+ /*
+ * We make sorted lists of the requested and found
+ * elements, which makes it easy to find missing or
+ * intruding values.
+ */
+ struct tdb_context *found_tdb = priv->found->tdb;
+ struct tdb_context *unwanted_tdb = \
+ priv->unwanted->tdb;
+ struct tdb_context *star_match_tdb = \
+ priv->star_match->tdb;
+ struct tdb_context *not_found_tdb = \
+ priv->not_found->tdb;
+
+ const char **requested_attrs = ac->requested_attrs;
+ const char **found_attrs = \
+ talloc_array(ac, const char *,
+ msg->num_elements);
+ if (found_attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ for (i = 0; i < msg->num_elements; i++) {
+ found_attrs[i] = msg->elements[i].name;
+ }
+
+ qsort(found_attrs, msg->num_elements, sizeof(char *),
+ QSORT_CAST strcasecmp_ptr);
+
+
+ /* find and report duplicates */
+ for (i = 1; i < msg->num_elements; i++) {
+ if (strcasecmp(found_attrs[i],
+ found_attrs[i - 1]) == 0) {
+ DBG_ERR("duplicate element: %s!\n",
+ found_attrs[i]);
+ /*
+ * If this happens it will muck up our
+ * counts, but probably have worse
+ * effects on the rest of the module
+ * stack. */
+ }
+ }
+
+ /*
+ * This next bit is like the merge stage of a
+ * mergesort, but instead of merging we only detect
+ * absense or presence.
+ */
+ i = 0;
+ j = 0;
+ while (i < ac->n_attrs ||
+ j < msg->num_elements) {
+ int cmp;
+ if (i >= ac->n_attrs) {
+ cmp = 1;
+ } else if (j >= msg->num_elements) {
+ cmp = -1;
+ } else {
+ cmp = strcasecmp(requested_attrs[i],
+ found_attrs[j]
+ );
+ }
+
+ if (cmp < 0) {
+ /* We did not find the element */
+ ret = increment_attr_count(
+ not_found_tdb,
+ requested_attrs[i]);
+ i++;
+ } else if (cmp > 0) {
+ /*
+ * We found the element, but didn't
+ * specifically ask for it.
+ */
+ if (ac->has_star) {
+ ret = increment_attr_count(
+ star_match_tdb,
+ found_attrs[j]);
+ } else {
+ ret = increment_attr_count(
+ unwanted_tdb,
+ found_attrs[j]);
+ }
+ j++;
+ } else {
+ /* We got what we asked for. */
+ ret = increment_attr_count(
+ found_tdb,
+ found_attrs[j]);
+ i++;
+ j++;
+ }
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ares);
+ DBG_ERR("inc failed\n");
+ return ret;
+ }
+ }
+ }
+ ret = increment_req_vs_found(priv->req_vs_found->tdb,
+ ac,
+ msg->num_elements);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ares);
+ DBG_ERR("inc of req vs found failed\n");
+ return ret;
+ }
+
+ return ldb_module_send_entry(
+ ac->req,
+ ares->message,
+ ares->controls);
+ }
+
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+
+static int count_attrs_search(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ int ret;
+ const char * const *attrs = req->op.search.attrs;
+ struct count_attrs_private *count_attrs_private = NULL;
+ struct tdb_context *tdb = NULL;
+ struct ldb_request *down_req = NULL;
+ struct count_attrs_context *ac = NULL;
+ bool has_star = false;
+ bool is_null = false;
+ size_t n_attrs = 0;
+ const char **sorted_attrs = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+
+ void *untyped_private = ldb_module_get_private(module);
+ if (untyped_private == NULL) {
+ /*
+ * There are some cases (in early start up, and during a
+ * backup restore) in which we get a NULL private object, in
+ * which case all we can do is ignore it and pass the request
+ * on unexamined.
+ */
+ return ldb_next_request(module, req);
+ }
+
+ count_attrs_private = talloc_get_type_abort(untyped_private,
+ struct count_attrs_private);
+ tdb = count_attrs_private->requested->tdb;
+
+ ac = talloc_zero(req, struct count_attrs_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ if (attrs == NULL) {
+ ret = increment_attr_count(tdb, NULL_ATTRS);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ is_null = true;
+ } else if (attrs[0] == NULL) {
+ ret = increment_attr_count(tdb, EMPTY_ATTRS);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ } else {
+ size_t i, j;
+ for (i = 0; attrs[i] != NULL; i++) {
+ ret = increment_attr_count(tdb, attrs[i]);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ if (strcmp("*", attrs[i]) == 0) {
+ has_star = true;
+ }
+ }
+ n_attrs = i;
+ sorted_attrs = get_sorted_attrs(req,
+ attrs,
+ n_attrs);
+ /*
+ * Find, report, and remove duplicates. Duplicate attrs in
+ * requests are allowed, but don't work well with our
+ * merge-count algorithm.
+ */
+ j = 0;
+ for (i = 1; i < n_attrs; i++) {
+ if (strcasecmp(sorted_attrs[i],
+ sorted_attrs[j]) == 0) {
+ ret = increment_attr_count(
+ count_attrs_private->duplicates->tdb,
+ sorted_attrs[i]);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ } else {
+ j++;
+ if (j != i) {
+ sorted_attrs[j] = sorted_attrs[i];
+ }
+ }
+ }
+ n_attrs = j;
+ }
+
+ ac->module = module;
+ ac->req = req;
+ ac->has_star = has_star;
+ ac->is_null = is_null;
+ ac->n_attrs = n_attrs;
+ ac->requested_attrs = sorted_attrs;
+
+ ret = ldb_build_search_req_ex(&down_req,
+ ldb,
+ ac,
+ req->op.search.base,
+ req->op.search.scope,
+ req->op.search.tree,
+ req->op.search.attrs,
+ req->controls,
+ ac,
+ count_attrs_search_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ return ldb_next_request(module, down_req);
+}
+
+
+static struct tdb_wrap * open_private_tdb(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx,
+ const char *name)
+{
+ struct tdb_wrap *store = NULL;
+ char *filename = lpcfg_private_path(mem_ctx, lp_ctx, name);
+
+ if (filename == NULL) {
+ return NULL;
+ }
+
+ store = tdb_wrap_open(mem_ctx, filename, 1000,
+ TDB_CLEAR_IF_FIRST,
+ O_RDWR | O_CREAT,
+ 0660);
+ if (store == NULL) {
+ DBG_ERR("failed to open tdb at %s\n", filename);
+ }
+ TALLOC_FREE(filename);
+ return store;
+}
+
+static int make_private_dir(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx,
+ const char *name)
+{
+ int ret;
+ char *dirname = lpcfg_private_path(mem_ctx, lp_ctx, name);
+ if (dirname == NULL) {
+ return -1;
+ }
+ ret = mkdir(dirname, 0755);
+ TALLOC_FREE(dirname);
+ return ret;
+}
+
+
+static int count_attrs_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb = NULL;
+ struct count_attrs_private *data = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ data = talloc_zero(module, struct count_attrs_private);
+ if (data == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+
+ ret = make_private_dir(data, lp_ctx, "debug");
+ if (ret != 0) {
+ goto no_private_dir;
+ }
+ data->requested = open_private_tdb(data, lp_ctx,
+ "debug/attr_counts_requested.tdb");
+ data->duplicates = \
+ open_private_tdb(data, lp_ctx,
+ "debug/attr_counts_duplicates.tdb");
+ data->found = open_private_tdb(data, lp_ctx,
+ "debug/attr_counts_found.tdb");
+ data->not_found = open_private_tdb(data, lp_ctx,
+ "debug/attr_counts_not_found.tdb");
+ data->unwanted = open_private_tdb(data, lp_ctx,
+ "debug/attr_counts_unwanted.tdb");
+ data->star_match = open_private_tdb(data, lp_ctx,
+ "debug/attr_counts_star_match.tdb");
+ data->null_req = open_private_tdb(data, lp_ctx,
+ "debug/attr_counts_null_req.tdb");
+ data->empty_req = open_private_tdb(data, lp_ctx,
+ "debug/attr_counts_empty_req.tdb");
+ data->req_vs_found = \
+ open_private_tdb(data, lp_ctx,
+ "debug/attr_counts_req_vs_found.tdb");
+ if (data->requested == NULL ||
+ data->duplicates == NULL ||
+ data->found == NULL ||
+ data->not_found == NULL ||
+ data->unwanted == NULL ||
+ data->star_match == NULL ||
+ data->null_req == NULL ||
+ data->empty_req == NULL ||
+ data->req_vs_found == NULL) {
+ goto no_private_dir;
+ }
+
+ ldb_module_set_private(module, data);
+ return ldb_next_init(module);
+
+ no_private_dir:
+ /*
+ * If we leave the private data NULL, the search function knows not to
+ * do anything.
+ */
+ DBG_WARNING("the count_attrs module could not open its databases\n");
+ DBG_WARNING("attributes will not be counted.\n");
+ TALLOC_FREE(data);
+ ldb_module_set_private(module, NULL);
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_count_attrs_module_ops = {
+ .name = "count_attrs",
+ .search = count_attrs_search,
+ .init_context = count_attrs_init
+};
+
+int ldb_count_attrs_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_count_attrs_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/descriptor.c b/source4/dsdb/samdb/ldb_modules/descriptor.c
new file mode 100644
index 0000000..bf0f8ec
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/descriptor.c
@@ -0,0 +1,1901 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2006-2008
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2007
+ Copyright (C) Nadezhda Ivanova 2009
+
+ 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: DS Security descriptor module
+ *
+ * Description:
+ * - Calculate the security descriptor of a newly created object
+ * - Perform sd recalculation on a move operation
+ * - Handle sd modification invariants
+ *
+ * Author: Nadezhda Ivanova
+ */
+
+#include "includes.h"
+#include <ldb_module.h>
+#include "util/dlinklist.h"
+#include "dsdb/samdb/samdb.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"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "lib/util/util_tdb.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+
+struct descriptor_changes {
+ struct descriptor_changes *prev, *next;
+ struct ldb_dn *nc_root;
+ struct GUID guid;
+ struct GUID parent_guid;
+ bool force_self;
+ bool force_children;
+ struct ldb_dn *stopped_dn;
+ size_t ref_count;
+ size_t sort_count;
+};
+
+struct descriptor_transaction {
+ TALLOC_CTX *mem;
+ struct {
+ /*
+ * We used to have a list of changes, appended with each
+ * DSDB_EXTENDED_SEC_DESC_PROPAGATION_OID operation.
+ *
+ * But the main problem was that a replication
+ * cycle (mainly the initial replication) calls
+ * DSDB_EXTENDED_SEC_DESC_PROPAGATION_OID for the
+ * same object[GUID] more than once. With
+ * DRSUAPI_DRS_GET_TGT we'll get the naming
+ * context head object and other top level
+ * containers, every often.
+ *
+ * It means we'll process objects more
+ * than once and waste a lot of time
+ * doing the same work again and again.
+ *
+ * We use an objectGUID based map in order to
+ * avoid registering objects more than once.
+ * In an domain with 22000 object it can
+ * reduce the work from 4 hours down to ~ 3.5 minutes.
+ */
+ struct descriptor_changes *list;
+ struct db_context *map;
+ size_t num_registrations;
+ size_t num_registered;
+ size_t num_toplevel;
+ size_t num_processed;
+ } changes;
+ struct {
+ struct db_context *map;
+ size_t num_processed;
+ size_t num_skipped;
+ } objects;
+};
+
+struct descriptor_data {
+ struct descriptor_transaction transaction;
+};
+
+struct descriptor_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+ struct ldb_message *msg;
+ struct ldb_reply *search_res;
+ struct ldb_reply *search_oc_res;
+ struct ldb_val *parentsd_val;
+ struct ldb_message_element *sd_element;
+ struct ldb_val *sd_val;
+ uint32_t sd_flags;
+ int (*step_fn)(struct descriptor_context *);
+};
+
+static struct dom_sid *get_default_ag(TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn,
+ struct security_token *token,
+ struct ldb_context *ldb)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ const struct dom_sid *domain_sid = samdb_domain_sid(ldb);
+ struct dom_sid *da_sid = dom_sid_add_rid(tmp_ctx, domain_sid, DOMAIN_RID_ADMINS);
+ struct dom_sid *ea_sid = dom_sid_add_rid(tmp_ctx, domain_sid, DOMAIN_RID_ENTERPRISE_ADMINS);
+ struct dom_sid *sa_sid = dom_sid_add_rid(tmp_ctx, domain_sid, DOMAIN_RID_SCHEMA_ADMINS);
+ struct dom_sid *dag_sid;
+ struct ldb_dn *nc_root;
+ int ret;
+
+ ret = dsdb_find_nc_root(ldb, tmp_ctx, dn, &nc_root);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return NULL;
+ }
+
+ if (ldb_dn_compare(nc_root, ldb_get_schema_basedn(ldb)) == 0) {
+ if (security_token_has_sid(token, sa_sid)) {
+ dag_sid = dom_sid_dup(mem_ctx, sa_sid);
+ } else if (security_token_has_sid(token, ea_sid)) {
+ dag_sid = dom_sid_dup(mem_ctx, ea_sid);
+ } else if (security_token_has_sid(token, da_sid)) {
+ dag_sid = dom_sid_dup(mem_ctx, da_sid);
+ } else if (security_token_is_system(token)) {
+ dag_sid = dom_sid_dup(mem_ctx, sa_sid);
+ } else {
+ dag_sid = NULL;
+ }
+ } else if (ldb_dn_compare(nc_root, ldb_get_config_basedn(ldb)) == 0) {
+ if (security_token_has_sid(token, ea_sid)) {
+ dag_sid = dom_sid_dup(mem_ctx, ea_sid);
+ } else if (security_token_has_sid(token, da_sid)) {
+ dag_sid = dom_sid_dup(mem_ctx, da_sid);
+ } else if (security_token_is_system(token)) {
+ dag_sid = dom_sid_dup(mem_ctx, ea_sid);
+ } else {
+ dag_sid = NULL;
+ }
+ } else if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) == 0) {
+ if (security_token_has_sid(token, da_sid)) {
+ dag_sid = dom_sid_dup(mem_ctx, da_sid);
+ } else if (security_token_has_sid(token, ea_sid)) {
+ dag_sid = dom_sid_dup(mem_ctx, ea_sid);
+ } else if (security_token_is_system(token)) {
+ dag_sid = dom_sid_dup(mem_ctx, da_sid);
+ } else {
+ dag_sid = NULL;
+ }
+ } else {
+ dag_sid = NULL;
+ }
+
+ talloc_free(tmp_ctx);
+ return dag_sid;
+}
+
+static struct security_descriptor *get_sd_unpacked(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ const struct dsdb_class *objectclass)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct security_descriptor *sd;
+ const struct dom_sid *domain_sid = samdb_domain_sid(ldb);
+
+ if (!objectclass->defaultSecurityDescriptor || !domain_sid) {
+ return NULL;
+ }
+
+ sd = sddl_decode(mem_ctx,
+ objectclass->defaultSecurityDescriptor,
+ domain_sid);
+ return sd;
+}
+
+static struct dom_sid *get_default_group(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ struct dom_sid *dag)
+{
+ /*
+ * This depends on the function level of the DC
+ * which is 2008R2 in our case. Which means it is
+ * higher than 2003 and we should use the
+ * "default administrator group" also as owning group.
+ *
+ * This matches dcpromo for a 2003 domain
+ * on a Windows 2008R2 DC.
+ */
+ return dag;
+}
+
+static struct security_descriptor *descr_handle_sd_flags(TALLOC_CTX *mem_ctx,
+ struct security_descriptor *new_sd,
+ struct security_descriptor *old_sd,
+ uint32_t sd_flags)
+{
+ struct security_descriptor *final_sd;
+ /* if there is no control or control == 0 modify everything */
+ if (!sd_flags) {
+ return new_sd;
+ }
+
+ final_sd = talloc_zero(mem_ctx, struct security_descriptor);
+ final_sd->revision = SECURITY_DESCRIPTOR_REVISION_1;
+ final_sd->type = SEC_DESC_SELF_RELATIVE;
+
+ if (sd_flags & (SECINFO_OWNER)) {
+ if (new_sd->owner_sid) {
+ final_sd->owner_sid = talloc_memdup(mem_ctx, new_sd->owner_sid, sizeof(struct dom_sid));
+ }
+ final_sd->type |= new_sd->type & SEC_DESC_OWNER_DEFAULTED;
+ }
+ else if (old_sd) {
+ if (old_sd->owner_sid) {
+ final_sd->owner_sid = talloc_memdup(mem_ctx, old_sd->owner_sid, sizeof(struct dom_sid));
+ }
+ final_sd->type |= old_sd->type & SEC_DESC_OWNER_DEFAULTED;
+ }
+
+ if (sd_flags & (SECINFO_GROUP)) {
+ if (new_sd->group_sid) {
+ final_sd->group_sid = talloc_memdup(mem_ctx, new_sd->group_sid, sizeof(struct dom_sid));
+ }
+ final_sd->type |= new_sd->type & SEC_DESC_GROUP_DEFAULTED;
+ }
+ else if (old_sd) {
+ if (old_sd->group_sid) {
+ final_sd->group_sid = talloc_memdup(mem_ctx, old_sd->group_sid, sizeof(struct dom_sid));
+ }
+ final_sd->type |= old_sd->type & SEC_DESC_GROUP_DEFAULTED;
+ }
+
+ if (sd_flags & (SECINFO_SACL)) {
+ final_sd->sacl = security_acl_dup(mem_ctx,new_sd->sacl);
+ final_sd->type |= new_sd->type & (SEC_DESC_SACL_PRESENT |
+ SEC_DESC_SACL_DEFAULTED|SEC_DESC_SACL_AUTO_INHERIT_REQ |
+ SEC_DESC_SACL_AUTO_INHERITED|SEC_DESC_SACL_PROTECTED |
+ SEC_DESC_SERVER_SECURITY);
+ }
+ else if (old_sd && old_sd->sacl) {
+ final_sd->sacl = security_acl_dup(mem_ctx,old_sd->sacl);
+ final_sd->type |= old_sd->type & (SEC_DESC_SACL_PRESENT |
+ SEC_DESC_SACL_DEFAULTED|SEC_DESC_SACL_AUTO_INHERIT_REQ |
+ SEC_DESC_SACL_AUTO_INHERITED|SEC_DESC_SACL_PROTECTED |
+ SEC_DESC_SERVER_SECURITY);
+ }
+
+ if (sd_flags & (SECINFO_DACL)) {
+ final_sd->dacl = security_acl_dup(mem_ctx,new_sd->dacl);
+ final_sd->type |= new_sd->type & (SEC_DESC_DACL_PRESENT |
+ SEC_DESC_DACL_DEFAULTED|SEC_DESC_DACL_AUTO_INHERIT_REQ |
+ SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_PROTECTED |
+ SEC_DESC_DACL_TRUSTED);
+ }
+ else if (old_sd && old_sd->dacl) {
+ final_sd->dacl = security_acl_dup(mem_ctx,old_sd->dacl);
+ final_sd->type |= old_sd->type & (SEC_DESC_DACL_PRESENT |
+ SEC_DESC_DACL_DEFAULTED|SEC_DESC_DACL_AUTO_INHERIT_REQ |
+ SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_PROTECTED |
+ SEC_DESC_DACL_TRUSTED);
+ }
+ /* not so sure about this */
+ final_sd->type |= new_sd->type & SEC_DESC_RM_CONTROL_VALID;
+ return final_sd;
+}
+
+static DATA_BLOB *get_new_descriptor(struct ldb_module *module,
+ struct ldb_dn *dn,
+ TALLOC_CTX *mem_ctx,
+ const struct dsdb_class *objectclass,
+ const struct ldb_val *parent,
+ const struct ldb_val *object,
+ const struct ldb_val *old_sd,
+ uint32_t sd_flags)
+{
+ struct security_descriptor *user_descriptor = NULL, *parent_descriptor = NULL;
+ struct security_descriptor *old_descriptor = NULL;
+ struct security_descriptor *new_sd, *final_sd;
+ DATA_BLOB *linear_sd;
+ enum ndr_err_code ndr_err;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct auth_session_info *session_info
+ = ldb_get_opaque(ldb, DSDB_SESSION_INFO);
+ const struct dom_sid *domain_sid = samdb_domain_sid(ldb);
+ struct dom_sid *default_owner;
+ struct dom_sid *default_group;
+ struct security_descriptor *default_descriptor = NULL;
+ struct GUID *object_list = NULL;
+
+ if (objectclass != NULL) {
+ default_descriptor = get_sd_unpacked(module, mem_ctx, objectclass);
+ object_list = talloc_zero_array(mem_ctx, struct GUID, 2);
+ if (object_list == NULL) {
+ return NULL;
+ }
+ object_list[0] = objectclass->schemaIDGUID;
+ }
+
+ if (object) {
+ user_descriptor = talloc(mem_ctx, struct security_descriptor);
+ if (!user_descriptor) {
+ return NULL;
+ }
+ ndr_err = ndr_pull_struct_blob(object, user_descriptor,
+ user_descriptor,
+ (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(user_descriptor);
+ return NULL;
+ }
+ } else {
+ user_descriptor = default_descriptor;
+ }
+
+ if (old_sd) {
+ old_descriptor = talloc(mem_ctx, struct security_descriptor);
+ if (!old_descriptor) {
+ return NULL;
+ }
+ ndr_err = ndr_pull_struct_blob(old_sd, old_descriptor,
+ old_descriptor,
+ (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(old_descriptor);
+ return NULL;
+ }
+ }
+
+ if (parent) {
+ parent_descriptor = talloc(mem_ctx, struct security_descriptor);
+ if (!parent_descriptor) {
+ return NULL;
+ }
+ ndr_err = ndr_pull_struct_blob(parent, parent_descriptor,
+ parent_descriptor,
+ (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(parent_descriptor);
+ return NULL;
+ }
+ }
+
+ if (user_descriptor && default_descriptor &&
+ (user_descriptor->dacl == NULL))
+ {
+ user_descriptor->dacl = default_descriptor->dacl;
+ user_descriptor->type |= default_descriptor->type & (
+ SEC_DESC_DACL_PRESENT |
+ SEC_DESC_DACL_DEFAULTED|SEC_DESC_DACL_AUTO_INHERIT_REQ |
+ SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_PROTECTED |
+ SEC_DESC_DACL_TRUSTED);
+ }
+
+ if (user_descriptor && default_descriptor &&
+ (user_descriptor->sacl == NULL))
+ {
+ user_descriptor->sacl = default_descriptor->sacl;
+ user_descriptor->type |= default_descriptor->type & (
+ SEC_DESC_SACL_PRESENT |
+ SEC_DESC_SACL_DEFAULTED|SEC_DESC_SACL_AUTO_INHERIT_REQ |
+ SEC_DESC_SACL_AUTO_INHERITED|SEC_DESC_SACL_PROTECTED |
+ SEC_DESC_SERVER_SECURITY);
+ }
+
+
+ if (!(sd_flags & SECINFO_OWNER) && user_descriptor) {
+ user_descriptor->owner_sid = NULL;
+
+ /*
+ * We need the correct owner sid
+ * when calculating the DACL or SACL
+ */
+ if (old_descriptor) {
+ user_descriptor->owner_sid = old_descriptor->owner_sid;
+ }
+ }
+ if (!(sd_flags & SECINFO_GROUP) && user_descriptor) {
+ user_descriptor->group_sid = NULL;
+
+ /*
+ * We need the correct group sid
+ * when calculating the DACL or SACL
+ */
+ if (old_descriptor) {
+ user_descriptor->group_sid = old_descriptor->group_sid;
+ }
+ }
+ if (!(sd_flags & SECINFO_DACL) && user_descriptor) {
+ user_descriptor->dacl = NULL;
+
+ /*
+ * We add SEC_DESC_DACL_PROTECTED so that
+ * create_security_descriptor() skips
+ * the unused inheritance calculation
+ */
+ user_descriptor->type |= SEC_DESC_DACL_PROTECTED;
+ }
+ if (!(sd_flags & SECINFO_SACL) && user_descriptor) {
+ user_descriptor->sacl = NULL;
+
+ /*
+ * We add SEC_DESC_SACL_PROTECTED so that
+ * create_security_descriptor() skips
+ * the unused inheritance calculation
+ */
+ user_descriptor->type |= SEC_DESC_SACL_PROTECTED;
+ }
+
+ default_owner = get_default_ag(mem_ctx, dn,
+ session_info->security_token, ldb);
+ default_group = get_default_group(mem_ctx, ldb, default_owner);
+ new_sd = create_security_descriptor(mem_ctx,
+ parent_descriptor,
+ user_descriptor,
+ true,
+ object_list,
+ SEC_DACL_AUTO_INHERIT |
+ SEC_SACL_AUTO_INHERIT,
+ session_info->security_token,
+ default_owner, default_group,
+ map_generic_rights_ds);
+ if (!new_sd) {
+ return NULL;
+ }
+ final_sd = descr_handle_sd_flags(mem_ctx, new_sd, old_descriptor, sd_flags);
+
+ if (!final_sd) {
+ return NULL;
+ }
+
+ if (final_sd->dacl) {
+ final_sd->dacl->revision = SECURITY_ACL_REVISION_ADS;
+ }
+ if (final_sd->sacl) {
+ final_sd->sacl->revision = SECURITY_ACL_REVISION_ADS;
+ }
+
+ {
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ DBG_DEBUG("Object %s created with descriptor %s\n\n",
+ ldb_dn_get_linearized(dn),
+ sddl_encode(tmp_ctx, final_sd, domain_sid));
+ TALLOC_FREE(tmp_ctx);
+ }
+
+ linear_sd = talloc(mem_ctx, DATA_BLOB);
+ if (!linear_sd) {
+ return NULL;
+ }
+
+ ndr_err = ndr_push_struct_blob(linear_sd, mem_ctx,
+ final_sd,
+ (ndr_push_flags_fn_t)ndr_push_security_descriptor);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NULL;
+ }
+
+ return linear_sd;
+}
+
+static DATA_BLOB *descr_get_descriptor_to_show(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_val *sd,
+ uint32_t sd_flags)
+{
+ struct security_descriptor *old_sd, *final_sd;
+ DATA_BLOB *linear_sd;
+ enum ndr_err_code ndr_err;
+
+ old_sd = talloc(mem_ctx, struct security_descriptor);
+ if (!old_sd) {
+ return NULL;
+ }
+ ndr_err = ndr_pull_struct_blob(sd, old_sd,
+ old_sd,
+ (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(old_sd);
+ return NULL;
+ }
+
+ final_sd = descr_handle_sd_flags(mem_ctx, old_sd, NULL, sd_flags);
+
+ if (!final_sd) {
+ return NULL;
+ }
+
+ linear_sd = talloc(mem_ctx, DATA_BLOB);
+ if (!linear_sd) {
+ return NULL;
+ }
+
+ ndr_err = ndr_push_struct_blob(linear_sd, mem_ctx,
+ final_sd,
+ (ndr_push_flags_fn_t)ndr_push_security_descriptor);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NULL;
+ }
+
+ return linear_sd;
+}
+
+static struct descriptor_context *descriptor_init_context(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct descriptor_context *ac;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = talloc_zero(req, struct descriptor_context);
+ if (ac == NULL) {
+ ldb_set_errstring(ldb, "Out of Memory");
+ return NULL;
+ }
+
+ ac->module = module;
+ ac->req = req;
+ return ac;
+}
+
+static int descriptor_search_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct descriptor_context *ac;
+ struct ldb_val *sd_val = NULL;
+ struct ldb_message_element *sd_el;
+ DATA_BLOB *show_sd;
+ int ret = LDB_SUCCESS;
+
+ ac = talloc_get_type(req->context, struct descriptor_context);
+
+ if (!ares) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto fail;
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ sd_el = ldb_msg_find_element(ares->message, "nTSecurityDescriptor");
+ if (sd_el) {
+ sd_val = sd_el->values;
+ }
+
+ if (sd_val) {
+ show_sd = descr_get_descriptor_to_show(ac->module, ac->req,
+ sd_val, ac->sd_flags);
+ if (!show_sd) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto fail;
+ }
+ ldb_msg_remove_attr(ares->message, "nTSecurityDescriptor");
+ ret = ldb_msg_add_steal_value(ares->message, "nTSecurityDescriptor", show_sd);
+ if (ret != LDB_SUCCESS) {
+ goto fail;
+ }
+ }
+ return ldb_module_send_entry(ac->req, ares->message, ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(ac->req, ares->referral);
+
+ case LDB_REPLY_DONE:
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+fail:
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+}
+
+static int descriptor_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_request *add_req;
+ struct ldb_message *msg;
+ struct ldb_result *parent_res;
+ const struct ldb_val *parent_sd = NULL;
+ const struct ldb_val *user_sd;
+ struct ldb_dn *dn = req->op.add.message->dn;
+ struct ldb_dn *parent_dn, *nc_root;
+ struct ldb_message_element *objectclass_element, *sd_element;
+ int ret;
+ const struct dsdb_schema *schema;
+ DATA_BLOB *sd;
+ const struct dsdb_class *objectclass;
+ static const char * const parent_attrs[] = { "nTSecurityDescriptor", NULL };
+ uint32_t instanceType;
+ bool isNC = false;
+ uint32_t sd_flags = dsdb_request_sd_flags(req, NULL);
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ user_sd = ldb_msg_find_ldb_val(req->op.add.message, "nTSecurityDescriptor");
+ sd_element = ldb_msg_find_element(req->op.add.message, "nTSecurityDescriptor");
+ /* nTSecurityDescriptor without a value is an error, letting through so it is handled */
+ if (user_sd == NULL && sd_element) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE,"descriptor_add: %s\n", ldb_dn_get_linearized(dn));
+
+ instanceType = ldb_msg_find_attr_as_uint(req->op.add.message, "instanceType", 0);
+
+ if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
+ isNC = true;
+ }
+
+ if (!isNC) {
+ ret = dsdb_find_nc_root(ldb, req, dn, &nc_root);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,"descriptor_add: Could not find NC root for %s\n",
+ ldb_dn_get_linearized(dn));
+ return ret;
+ }
+
+ if (ldb_dn_compare(dn, nc_root) == 0) {
+ DEBUG(0, ("Found DN %s being a NC by the old method\n", ldb_dn_get_linearized(dn)));
+ isNC = true;
+ }
+ }
+
+ if (isNC) {
+ DEBUG(2, ("DN: %s is a NC\n", ldb_dn_get_linearized(dn)));
+ }
+ if (!isNC) {
+ /* if the object has a parent, retrieve its SD to
+ * use for calculation. Unfortunately we do not yet have
+ * instanceType, so we use dsdb_find_nc_root. */
+
+ parent_dn = ldb_dn_get_parent(req, dn);
+ if (parent_dn == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /* we aren't any NC */
+ ret = dsdb_module_search_dn(module, req, &parent_res, parent_dn,
+ parent_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ req);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,"descriptor_add: Could not find SD for %s\n",
+ ldb_dn_get_linearized(parent_dn));
+ return ret;
+ }
+ if (parent_res->count != 1) {
+ return ldb_operr(ldb);
+ }
+ parent_sd = ldb_msg_find_ldb_val(parent_res->msgs[0], "nTSecurityDescriptor");
+ }
+
+ schema = dsdb_get_schema(ldb, req);
+
+ objectclass_element = ldb_msg_find_element(req->op.add.message, "objectClass");
+ if (objectclass_element == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ objectclass = dsdb_get_last_structural_class(schema,
+ objectclass_element);
+ if (objectclass == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /*
+ * The SD_FLAG control is ignored on add
+ * and we default to all bits set.
+ */
+ sd_flags = SECINFO_OWNER|SECINFO_GROUP|SECINFO_SACL|SECINFO_DACL;
+
+ sd = get_new_descriptor(module, dn, req,
+ objectclass, parent_sd,
+ user_sd, NULL, sd_flags);
+ if (sd == NULL) {
+ return ldb_operr(ldb);
+ }
+ msg = ldb_msg_copy_shallow(req, req->op.add.message);
+ if (msg == NULL) {
+ return ldb_oom(ldb);
+ }
+ if (sd_element != NULL) {
+ sd_element->values[0] = *sd;
+ } else {
+ ret = ldb_msg_add_steal_value(msg,
+ "nTSecurityDescriptor",
+ sd);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ ret = ldb_build_add_req(&add_req, ldb, req,
+ msg,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(add_req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_error(ldb, ret,
+ "descriptor_add: Error creating new add request.");
+ }
+
+ return ldb_next_request(module, add_req);
+}
+
+static int descriptor_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_request *mod_req;
+ struct ldb_message *msg;
+ struct ldb_result *current_res, *parent_res;
+ const struct ldb_val *old_sd = NULL;
+ const struct ldb_val *parent_sd = NULL;
+ const struct ldb_val *user_sd;
+ struct ldb_dn *dn = req->op.mod.message->dn;
+ struct ldb_dn *parent_dn;
+ struct ldb_message_element *objectclass_element, *sd_element;
+ int ret;
+ uint32_t instanceType;
+ bool explicit_sd_flags = false;
+ uint32_t sd_flags = dsdb_request_sd_flags(req, &explicit_sd_flags);
+ const struct dsdb_schema *schema;
+ DATA_BLOB *sd;
+ const struct dsdb_class *objectclass;
+ static const char * const parent_attrs[] = { "nTSecurityDescriptor", NULL };
+ static const char * const current_attrs[] = { "nTSecurityDescriptor",
+ "instanceType",
+ "objectClass", NULL };
+ struct GUID parent_guid = { .time_low = 0 };
+ struct ldb_control *sd_propagation_control;
+ int cmp_ret = -1;
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ sd_propagation_control = ldb_request_get_control(req,
+ DSDB_CONTROL_SEC_DESC_PROPAGATION_OID);
+ if (sd_propagation_control != NULL) {
+ if (sd_propagation_control->data != module) {
+ return ldb_operr(ldb);
+ }
+ if (req->op.mod.message->num_elements != 0) {
+ return ldb_operr(ldb);
+ }
+ if (explicit_sd_flags) {
+ return ldb_operr(ldb);
+ }
+ if (sd_flags != 0xF) {
+ return ldb_operr(ldb);
+ }
+ if (sd_propagation_control->critical == 0) {
+ return ldb_operr(ldb);
+ }
+
+ sd_propagation_control->critical = 0;
+ }
+
+ sd_element = ldb_msg_find_element(req->op.mod.message, "nTSecurityDescriptor");
+ if (sd_propagation_control == NULL && sd_element == NULL) {
+ return ldb_next_request(module, req);
+ }
+
+ /*
+ * nTSecurityDescriptor with DELETE is not supported yet.
+ * TODO: handle this correctly.
+ */
+ if (sd_propagation_control == NULL &&
+ LDB_FLAG_MOD_TYPE(sd_element->flags) == LDB_FLAG_MOD_DELETE)
+ {
+ return ldb_module_error(module,
+ LDB_ERR_UNWILLING_TO_PERFORM,
+ "MOD_DELETE for nTSecurityDescriptor "
+ "not supported yet");
+ }
+
+ user_sd = ldb_msg_find_ldb_val(req->op.mod.message, "nTSecurityDescriptor");
+ /* nTSecurityDescriptor without a value is an error, letting through so it is handled */
+ if (sd_propagation_control == NULL && user_sd == NULL) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE,"descriptor_modify: %s\n", ldb_dn_get_linearized(dn));
+
+ ret = dsdb_module_search_dn(module, req, &current_res, dn,
+ current_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SHOW_EXTENDED_DN,
+ req);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,"descriptor_modify: Could not find %s\n",
+ ldb_dn_get_linearized(dn));
+ return ret;
+ }
+
+ instanceType = ldb_msg_find_attr_as_uint(current_res->msgs[0],
+ "instanceType", 0);
+ /* if the object has a parent, retrieve its SD to
+ * use for calculation */
+ if (!ldb_dn_is_null(current_res->msgs[0]->dn) &&
+ !(instanceType & INSTANCE_TYPE_IS_NC_HEAD)) {
+ NTSTATUS status;
+
+ parent_dn = ldb_dn_get_parent(req, dn);
+ if (parent_dn == NULL) {
+ return ldb_oom(ldb);
+ }
+ ret = dsdb_module_search_dn(module, req, &parent_res, parent_dn,
+ parent_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SHOW_EXTENDED_DN,
+ req);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR, "descriptor_modify: Could not find SD for %s\n",
+ ldb_dn_get_linearized(parent_dn));
+ return ret;
+ }
+ if (parent_res->count != 1) {
+ return ldb_operr(ldb);
+ }
+ parent_sd = ldb_msg_find_ldb_val(parent_res->msgs[0], "nTSecurityDescriptor");
+
+ status = dsdb_get_extended_dn_guid(parent_res->msgs[0]->dn,
+ &parent_guid,
+ "GUID");
+ if (!NT_STATUS_IS_OK(status)) {
+ return ldb_operr(ldb);
+ }
+ }
+
+ schema = dsdb_get_schema(ldb, req);
+
+ objectclass_element = ldb_msg_find_element(current_res->msgs[0], "objectClass");
+ if (objectclass_element == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ objectclass = dsdb_get_last_structural_class(schema,
+ objectclass_element);
+ if (objectclass == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ old_sd = ldb_msg_find_ldb_val(current_res->msgs[0], "nTSecurityDescriptor");
+ if (old_sd == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ if (sd_propagation_control != NULL) {
+ /*
+ * This just triggers a recalculation of the
+ * inherited aces.
+ */
+ user_sd = old_sd;
+ }
+
+ sd = get_new_descriptor(module, current_res->msgs[0]->dn, req,
+ objectclass, parent_sd,
+ user_sd, old_sd, sd_flags);
+ if (sd == NULL) {
+ return ldb_operr(ldb);
+ }
+ msg = ldb_msg_copy_shallow(req, req->op.mod.message);
+ if (msg == NULL) {
+ return ldb_oom(ldb);
+ }
+ cmp_ret = data_blob_cmp(old_sd, sd);
+ if (sd_propagation_control != NULL) {
+ if (cmp_ret == 0) {
+ /*
+ * The nTSecurityDescriptor is unchanged,
+ * which means we can stop the processing.
+ *
+ * We mark the control as critical again,
+ * as we have not processed it, so the caller
+ * can tell that the descriptor was unchanged.
+ */
+ sd_propagation_control->critical = 1;
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
+ }
+
+ ret = ldb_msg_append_value(msg, "nTSecurityDescriptor",
+ sd, LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ldb_oom(ldb);
+ }
+ } else if (cmp_ret != 0) {
+ struct GUID guid;
+ struct ldb_dn *nc_root;
+ NTSTATUS status;
+
+ ret = dsdb_find_nc_root(ldb,
+ msg,
+ current_res->msgs[0]->dn,
+ &nc_root);
+ if (ret != LDB_SUCCESS) {
+ return ldb_oom(ldb);
+ }
+
+ status = dsdb_get_extended_dn_guid(current_res->msgs[0]->dn,
+ &guid,
+ "GUID");
+ if (!NT_STATUS_IS_OK(status)) {
+ return ldb_operr(ldb);
+ }
+
+ /*
+ * Force SD propagation on children of this record
+ */
+ ret = dsdb_module_schedule_sd_propagation(module,
+ nc_root,
+ guid,
+ parent_guid,
+ false);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+ sd_element->values[0] = *sd;
+ } else {
+ sd_element->values[0] = *sd;
+ }
+
+ ret = ldb_build_mod_req(&mod_req, ldb, req,
+ msg,
+ req->controls,
+ req,
+ dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(mod_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, mod_req);
+}
+
+static int descriptor_search(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ struct ldb_context *ldb;
+ struct ldb_request *down_req;
+ struct descriptor_context *ac;
+ bool explicit_sd_flags = false;
+ uint32_t sd_flags = dsdb_request_sd_flags(req, &explicit_sd_flags);
+ bool show_sd = explicit_sd_flags;
+
+ if (!show_sd &&
+ ldb_attr_in_list(req->op.search.attrs, "nTSecurityDescriptor"))
+ {
+ show_sd = true;
+ }
+
+ if (!show_sd) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+ ac = descriptor_init_context(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+ ac->sd_flags = sd_flags;
+
+ ret = ldb_build_search_req_ex(&down_req, ldb, ac,
+ req->op.search.base,
+ req->op.search.scope,
+ req->op.search.tree,
+ req->op.search.attrs,
+ req->controls,
+ ac, descriptor_search_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ac->module, down_req);
+}
+
+static int descriptor_rename_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct descriptor_context *ac = NULL;
+ struct ldb_context *ldb = NULL;
+ struct ldb_dn *newdn = req->op.rename.newdn;
+ struct GUID guid;
+ struct ldb_dn *nc_root;
+ struct GUID parent_guid = { .time_low = 0 };
+ int ret;
+
+ ac = talloc_get_type_abort(req->context, struct descriptor_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = dsdb_module_guid_by_dn(ac->module,
+ newdn,
+ &guid,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ ret);
+ }
+ ret = dsdb_find_nc_root(ldb, req, newdn, &nc_root);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ ret);
+ }
+
+ /*
+ * After a successful rename, force SD propagation on this
+ * record (get a new inherited SD from the potentially new
+ * parent
+ *
+ * We don't know the parent guid here (it is filled in as
+ * all-zero in the initialiser above), but we're not in a hot
+ * code path here, as the "descriptor" module is located above
+ * the "repl_meta_data", only originating changes are handled
+ * here.
+ *
+ * If it turns out to be a problem we may search for the new
+ * parent guid.
+ */
+
+ ret = dsdb_module_schedule_sd_propagation(ac->module,
+ nc_root,
+ guid,
+ parent_guid,
+ true);
+ if (ret != LDB_SUCCESS) {
+ ret = ldb_operr(ldb);
+ return ldb_module_done(ac->req, NULL, NULL,
+ ret);
+ }
+
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+}
+
+
+
+
+static int descriptor_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ struct descriptor_context *ac = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_dn *olddn = req->op.rename.olddn;
+ struct ldb_dn *newdn = req->op.rename.newdn;
+ struct ldb_request *down_req;
+ int ret;
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.rename.olddn)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE,"descriptor_rename: %s\n",
+ ldb_dn_get_linearized(olddn));
+
+ if (ldb_dn_compare(olddn, newdn) == 0) {
+ /* No special work required for a case-only rename */
+ return ldb_next_request(module, req);
+ }
+
+ ac = descriptor_init_context(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ ret = ldb_build_rename_req(&down_req, ldb, ac,
+ req->op.rename.olddn,
+ req->op.rename.newdn,
+ req->controls,
+ ac, descriptor_rename_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, down_req);
+}
+
+static void descriptor_changes_parser(TDB_DATA key, TDB_DATA data, void *private_data)
+{
+ struct descriptor_changes **c_ptr = (struct descriptor_changes **)private_data;
+ uintptr_t ptr = 0;
+
+ SMB_ASSERT(data.dsize == sizeof(ptr));
+
+ memcpy(&ptr, data.dptr, data.dsize);
+
+ *c_ptr = talloc_get_type_abort((void *)ptr, struct descriptor_changes);
+}
+
+static void descriptor_object_parser(TDB_DATA key, TDB_DATA data, void *private_data)
+{
+ SMB_ASSERT(data.dsize == 0);
+}
+
+static int descriptor_extended_sec_desc_propagation(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct descriptor_data *descriptor_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct descriptor_data);
+ struct descriptor_transaction *t = &descriptor_private->transaction;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct dsdb_extended_sec_desc_propagation_op *op;
+ struct descriptor_changes *c = NULL;
+ TDB_DATA key;
+ NTSTATUS status;
+
+ op = talloc_get_type(req->op.extended.data,
+ struct dsdb_extended_sec_desc_propagation_op);
+ if (op == NULL) {
+ ldb_debug(ldb, LDB_DEBUG_FATAL,
+ "descriptor_extended_sec_desc_propagation: "
+ "invalid extended data\n");
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ if (t->mem == NULL) {
+ return ldb_module_operr(module);
+ }
+
+ if (GUID_equal(&op->parent_guid, &op->guid)) {
+ /*
+ * This is an unexpected situation,
+ * it should never happen!
+ */
+ DBG_ERR("ERROR: Object %s is its own parent (nc_root=%s)\n",
+ GUID_string(t->mem, &op->guid),
+ ldb_dn_get_extended_linearized(t->mem, op->nc_root, 1));
+ return ldb_module_operr(module);
+ }
+
+ /*
+ * First we check if we already have an registration
+ * for the given object.
+ */
+
+ key = make_tdb_data((const void*)&op->guid, sizeof(op->guid));
+ status = dbwrap_parse_record(t->changes.map, key,
+ descriptor_changes_parser, &c);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ c = NULL;
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_debug(ldb, LDB_DEBUG_FATAL,
+ "dbwrap_parse_record() - %s\n",
+ nt_errstr(status));
+ return ldb_module_operr(module);
+ }
+
+ if (c == NULL) {
+ /*
+ * Create a new structure if we
+ * don't know about the object yet.
+ */
+
+ c = talloc_zero(t->mem, struct descriptor_changes);
+ if (c == NULL) {
+ return ldb_module_oom(module);
+ }
+ c->nc_root = ldb_dn_copy(c, op->nc_root);
+ if (c->nc_root == NULL) {
+ return ldb_module_oom(module);
+ }
+ c->guid = op->guid;
+ }
+
+ if (ldb_dn_compare(c->nc_root, op->nc_root) != 0) {
+ /*
+ * This is an unexpected situation,
+ * we don't expect the nc root to change
+ * during a replication cycle.
+ */
+ DBG_ERR("ERROR: Object %s nc_root changed %s => %s\n",
+ GUID_string(c, &c->guid),
+ ldb_dn_get_extended_linearized(c, c->nc_root, 1),
+ ldb_dn_get_extended_linearized(c, op->nc_root, 1));
+ return ldb_module_operr(module);
+ }
+
+ c->ref_count += 1;
+
+ /*
+ * always use the last known parent_guid.
+ */
+ c->parent_guid = op->parent_guid;
+
+ /*
+ * Note that we only set, but don't clear values here,
+ * it means c->force_self and c->force_children can
+ * both be true in the end.
+ */
+ if (op->include_self) {
+ c->force_self = true;
+ } else {
+ c->force_children = true;
+ }
+
+ if (c->ref_count == 1) {
+ struct TDB_DATA val = make_tdb_data((const void*)&c, sizeof(c));
+
+ /*
+ * Remember the change by objectGUID in order
+ * to avoid processing it more than once.
+ */
+
+ status = dbwrap_store(t->changes.map, key, val, TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_debug(ldb, LDB_DEBUG_FATAL,
+ "dbwrap_parse_record() - %s\n",
+ nt_errstr(status));
+ return ldb_module_operr(module);
+ }
+
+ DLIST_ADD_END(t->changes.list, c);
+ t->changes.num_registered += 1;
+ }
+ t->changes.num_registrations += 1;
+
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
+}
+
+static int descriptor_extended(struct ldb_module *module, struct ldb_request *req)
+{
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_SEC_DESC_PROPAGATION_OID) == 0) {
+ return descriptor_extended_sec_desc_propagation(module, req);
+ }
+
+ return ldb_next_request(module, req);
+}
+
+static int descriptor_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret;
+ struct descriptor_data *descriptor_private;
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_SD_FLAGS_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "descriptor: Unable to register control with rootdse!\n");
+ return ldb_operr(ldb);
+ }
+
+ descriptor_private = talloc_zero(module, struct descriptor_data);
+ if (descriptor_private == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ldb_module_set_private(module, descriptor_private);
+
+ return ldb_next_init(module);
+}
+
+static int descriptor_sd_propagation_object(struct ldb_module *module,
+ struct ldb_message *msg,
+ bool *stop)
+{
+ struct descriptor_data *descriptor_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct descriptor_data);
+ struct descriptor_transaction *t = &descriptor_private->transaction;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_request *sub_req;
+ struct ldb_result *mod_res;
+ struct ldb_control *sd_propagation_control;
+ struct GUID guid;
+ int ret;
+ TDB_DATA key;
+ TDB_DATA empty_val = { .dsize = 0, };
+ NTSTATUS status;
+ struct descriptor_changes *c = NULL;
+
+ *stop = false;
+
+ /*
+ * We get the GUID of the object
+ * in order to have the cache key
+ * for the object.
+ */
+
+ status = dsdb_get_extended_dn_guid(msg->dn, &guid, "GUID");
+ if (!NT_STATUS_IS_OK(status)) {
+ return ldb_operr(ldb);
+ }
+ key = make_tdb_data((const void*)&guid, sizeof(guid));
+
+ /*
+ * Check if we already processed this object.
+ */
+ status = dbwrap_parse_record(t->objects.map, key,
+ descriptor_object_parser, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * All work is already one
+ */
+ t->objects.num_skipped += 1;
+ *stop = true;
+ return LDB_SUCCESS;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ ldb_debug(ldb, LDB_DEBUG_FATAL,
+ "dbwrap_parse_record() - %s\n",
+ nt_errstr(status));
+ return ldb_module_operr(module);
+ }
+
+ t->objects.num_processed += 1;
+
+ /*
+ * Remember that we're processing this object.
+ */
+ status = dbwrap_store(t->objects.map, key, empty_val, TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_debug(ldb, LDB_DEBUG_FATAL,
+ "dbwrap_parse_record() - %s\n",
+ nt_errstr(status));
+ return ldb_module_operr(module);
+ }
+
+ /*
+ * Check that if there's a descriptor_change in our list,
+ * which we may be able to remove from the pending list
+ * when we processed the object.
+ */
+
+ status = dbwrap_parse_record(t->changes.map, key, descriptor_changes_parser, &c);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ c = NULL;
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_debug(ldb, LDB_DEBUG_FATAL,
+ "dbwrap_parse_record() - %s\n",
+ nt_errstr(status));
+ return ldb_module_operr(module);
+ }
+
+ mod_res = talloc_zero(msg, struct ldb_result);
+ if (mod_res == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ ret = ldb_build_mod_req(&sub_req, ldb, mod_res,
+ msg,
+ NULL,
+ mod_res,
+ ldb_modify_default_callback,
+ NULL);
+ LDB_REQ_SET_LOCATION(sub_req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_operr(module);
+ }
+
+ ldb_req_mark_trusted(sub_req);
+
+ ret = ldb_request_add_control(sub_req,
+ DSDB_CONTROL_SEC_DESC_PROPAGATION_OID,
+ true, module);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_operr(module);
+ }
+
+ sd_propagation_control = ldb_request_get_control(sub_req,
+ DSDB_CONTROL_SEC_DESC_PROPAGATION_OID);
+ if (sd_propagation_control == NULL) {
+ return ldb_module_operr(module);
+ }
+
+ ret = dsdb_request_add_controls(sub_req,
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_operr(module);
+ }
+
+ ret = descriptor_modify(module, sub_req);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(sub_req->handle, LDB_WAIT_ALL);
+ }
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "descriptor_modify on %s failed: %s",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb_module_get_ctx(module)));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (sd_propagation_control->critical != 0) {
+ if (c == NULL) {
+ /*
+ * If we don't have a
+ * descriptor_changes structure
+ * we're done.
+ */
+ *stop = true;
+ } else if (!c->force_children) {
+ /*
+ * If we don't need to
+ * propagate to children,
+ * we're done.
+ */
+ *stop = true;
+ }
+ }
+
+ if (c != NULL && !c->force_children) {
+ /*
+ * Remove the pending change,
+ * we already done all required work,
+ * there's no need to do it again.
+ *
+ * Note DLIST_REMOVE() is a noop
+ * if the element is not part of
+ * the list.
+ */
+ DLIST_REMOVE(t->changes.list, c);
+ }
+
+ talloc_free(mod_res);
+
+ return LDB_SUCCESS;
+}
+
+static int descriptor_sd_propagation_msg_sort(struct ldb_message **m1,
+ struct ldb_message **m2)
+{
+ struct ldb_dn *dn1 = (*m1)->dn;
+ struct ldb_dn *dn2 = (*m2)->dn;
+
+ /*
+ * This sorts in tree order, parents first
+ */
+ return ldb_dn_compare(dn2, dn1);
+}
+
+static int descriptor_sd_propagation_recursive(struct ldb_module *module,
+ struct descriptor_changes *change)
+{
+ struct descriptor_data *descriptor_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct descriptor_data);
+ struct descriptor_transaction *t = &descriptor_private->transaction;
+ struct ldb_result *guid_res = NULL;
+ struct ldb_result *res = NULL;
+ unsigned int i;
+ const char * const no_attrs[] = { "@__NONE__", NULL };
+ struct ldb_dn *stopped_dn = NULL;
+ struct GUID_txt_buf guid_buf;
+ int ret;
+ bool stop = false;
+
+ t->changes.num_processed += 1;
+
+ /*
+ * First confirm this object has children, or exists
+ * (depending on change->force_self)
+ *
+ * LDB_SCOPE_SUBTREE searches are expensive.
+ *
+ * We know this is safe against a rename race as we are in the
+ * prepare_commit(), so must be in a transaction.
+ */
+
+ /* Find the DN by GUID, as this is stable under rename */
+ ret = dsdb_module_search(module,
+ change,
+ &guid_res,
+ change->nc_root,
+ LDB_SCOPE_SUBTREE,
+ no_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_DELETED |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SHOW_EXTENDED_DN,
+ NULL, /* parent_req */
+ "(objectGUID=%s)",
+ GUID_buf_string(&change->guid,
+ &guid_buf));
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (guid_res->count != 1) {
+ /*
+ * We were just given this GUID during the same
+ * transaction, if it is missing this is a big
+ * problem.
+ *
+ * Cleanup of tombstones does not trigger this module
+ * as it just does a delete.
+ */
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "failed to find GUID %s under %s "
+ "for transaction-end SD inheritance: %d results",
+ GUID_buf_string(&change->guid,
+ &guid_buf),
+ ldb_dn_get_linearized(change->nc_root),
+ guid_res->count);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * OK, so there was a parent, are there children? Note: that
+ * this time we do not search for deleted/recycled objects
+ */
+ ret = dsdb_module_search(module,
+ change,
+ &res,
+ guid_res->msgs[0]->dn,
+ LDB_SCOPE_ONELEVEL,
+ no_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM,
+ NULL, /* parent_req */
+ "(objectClass=*)");
+ if (ret != LDB_SUCCESS) {
+ /*
+ * LDB_ERR_NO_SUCH_OBJECT, say if the DN was a deleted
+ * object, is ignored by the caller
+ */
+ return ret;
+ }
+
+ if (res->count == 0 && !change->force_self) {
+ /* All done, no children */
+ TALLOC_FREE(res);
+ return LDB_SUCCESS;
+ }
+
+ /*
+ * First, if we are in force_self mode (eg renamed under new
+ * parent) then apply the SD to the top object
+ */
+ if (change->force_self) {
+ ret = descriptor_sd_propagation_object(module,
+ guid_res->msgs[0],
+ &stop);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(guid_res);
+ return ret;
+ }
+
+ if (stop == true && !change->force_children) {
+ /* There was no change, nothing more to do */
+ TALLOC_FREE(guid_res);
+ return LDB_SUCCESS;
+ }
+
+ if (res->count == 0) {
+ /* All done! */
+ TALLOC_FREE(guid_res);
+ return LDB_SUCCESS;
+ }
+ }
+
+ /*
+ * Look for children
+ *
+ * Note: that we do not search for deleted/recycled objects
+ */
+ ret = dsdb_module_search(module,
+ change,
+ &res,
+ guid_res->msgs[0]->dn,
+ LDB_SCOPE_SUBTREE,
+ no_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_EXTENDED_DN,
+ NULL, /* parent_req */
+ "(objectClass=*)");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ TYPESAFE_QSORT(res->msgs, res->count,
+ descriptor_sd_propagation_msg_sort);
+
+ /* We start from 1, the top object has been done */
+ for (i = 1; i < res->count; i++) {
+ /*
+ * ldb_dn_compare_base() does not match for NULL but
+ * this is clearer
+ */
+ if (stopped_dn != NULL) {
+ ret = ldb_dn_compare_base(stopped_dn,
+ res->msgs[i]->dn);
+ /*
+ * Skip further processing of this
+ * sub-subtree
+ */
+ if (ret == 0) {
+ continue;
+ }
+ }
+ ret = descriptor_sd_propagation_object(module,
+ res->msgs[i],
+ &stop);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (stop) {
+ /*
+ * If this child didn't change, then nothing
+ * under it needs to change
+ *
+ * res has been sorted into tree order so the
+ * next few entries can be skipped
+ */
+ stopped_dn = res->msgs[i]->dn;
+ }
+ }
+
+ TALLOC_FREE(res);
+ return LDB_SUCCESS;
+}
+
+static int descriptor_start_transaction(struct ldb_module *module)
+{
+ struct descriptor_data *descriptor_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct descriptor_data);
+ struct descriptor_transaction *t = &descriptor_private->transaction;
+
+ if (t->mem != NULL) {
+ return ldb_module_operr(module);
+ }
+
+ *t = (struct descriptor_transaction) { .mem = NULL, };
+ t->mem = talloc_new(descriptor_private);
+ if (t->mem == NULL) {
+ return ldb_module_oom(module);
+ }
+ t->changes.map = db_open_rbt(t->mem);
+ if (t->changes.map == NULL) {
+ TALLOC_FREE(t->mem);
+ *t = (struct descriptor_transaction) { .mem = NULL, };
+ return ldb_module_oom(module);
+ }
+ t->objects.map = db_open_rbt(t->mem);
+ if (t->objects.map == NULL) {
+ TALLOC_FREE(t->mem);
+ *t = (struct descriptor_transaction) { .mem = NULL, };
+ return ldb_module_oom(module);
+ }
+
+ return ldb_next_start_trans(module);
+}
+
+static int descriptor_prepare_commit(struct ldb_module *module)
+{
+ struct descriptor_data *descriptor_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct descriptor_data);
+ struct descriptor_transaction *t = &descriptor_private->transaction;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct descriptor_changes *c, *n;
+ int ret;
+
+ DBG_NOTICE("changes: num_registrations=%zu\n",
+ t->changes.num_registrations);
+ DBG_NOTICE("changes: num_registered=%zu\n",
+ t->changes.num_registered);
+
+ /*
+ * The security descriptor propagation
+ * needs to apply the inheritance from
+ * an object to itself and/or all it's
+ * children.
+ *
+ * In the initial replication during
+ * a join, we have every object in our
+ * list.
+ *
+ * In order to avoid useless work it's
+ * better to start with toplevel objects and
+ * move down to the leaf object from there.
+ *
+ * So if the parent_guid is also in our list,
+ * we better move the object behind its parent.
+ *
+ * It allows that the recursive processing of
+ * the parent already does the work needed
+ * for the child.
+ *
+ * If we have a list for this directory tree:
+ *
+ * A
+ * -> B
+ * -> C
+ * -> D
+ * -> E
+ *
+ * The initial list would have the order D, E, B, A, C
+ *
+ * By still processing from the front, we ensure that,
+ * when D is found to be below C, that E follows because
+ * we keep peeling items off the front for checking and
+ * move them behind their parent.
+ *
+ * So we would go:
+ *
+ * E B A C D
+ *
+ * B A C D E
+ *
+ * A B C D E
+ */
+ for (c = t->changes.list; c; c = n) {
+ struct descriptor_changes *pc = NULL;
+ n = c->next;
+
+ if (c->sort_count >= t->changes.num_registered) {
+ /*
+ * This should never happen, but it's
+ * a sanity check in order to avoid
+ * endless loops. Just stop sorting.
+ */
+ break;
+ }
+
+ /*
+ * Check if we have the parent also in the list.
+ */
+ if (!GUID_all_zero((const void*)&c->parent_guid)) {
+ TDB_DATA pkey;
+ NTSTATUS status;
+
+ pkey = make_tdb_data((const void*)&c->parent_guid,
+ sizeof(c->parent_guid));
+
+ status = dbwrap_parse_record(t->changes.map, pkey,
+ descriptor_changes_parser, &pc);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ pc = NULL;
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_debug(ldb, LDB_DEBUG_FATAL,
+ "dbwrap_parse_record() - %s\n",
+ nt_errstr(status));
+ return ldb_module_operr(module);
+ }
+ }
+
+ if (pc == NULL) {
+ /*
+ * There is no parent in the list
+ */
+ t->changes.num_toplevel += 1;
+ continue;
+ }
+
+ /*
+ * Move the child after the parent
+ *
+ * Note that we do that multiple times
+ * in case the parent already moved itself.
+ *
+ * See the comment above the loop.
+ */
+ DLIST_REMOVE(t->changes.list, c);
+ DLIST_ADD_AFTER(t->changes.list, c, pc);
+
+ /*
+ * Remember how often we moved the object
+ * in order to avoid endless loops.
+ */
+ c->sort_count += 1;
+ }
+
+ DBG_NOTICE("changes: num_toplevel=%zu\n", t->changes.num_toplevel);
+
+ while (t->changes.list != NULL) {
+ c = t->changes.list;
+
+ DLIST_REMOVE(t->changes.list, c);
+
+ /*
+ * Note that descriptor_sd_propagation_recursive()
+ * may also remove other elements of the list,
+ * so we can't use a next pointer
+ */
+ ret = descriptor_sd_propagation_recursive(module, c);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ continue;
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ DBG_NOTICE("changes: num_processed=%zu\n", t->changes.num_processed);
+ DBG_NOTICE("objects: num_processed=%zu\n", t->objects.num_processed);
+ DBG_NOTICE("objects: num_skipped=%zu\n", t->objects.num_skipped);
+
+ return ldb_next_prepare_commit(module);
+}
+
+static int descriptor_end_transaction(struct ldb_module *module)
+{
+ struct descriptor_data *descriptor_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct descriptor_data);
+ struct descriptor_transaction *t = &descriptor_private->transaction;
+
+ TALLOC_FREE(t->mem);
+ *t = (struct descriptor_transaction) { .mem = NULL, };
+
+ return ldb_next_end_trans(module);
+}
+
+static int descriptor_del_transaction(struct ldb_module *module)
+{
+ struct descriptor_data *descriptor_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct descriptor_data);
+ struct descriptor_transaction *t = &descriptor_private->transaction;
+
+ TALLOC_FREE(t->mem);
+ *t = (struct descriptor_transaction) { .mem = NULL, };
+
+ return ldb_next_del_trans(module);
+}
+
+static const struct ldb_module_ops ldb_descriptor_module_ops = {
+ .name = "descriptor",
+ .search = descriptor_search,
+ .add = descriptor_add,
+ .modify = descriptor_modify,
+ .rename = descriptor_rename,
+ .init_context = descriptor_init,
+ .extended = descriptor_extended,
+ .start_transaction = descriptor_start_transaction,
+ .prepare_commit = descriptor_prepare_commit,
+ .end_transaction = descriptor_end_transaction,
+ .del_transaction = descriptor_del_transaction,
+};
+
+int ldb_descriptor_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_descriptor_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/dirsync.c b/source4/dsdb/samdb/ldb_modules/dirsync.c
new file mode 100644
index 0000000..fbb7579
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/dirsync.c
@@ -0,0 +1,1428 @@
+/*
+ SAMDB control module
+
+ Copyright (C) Matthieu Patou <mat@matws.net> 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_module.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/drsblobs.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "librpc/ndr/libndr.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "lib/util/smb_strtox.h"
+
+#define LDAP_DIRSYNC_OBJECT_SECURITY 0x01
+#define LDAP_DIRSYNC_ANCESTORS_FIRST_ORDER 0x800
+#define LDAP_DIRSYNC_PUBLIC_DATA_ONLY 0x2000
+#define LDAP_DIRSYNC_INCREMENTAL_VALUES 0x80000000
+
+
+struct dirsync_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+
+ /*
+ * We keep a track of the number of attributes that we
+ * add just for the need of the implementation
+ * it will be usefull to track then entries that needs not to
+ * be returned because there is no real change
+ */
+
+ unsigned int nbDefaultAttrs;
+ uint64_t highestUSN;
+ uint64_t fromreqUSN;
+ uint32_t cursor_size;
+ bool noextended;
+ int extended_type;
+ bool linkIncrVal;
+ bool localonly;
+ bool partial;
+ int functional_level;
+ const struct GUID *our_invocation_id;
+ const struct dsdb_schema *schema;
+ struct ldb_dn *nc_root;
+ struct drsuapi_DsReplicaCursor *cursors;
+};
+
+
+static int dirsync_filter_entry(struct ldb_request *req,
+ struct ldb_message *msg,
+ struct ldb_control **controls,
+ struct dirsync_context *dsc,
+ bool referral)
+{
+ struct ldb_context *ldb;
+ uint64_t val = 0;
+ enum ndr_err_code ndr_err;
+ uint32_t n;
+ int i;
+ unsigned int size, j;
+ struct ldb_val *replMetaData = NULL;
+ struct replPropertyMetaDataBlob rmd;
+ const struct dsdb_attribute *attr;
+ const char **listAttr = NULL;
+ bool namereturned = false;
+ bool nameasked = false;
+ NTSTATUS status;
+ /* Ajustment for the added attributes, it will reduce the number of
+ * expected to be here attributes*/
+ unsigned int delta = 0;
+ const char **myaccept = NULL;
+ const char *emptyaccept[] = { NULL };
+ const char *extendedaccept[] = { "GUID", "SID", "WKGUID", NULL };
+ const char *rdn = NULL;
+ struct ldb_message_element *el;
+ struct ldb_message *newmsg;
+ bool keep = false;
+ /*
+ * Where we asked to do extended dn ?
+ * if so filter out everything bug GUID, SID, WKGUID,
+ * if not filter out everything (just keep the dn).
+ */
+ if ( dsc->noextended == true ) {
+ myaccept = emptyaccept;
+ } else {
+ myaccept = extendedaccept;
+ }
+ ldb = ldb_module_get_ctx(dsc->module);
+
+ if (msg->num_elements == 0) {
+ /*
+ * Entry that we don't really have access to
+ */
+ return LDB_SUCCESS;
+ }
+ ldb_dn_extended_filter(msg->dn, myaccept);
+
+ /*
+ * If the RDN starts with CN then the CN attribute is never returned
+ */
+ rdn = ldb_dn_get_rdn_name(msg->dn);
+
+ /*
+ * if objectGUID is asked and we are dealing for the referrals entries and
+ * the usn searched is 0 then we didn't count the objectGUID as an automatically
+ * returned attribute, do to so we increament delta.
+ */
+ if (referral == true &&
+ ldb_attr_in_list(req->op.search.attrs, "objectGUID") &&
+ dsc->fromreqUSN == 0) {
+ delta++;
+ }
+
+
+ /*
+ * In terms of big O notation this is not the best algorithm,
+ * but we try our best not to make the worse one.
+ * We are obliged to run through the n message's elements
+ * and through the p elements of the replPropertyMetaData.
+ *
+ * It turns out that we are crawling twice the message's elements
+ * the first crawl is to remove the non replicated and generated
+ * attributes. The second one is to remove attributes that haven't
+ * a USN > as the requested one.
+ *
+ * In the second crawl we are reading the list of elements in the
+ * replPropertyMetaData for each remaining replicated attribute.
+ * In order to keep the list small
+ *
+ * We have a O(n'*p') complexity, in worse case n' = n and p' = p
+ * but in most case n' = n/2 (at least half of returned attributes
+ * are not replicated or generated) and p' is small as we
+ * list only the attribute that have been modified since last interogation
+ *
+ */
+ newmsg = ldb_msg_new(dsc->req);
+ if (newmsg == NULL) {
+ return ldb_oom(ldb);
+ }
+ for (i = msg->num_elements - 1; i >= 0; i--) {
+ if (ldb_attr_cmp(msg->elements[i].name, "uSNChanged") == 0) {
+ int error = 0;
+ /* Read the USN it will used at the end of the filtering
+ * to update the max USN in the cookie if we
+ * decide to keep this entry
+ */
+ val = smb_strtoull(
+ (const char*)msg->elements[i].values[0].data,
+ NULL,
+ 0,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ ldb_set_errstring(ldb,
+ "Failed to convert USN");
+ return ldb_module_done(dsc->req,
+ NULL,
+ NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ continue;
+ }
+
+ if (ldb_attr_cmp(msg->elements[i].name,
+ "replPropertyMetaData") == 0) {
+ replMetaData = (talloc_steal(dsc, &msg->elements[i].values[0]));
+ continue;
+ }
+ }
+
+ if (replMetaData == NULL) {
+ bool guidfound = false;
+
+ /*
+ * We are in the case of deleted object where we don't have the
+ * right to read it.
+ */
+ if (!ldb_msg_find_attr_as_uint(msg, "isDeleted", 0)) {
+ /*
+ * This is not a deleted item and we don't
+ * have the replPropertyMetaData.
+ * Do not return it
+ */
+ return LDB_SUCCESS;
+ }
+ newmsg->dn = ldb_dn_new(newmsg, ldb, "");
+ if (newmsg->dn == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ el = ldb_msg_find_element(msg, "objectGUID");
+ if ( el != NULL) {
+ guidfound = true;
+ }
+ /*
+ * We expect to find the GUID in the object,
+ * if it turns out not to be the case sometime
+ * well will uncomment the code bellow
+ */
+ SMB_ASSERT(guidfound == true);
+ /*
+ if (guidfound == false) {
+ struct GUID guid;
+ struct ldb_val *new_val;
+ DATA_BLOB guid_blob;
+
+ tmp[0] = '\0';
+ txt = strrchr(txt, ':');
+ if (txt == NULL) {
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+ txt++;
+
+ status = GUID_from_string(txt, &guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ status = GUID_to_ndr_blob(&guid, msg, &guid_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ new_val = talloc(msg, struct ldb_val);
+ if (new_val == NULL) {
+ return ldb_oom(ldb);
+ }
+ new_val->data = talloc_steal(new_val, guid_blob.data);
+ new_val->length = guid_blob.length;
+ if (ldb_msg_add_value(msg, "objectGUID", new_val, NULL) != 0) {
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+ */
+ ldb_msg_add(newmsg, el, LDB_FLAG_MOD_ADD);
+ talloc_steal(newmsg->elements, el->name);
+ talloc_steal(newmsg->elements, el->values);
+
+ talloc_steal(newmsg->elements, msg);
+ return ldb_module_send_entry(dsc->req, msg, controls);
+ }
+
+ ndr_err = ndr_pull_struct_blob(replMetaData, dsc, &rmd,
+ (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ ldb_set_errstring(ldb, "Unable to unmarshall replPropertyMetaData");
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ldb_attr_in_list(req->op.search.attrs, "name") ||
+ ldb_attr_in_list(req->op.search.attrs, "*")) {
+ nameasked = true;
+ }
+
+ /*
+ * If we don't have an USN and no updateness array then we skip the
+ * test phase this is an optimisation for the case when you
+ * first query the DC without a cookie.
+ * As this query is most probably the one
+ * that will return the biggest answer, skipping this part
+ * will really save time.
+ */
+ if (ldb_dn_compare(dsc->nc_root, msg->dn) == 0) {
+ /* If we have name then we expect to have parentGUID,
+ * it will not be the case for the root of the NC
+ */
+ delta++;
+ }
+
+ if (dsc->fromreqUSN > 0 || dsc->cursors != NULL) {
+ j = 0;
+ /*
+ * Allocate an array of size(replMetaData) of char*
+ * we know that it will be oversized but it's a short lived element
+ */
+ listAttr = talloc_array(msg, const char*, rmd.ctr.ctr1.count + 1);
+ if (listAttr == NULL) {
+ return ldb_oom(ldb);
+ }
+ for (n=0; n < rmd.ctr.ctr1.count; n++) {
+ struct replPropertyMetaData1 *omd = &rmd.ctr.ctr1.array[n];
+ if (omd->local_usn > dsc->fromreqUSN) {
+ const struct dsdb_attribute *a = dsdb_attribute_by_attributeID_id(dsc->schema,
+ omd->attid);
+ if (!dsc->localonly) {
+ struct drsuapi_DsReplicaCursor *tab = dsc->cursors;
+ uint32_t l;
+ for (l=0; l < dsc->cursor_size; l++) {
+ if (GUID_equal(&tab[l].source_dsa_invocation_id, &omd->originating_invocation_id) &&
+ tab[l].highest_usn >= omd->originating_usn) {
+ /*
+ * If we have in the uptodateness vector an entry
+ * with the same invocation id as the originating invocation
+ * and if the usn in the vector is greater or equal to
+ * the one in originating_usn, then it means that this entry
+ * has already been sent (from another DC) to the client
+ * no need to resend it one more time.
+ */
+ goto skip;
+ }
+ }
+ /* If we are here it's because we have a usn > (max(usn of vectors))*/
+ }
+ if (namereturned == false &&
+ nameasked == true &&
+ ldb_attr_cmp(a->lDAPDisplayName, "name") == 0) {
+ namereturned = true;
+ if (ldb_dn_compare(dsc->nc_root, msg->dn) == 0) {
+ delta++;
+ }
+ }
+ listAttr[j] = a->lDAPDisplayName;
+ j++;
+skip:
+ continue;
+ }
+ }
+ size = j;
+ } else {
+ size = 0;
+ if (ldb_attr_in_list(req->op.search.attrs, "*") ||
+ ldb_attr_in_list(req->op.search.attrs, "name")) {
+ namereturned = true;
+ }
+ }
+
+
+ /*
+ * Let's loop around the remaining elements
+ * to see which one are in the listAttr.
+ * If they are in this array it means that
+ * their localusn > usn from the request (in the cookie)
+ * if not we remove the attribute.
+ */
+ for (i = msg->num_elements - 1; i >= 0; i--) {
+ const char *ldapattrname;
+
+ el = &(msg->elements[i]);
+ ldapattrname = el->name;
+
+ attr = dsdb_attribute_by_lDAPDisplayName(dsc->schema,
+ el->name);
+ if (attr == NULL) {
+ continue;
+ }
+
+ keep = false;
+
+ if (attr->linkID & 1) {
+ /*
+ * Attribute is a backlink so let's remove it
+ */
+ continue;
+ }
+
+ if (ldb_attr_cmp(msg->elements[i].name,
+ "replPropertyMetaData") == 0) {
+ continue;
+ }
+
+ if ((attr->systemFlags & (DS_FLAG_ATTR_NOT_REPLICATED | DS_FLAG_ATTR_IS_CONSTRUCTED))) {
+ if (ldb_attr_cmp(attr->lDAPDisplayName, "objectGUID") != 0 &&
+ ldb_attr_cmp(attr->lDAPDisplayName, "parentGUID") != 0) {
+ /*
+ * Attribute is constructed or not replicated, let's get rid of it
+ */
+ continue;
+ } else {
+ /* Let's keep the attribute that we forced to be added
+ * even if they are not in the replicationMetaData
+ * or are just generated
+ */
+ if (namereturned == false &&
+ (ldb_attr_cmp(attr->lDAPDisplayName, "parentGUID") == 0)) {
+ delta++;
+ continue;
+ }
+ if (ldb_msg_add(newmsg, el, LDB_FLAG_MOD_ADD) != LDB_SUCCESS) {
+ return ldb_error(ldb,
+ LDB_ERR_OPERATIONS_ERROR,
+ "Unable to add attribute");
+ }
+ talloc_steal(newmsg->elements, el->name);
+ talloc_steal(newmsg->elements, el->values);
+ continue;
+ }
+ }
+
+ if (ldb_attr_cmp(msg->elements[i].name, rdn) == 0) {
+ /*
+ * We have an attribute that is the same as the start of the RDN
+ * (ie. attribute CN with rdn CN=).
+ */
+ continue;
+ }
+
+ if (ldb_attr_cmp(attr->lDAPDisplayName, "instanceType") == 0) {
+ if (ldb_msg_add(newmsg, el, LDB_FLAG_MOD_ADD) != LDB_SUCCESS) {
+ return ldb_error(ldb,
+ LDB_ERR_OPERATIONS_ERROR,
+ "Unable to add attribute");
+ }
+ talloc_steal(newmsg->elements, el->name);
+ talloc_steal(newmsg->elements, el->values);
+ continue;
+ }
+ /* For links, when our functional level > windows 2000
+ * we use the RMD_LOCAL_USN information to decide whether
+ * we return the attribute or not.
+ * For windows 2000 this information is in the replPropertyMetaData
+ * so it will be handled like any other replicated attribute
+ */
+
+ if (dsc->functional_level > DS_DOMAIN_FUNCTION_2000 &&
+ attr->linkID != 0 ) {
+ int k;
+ /*
+ * Elements for incremental changes on linked attributes
+ */
+ struct ldb_message_element *el_incr_add = NULL;
+ struct ldb_message_element *el_incr_del = NULL;
+ /*
+ * Attribute is a forwardlink so let's remove it
+ */
+
+ for (k = el->num_values -1; k >= 0; k--) {
+ char *dn_ln;
+ uint32_t flags = 0;
+ uint32_t tmp_usn = 0;
+ uint32_t tmp_usn2 = 0;
+ struct GUID invocation_id = GUID_zero();
+ struct dsdb_dn *dn = dsdb_dn_parse(msg, ldb, &el->values[k], attr->syntax->ldap_oid);
+ struct ldb_dn *copydn;
+ if (dn == NULL) {
+ ldb_set_errstring(ldb, "Cannot parse DN");
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ copydn = ldb_dn_copy(msg, dn->dn);
+ if (copydn == NULL) {
+ ldb_oom(ldb);
+ }
+
+ status = dsdb_get_extended_dn_uint32(dn->dn, &tmp_usn, "RMD_LOCAL_USN");
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(dn);
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+ status = dsdb_get_extended_dn_guid(dn->dn, &invocation_id, "RMD_INVOCID");
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(dn);
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ status = dsdb_get_extended_dn_uint32(dn->dn, &flags, "RMD_FLAGS");
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(dn);
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ status = dsdb_get_extended_dn_uint32(dn->dn, &tmp_usn2, "RMD_ORIGINATING_USN");
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(dn);
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ldb_dn_extended_filter(dn->dn, myaccept);
+ dn_ln = dsdb_dn_get_extended_linearized(dn, dn,
+ dsc->extended_type);
+ if (dn_ln == NULL)
+ {
+ talloc_free(dn);
+ ldb_set_errstring(ldb, "Cannot linearize dn");
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ talloc_free(el->values[k].data);
+ el->values[k].data = (uint8_t*)talloc_steal(el->values, dn_ln);
+ if (el->values[k].data == NULL) {
+ talloc_free(dn);
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+ el->values[k].length = strlen(dn_ln);
+
+
+ if (tmp_usn > dsc->fromreqUSN) {
+ if (!dsc->localonly) {
+ struct drsuapi_DsReplicaCursor *tab = dsc->cursors;
+ uint32_t l;
+
+ for (l=0; l < dsc->cursor_size; l++) {
+ if (GUID_equal(&tab[l].source_dsa_invocation_id, &invocation_id) &&
+ tab[l].highest_usn >= tmp_usn2) {
+ /*
+ * If we have in the uptodateness vector an entry
+ * with the same invocation id as the originating invocation
+ * and if the usn in the vector is greater or equal to
+ * the one in originating_usn, then it means that this entry
+ * has already been sent (from another DC) to the client
+ * no need to resend it one more time.
+ */
+ goto skip_link;
+ }
+ }
+ /* If we are here it's because we have a usn > (max(usn of vectors))*/
+ keep = true;
+ } else {
+ keep = true;
+ }
+ /* If we are here it's because the link is more recent than either any
+ * originating usn or local usn
+ */
+
+ if (dsc->linkIncrVal == true) {
+ struct ldb_message_element *tmpel;
+ if (flags & DSDB_RMD_FLAG_DELETED) {
+ /* We have to check that the inactive link still point to an existing object */
+ struct GUID guid;
+ struct ldb_dn *tdn;
+ int ret;
+
+ status = dsdb_get_extended_dn_guid(copydn, &guid, "GUID");
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,(__location__ " Unable to extract GUID in linked attribute '%s' in '%s'\n",
+ el->name, ldb_dn_get_linearized(copydn)));
+ return ldb_operr(ldb);
+ }
+ ret = dsdb_module_dn_by_guid(dsc->module, newmsg, &guid, &tdn, req);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ DEBUG(2, (" Search of guid %s returned 0 objects, skipping it !\n",
+ GUID_string(newmsg, &guid)));
+ continue;
+ } else if (ret != LDB_SUCCESS) {
+ DEBUG(0, (__location__ " Search of guid %s failed with error code %d\n",
+ GUID_string(newmsg, &guid),
+ ret));
+ continue;
+ }
+ tmpel = el_incr_del;
+ } else {
+ tmpel = el_incr_add;
+ }
+
+ if (tmpel == NULL) {
+ tmpel = talloc_zero(newmsg, struct ldb_message_element);
+ if (tmpel == NULL) {
+ return ldb_oom(ldb);
+ }
+ tmpel->values = talloc_array(tmpel, struct ldb_val, 1);
+ if (tmpel->values == NULL) {
+ return ldb_oom(ldb);
+ }
+ if (flags & DSDB_RMD_FLAG_DELETED) {
+ tmpel->name = talloc_asprintf(tmpel,
+ "%s;range=0-0",
+ el->name);
+ }
+ else {
+ tmpel->name = talloc_asprintf(tmpel,
+ "%s;range=1-1",
+ el->name);
+ }
+ if (tmpel->name == NULL) {
+ return ldb_oom(ldb);
+ }
+ tmpel->num_values = 1;
+ } else {
+ tmpel->num_values += 1;
+ tmpel->values = talloc_realloc(tmpel,
+ tmpel->values,
+ struct ldb_val,
+ tmpel->num_values);
+ if (tmpel->values == NULL) {
+ return ldb_oom(ldb);
+ }
+ }
+ tmpel->values[tmpel->num_values -1].data =talloc_steal(tmpel->values, el->values[k].data);
+ tmpel->values[tmpel->num_values -1].length = el->values[k].length;
+
+ if (flags & DSDB_RMD_FLAG_DELETED) {
+ el_incr_del = tmpel;
+ } else {
+ el_incr_add = tmpel;
+ }
+ }
+ }
+
+ if (dsc->linkIncrVal == false) {
+ if (flags & DSDB_RMD_FLAG_DELETED) {
+ ARRAY_DEL_ELEMENT(
+ el->values,
+ k,
+ el->num_values);
+ el->num_values--;
+ }
+ }
+skip_link:
+ talloc_free(dn);
+
+ }
+ if (keep == true) {
+ if (dsc->linkIncrVal == false) {
+ if (ldb_msg_add(newmsg, el, LDB_FLAG_MOD_ADD) != LDB_SUCCESS) {
+ return ldb_error(ldb,
+ LDB_ERR_OPERATIONS_ERROR,
+ "Unable to add attribute");
+ }
+ talloc_steal(newmsg->elements, el->name);
+ talloc_steal(newmsg->elements, el->values);
+ } else {
+ if (el_incr_del) {
+ if (ldb_msg_add(newmsg, el_incr_del, LDB_FLAG_MOD_ADD))
+ return ldb_error(ldb,
+ LDB_ERR_OPERATIONS_ERROR,
+ "Unable to add attribute");
+ }
+ if (el_incr_add) {
+ if (ldb_msg_add(newmsg, el_incr_add, LDB_FLAG_MOD_ADD))
+ return ldb_error(ldb,
+ LDB_ERR_OPERATIONS_ERROR,
+ "Unable to add attribute");
+ }
+ }
+ }
+ continue;
+ }
+
+ if (listAttr) {
+ for (j=0; j<size; j++) {
+ /*
+ * We mark attribute that has already been seen well
+ * as seen. So that after attribute that are still in
+ * listAttr are attributes that has been modified after
+ * the requested USN but not present in the attributes
+ * returned by the ldb search.
+ * That is to say attributes that have been removed
+ */
+ if (listAttr[j] && ldb_attr_cmp(listAttr[j], ldapattrname) == 0) {
+ listAttr[j] = NULL;
+ keep = true;
+ continue;
+ }
+ }
+ } else {
+ keep = true;
+ }
+
+ if (keep == true) {
+ if (ldb_msg_add(newmsg, el, LDB_FLAG_MOD_ADD) != LDB_SUCCESS) {
+ return ldb_error(ldb,
+ LDB_ERR_OPERATIONS_ERROR,
+ "Unable to add attribute");
+ }
+ talloc_steal(newmsg->elements, el->name);
+ talloc_steal(newmsg->elements, el->values);
+ continue;
+ }
+ }
+ talloc_steal(newmsg->elements, msg);
+
+ /*
+ * Here we run through the list of attributes returned
+ * in the propertyMetaData.
+ * Entries of this list have usn > requested_usn,
+ * entries that are also present in the message have been
+ * replaced by NULL, so at this moment the list contains
+ * only elements that have a usn > requested_usn and that
+ * haven't been seen. It's attributes that were removed.
+ * We add them to the message like empty elements.
+ */
+ for (j=0; j<size; j++) {
+ if (listAttr[j] && (
+ ldb_attr_in_list(req->op.search.attrs, "*") ||
+ ldb_attr_in_list(req->op.search.attrs, listAttr[j])) &&
+ (ldb_attr_cmp(listAttr[j], rdn) != 0) &&
+ (ldb_attr_cmp(listAttr[j], "instanceType") != 0)) {
+ ldb_msg_add_empty(newmsg, listAttr[j], LDB_FLAG_MOD_DELETE, NULL);
+ }
+ }
+ talloc_free(listAttr);
+
+ if ((newmsg->num_elements - ( dsc->nbDefaultAttrs - delta)) > 0) {
+ /*
+ * After cleaning attributes there is still some attributes that were not added just
+ * for the purpose of the control (objectGUID, instanceType, ...)
+ */
+
+ newmsg->dn = talloc_steal(newmsg, msg->dn);
+ if (val > dsc->highestUSN) {
+ dsc->highestUSN = val;
+ }
+ return ldb_module_send_entry(dsc->req, newmsg, controls);
+ } else {
+ talloc_free(newmsg);
+ return LDB_SUCCESS;
+ }
+}
+
+
+static int dirsync_create_vector(struct ldb_request *req,
+ struct ldb_reply *ares,
+ struct dirsync_context *dsc,
+ struct ldapControlDirSyncCookie *cookie,
+ struct ldb_context *ldb)
+{
+ struct ldb_result *resVector;
+ const char* attrVector[] = {"replUpToDateVector", NULL };
+ uint64_t highest_usn;
+ uint32_t count = 1;
+ int ret;
+ struct drsuapi_DsReplicaCursor *tab;
+
+ ret = ldb_sequence_number(ldb, LDB_SEQ_HIGHEST_SEQ, &highest_usn);
+ if (ret != LDB_SUCCESS) {
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, "Unable to get highest USN from current NC");
+ }
+
+ /* If we have a full answer then the highest USN
+ * is not the highest USN from the result set but the
+ * highest of the naming context, unless the sequence is not updated yet.
+ */
+ if (highest_usn > dsc->highestUSN) {
+ dsc->highestUSN = highest_usn;
+ }
+
+
+ ret = dsdb_module_search_dn(dsc->module, dsc, &resVector,
+ dsc->nc_root,
+ attrVector,
+ DSDB_FLAG_NEXT_MODULE, req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "Unable to get replUpToDateVector for current NC");
+ }
+
+ if (resVector->count != 0) {
+ DATA_BLOB blob;
+ uint32_t i;
+ struct ldb_message_element *el = ldb_msg_find_element(resVector->msgs[0], "replUpToDateVector");
+ if (el) {
+ enum ndr_err_code ndr_err;
+ struct replUpToDateVectorBlob utd;
+ blob.data = el->values[0].data;
+ blob.length = el->values[0].length;
+ ndr_err = ndr_pull_struct_blob(&blob, dsc, &utd,
+ (ndr_pull_flags_fn_t)ndr_pull_replUpToDateVectorBlob);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "Unable to pull replUpToDateVectorBlob structure");
+ }
+
+
+ count += utd.ctr.ctr2.count;
+ tab = talloc_array(cookie, struct drsuapi_DsReplicaCursor, count);
+ if (tab == NULL) {
+ return ldb_oom(ldb);
+ }
+ for (i=1; i < count; i++) {
+ memset(&tab[i], 0, sizeof(struct drsuapi_DsReplicaCursor));
+ tab[i].highest_usn = utd.ctr.ctr2.cursors[i-1].highest_usn;
+ tab[i].source_dsa_invocation_id = utd.ctr.ctr2.cursors[i-1].source_dsa_invocation_id;
+ }
+ } else {
+ tab = talloc_array(cookie, struct drsuapi_DsReplicaCursor, count);
+ if (tab == NULL) {
+ return ldb_oom(ldb);
+ }
+ }
+ } else {
+ /*
+ * No replUpToDateVector ? it happens quite often (1 DC,
+ * other DCs didn't update ...
+ */
+ tab = talloc_array(cookie, struct drsuapi_DsReplicaCursor, count);
+ if (tab == NULL) {
+ return ldb_oom(ldb);
+ }
+ }
+ /* Our vector is always the first */
+ tab[0].highest_usn = dsc->highestUSN;
+ tab[0].source_dsa_invocation_id = *(dsc->our_invocation_id);
+
+
+ /* We have to add the updateness vector that we have*/
+ /* Version is always 1 in dirsync cookies */
+ cookie->blob.extra.uptodateness_vector.version = 1;
+ cookie->blob.extra.uptodateness_vector.reserved = 0;
+ cookie->blob.extra.uptodateness_vector.ctr.ctr1.count = count;
+ cookie->blob.extra.uptodateness_vector.ctr.ctr1.reserved = 0;
+ cookie->blob.extra.uptodateness_vector.ctr.ctr1.cursors = tab;
+
+ return LDB_SUCCESS;
+}
+
+static int dirsync_search_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ int ret;
+ struct dirsync_context *dsc;
+ struct ldb_result *res, *res2;
+ struct ldb_dirsync_control *control;
+ struct ldapControlDirSyncCookie *cookie;
+ struct ldb_context *ldb;
+ struct ldb_dn *dn;
+ struct ldb_val *val;
+ DATA_BLOB *blob;
+ NTTIME now;
+ const char *attrs[] = { "objectGUID", NULL };
+ enum ndr_err_code ndr_err;
+ char *tmp;
+ uint32_t flags;
+
+ dsc = talloc_get_type_abort(req->context, struct dirsync_context);
+ ldb = ldb_module_get_ctx(dsc->module);
+ if (!ares) {
+ return ldb_module_done(dsc->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(dsc->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ return dirsync_filter_entry(req, ares->message, ares->controls, dsc, false);
+
+ case LDB_REPLY_REFERRAL:
+ /* Skip the ldap(s):// so up to 8 chars,
+ * we don't care to be precise as the goal is to be in
+ * the name of DC, then we search the next '/'
+ * as it will be the last char before the DN of the referal
+ */
+ if (strncmp(ares->referral, "ldap://", 7) == 0) {
+ tmp = ares->referral + 7;
+ } else if (strncmp(ares->referral, "ldaps://", 8) == 0) {
+ tmp = ares->referral + 8;
+ } else {
+ return ldb_operr(ldb);
+ }
+
+ tmp = strchr(tmp, '/');
+ if (tmp == NULL) {
+ return ldb_operr(ldb);
+ }
+ tmp++;
+
+ dn = ldb_dn_new(dsc, ldb, tmp);
+ if (dn == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ flags = DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_DELETED |
+ DSDB_SEARCH_SHOW_EXTENDED_DN;
+
+ ret = dsdb_module_search_tree(dsc->module, dsc, &res,
+ dn, LDB_SCOPE_BASE,
+ req->op.search.tree,
+ req->op.search.attrs,
+ flags, req);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(dn);
+ return ret;
+ }
+
+ if (res->count > 1) {
+ char *ldbmsg = talloc_asprintf(dn, "LDB returned more than result for dn: %s", tmp);
+ if (ldbmsg) {
+ ldb_set_errstring(ldb, ldbmsg);
+ }
+ talloc_free(dn);
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ } else if (res->count == 0) {
+ /* if nothing is returned then it means that we don't
+ * have access to it.
+ */
+ return LDB_SUCCESS;
+ }
+
+ talloc_free(dn);
+ /*
+ * Fetch the objectGUID of the root of current NC
+ */
+ ret = dsdb_module_search_dn(dsc->module, dsc, &res2,
+ req->op.search.base,
+ attrs,
+ DSDB_FLAG_NEXT_MODULE, req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (res2->msgs[0]->num_elements != 1) {
+ ldb_set_errstring(ldb,
+ "More than 1 attribute returned while looking for objectGUID");
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ val = res2->msgs[0]->elements[0].values;
+ ret = ldb_msg_add_value(res->msgs[0], "parentGUID", val, NULL);
+ /*
+ * It *very* important to steal otherwise as val is in a subcontext
+ * related to res2, when the value will be one more time stolen
+ * it's elements[x].values that will be stolen, so it's important to
+ * recreate the context hierrachy as if it was done from a ldb_request
+ */
+ talloc_steal(res->msgs[0]->elements[0].values, val);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return dirsync_filter_entry(req, res->msgs[0], res->controls, dsc, true);
+
+ case LDB_REPLY_DONE:
+ /*
+ * Let's add our own control
+ */
+
+ control = talloc_zero(ares->controls, struct ldb_dirsync_control);
+ if (control == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /*
+ * When outputing flags is used to say more results.
+ * For the moment we didn't honnor the size info */
+
+ control->flags = 0;
+
+ /*
+ * max_attribute is unused cf. 3.1.1.3.4.1.3 LDAP_SERVER_DIRSYNC_OID in MS-ADTS
+ */
+
+ control->max_attributes = 0;
+ cookie = talloc_zero(control, struct ldapControlDirSyncCookie);
+ if (cookie == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ if (!dsc->partial) {
+ ret = dirsync_create_vector(req, ares, dsc, cookie, ldb);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(dsc->req, NULL, NULL, ret);
+ }
+ }
+
+ unix_to_nt_time(&now, time(NULL));
+ cookie->blob.time = now;
+ cookie->blob.highwatermark.highest_usn = dsc->highestUSN;
+ cookie->blob.highwatermark.tmp_highest_usn = dsc->highestUSN;
+ cookie->blob.guid1 = *(dsc->our_invocation_id);
+
+ blob = talloc_zero(control, DATA_BLOB);
+ if (blob == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ndr_err = ndr_push_struct_blob(blob, blob, cookie,
+ (ndr_push_flags_fn_t)ndr_push_ldapControlDirSyncCookie);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ ldb_set_errstring(ldb, "Can't marshall ldapControlDirSyncCookie struct");
+ return ldb_module_done(dsc->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+ control->cookie = (char *)blob->data;
+ control->cookie_len = blob->length;
+ ldb_reply_add_control(ares, LDB_CONTROL_DIRSYNC_OID, true, control);
+
+ return ldb_module_done(dsc->req, ares->controls,
+ ares->response, LDB_SUCCESS);
+
+ }
+ return LDB_SUCCESS;
+}
+
+static int dirsync_ldb_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_control *control;
+ struct ldb_result *acl_res;
+ struct ldb_dirsync_control *dirsync_ctl;
+ struct ldb_control *extended = NULL;
+ struct ldb_request *down_req;
+ struct dirsync_context *dsc;
+ struct ldb_context *ldb;
+ struct ldb_parse_tree *new_tree = req->op.search.tree;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ const char **attrs;
+ int ret;
+
+
+ if (ldb_dn_is_special(req->op.search.base)) {
+ return ldb_next_request(module, req);
+ }
+
+ /*
+ * check if there's a dirsync control
+ */
+ control = ldb_request_get_control(req, LDB_CONTROL_DIRSYNC_OID);
+ if (control == NULL) {
+ /* not found go on */
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+ /*
+ * This control must always be critical otherwise we return PROTOCOL error
+ */
+ if (!control->critical) {
+ return ldb_operr(ldb);
+ }
+
+ dsc = talloc_zero(req, struct dirsync_context);
+ if (dsc == NULL) {
+ return ldb_oom(ldb);
+ }
+ dsc->module = module;
+ dsc->req = req;
+ dsc->nbDefaultAttrs = 0;
+
+
+ dirsync_ctl = talloc_get_type(control->data, struct ldb_dirsync_control);
+ if (dirsync_ctl == NULL) {
+ return ldb_error(ldb, LDB_ERR_PROTOCOL_ERROR, "No data in dirsync control");
+ }
+
+ ret = dsdb_find_nc_root(ldb, dsc, req->op.search.base, &dsc->nc_root);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (ldb_dn_compare(dsc->nc_root, req->op.search.base) != 0) {
+ if (dirsync_ctl->flags & LDAP_DIRSYNC_OBJECT_SECURITY) {
+ return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM,
+ "DN is not one of the naming context");
+ }
+ else {
+ return ldb_error(ldb, LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS,
+ "dN is not one of the naming context");
+ }
+ }
+
+ if (!(dirsync_ctl->flags & LDAP_DIRSYNC_OBJECT_SECURITY)) {
+ struct dom_sid *sid;
+ struct security_descriptor *sd = NULL;
+ const char *acl_attrs[] = { "nTSecurityDescriptor", "objectSid", "objectClass", NULL };
+ const struct dsdb_schema *schema = NULL;
+ const struct dsdb_class *objectclass = NULL;
+ /*
+ * If we don't have the flag and if we have the "replicate directory change" granted
+ * then we upgrade ourself to system to not be blocked by the acl
+ */
+ /* FIXME we won't check the replicate directory change filtered attribute set
+ * it should be done so that if attr is not empty then we check that the user
+ * has also this right
+ */
+
+ /*
+ * First change to system to get the SD of the root of current NC
+ * if we don't the acl_read will forbid us the right to read it ...
+ */
+ ret = dsdb_module_search_dn(module, dsc, &acl_res,
+ req->op.search.base,
+ acl_attrs,
+ DSDB_FLAG_NEXT_MODULE|DSDB_FLAG_AS_SYSTEM, req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ sid = samdb_result_dom_sid(dsc, acl_res->msgs[0], "objectSid");
+ /* sid can be null ... */
+ ret = dsdb_get_sd_from_ldb_message(ldb_module_get_ctx(module), acl_res, acl_res->msgs[0], &sd);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ schema = dsdb_get_schema(ldb, req);
+ if (!schema) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ objectclass = dsdb_get_structural_oc_from_msg(schema, acl_res->msgs[0]);
+
+ /*
+ * While we never use the answer to this for access
+ * control (after CVE-2023-4154), we return a
+ * different error message depending on if the user
+ * was granted GUID_DRS_GET_CHANGES to provide a closer
+ * emulation and keep some tests passing.
+ *
+ * (Samba's ACL logic is not well suited to redacting
+ * only the secret and RODC filtered attributes).
+ */
+ ret = acl_check_extended_right(dsc, module, req, objectclass,
+ sd, acl_user_token(module),
+ GUID_DRS_GET_CHANGES, SEC_ADS_CONTROL_ACCESS, sid);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_free(acl_res);
+ } else if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ dsc->functional_level = dsdb_functional_level(ldb);
+
+ if (req->op.search.attrs) {
+ attrs = ldb_attr_list_copy(dsc, req->op.search.attrs);
+ if (attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+ /*
+ * Check if we have only "dn" as attribute, if so then
+ * treat as if "*" was requested
+ */
+ if (attrs && attrs[0]) {
+ if (ldb_attr_cmp(attrs[0], "dn") == 0 && !attrs[1]) {
+ attrs = talloc_array(dsc, const char*, 2);
+ if (attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+ attrs[0] = "*";
+ attrs[1] = NULL;
+ }
+ }
+ /*
+ * When returning all the attributes return also the SD as
+ * Windws do so.
+ */
+ if (ldb_attr_in_list(attrs, "*")) {
+ struct ldb_sd_flags_control *sdctr = talloc_zero(dsc, struct ldb_sd_flags_control);
+ sdctr->secinfo_flags = 0xF;
+ ret = ldb_request_add_control(req, LDB_CONTROL_SD_FLAGS_OID, false, sdctr);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ attrs = ldb_attr_list_copy_add(dsc, attrs, "parentGUID");
+ if (attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+ attrs = ldb_attr_list_copy_add(dsc, attrs, "replPropertyMetaData");
+ if (attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+ /*
+ * When no attributes are asked we in anycase expect at least 3 attributes:
+ * * instanceType
+ * * objectGUID
+ * * parentGUID
+ */
+
+ dsc->nbDefaultAttrs = 3;
+ } else {
+ /*
+ * We will need this two attributes in the callback
+ */
+ attrs = ldb_attr_list_copy_add(dsc, attrs, "usnChanged");
+ if (attrs == NULL) {
+ return ldb_operr(ldb);
+ }
+ attrs = ldb_attr_list_copy_add(dsc, attrs, "replPropertyMetaData");
+ if (attrs == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ if (!ldb_attr_in_list(attrs, "instanceType")) {
+ attrs = ldb_attr_list_copy_add(dsc, attrs, "instanceType");
+ if (attrs == NULL) {
+ return ldb_operr(ldb);
+ }
+ dsc->nbDefaultAttrs++;
+ }
+
+ if (!ldb_attr_in_list(attrs, "objectGUID")) {
+ attrs = ldb_attr_list_copy_add(dsc, attrs, "objectGUID");
+ if (attrs == NULL) {
+ return ldb_operr(ldb);
+ }
+ }
+ /*
+ * Always increment the number of asked attributes as we don't care if objectGUID was asked
+ * or not for counting the number of "real" attributes returned.
+ */
+ dsc->nbDefaultAttrs++;
+
+ if (!ldb_attr_in_list(attrs, "parentGUID")) {
+ attrs = ldb_attr_list_copy_add(dsc, attrs, "parentGUID");
+ if (attrs == NULL) {
+ return ldb_operr(ldb);
+ }
+ }
+ dsc->nbDefaultAttrs++;
+
+ }
+ } else {
+ struct ldb_sd_flags_control *sdctr = talloc_zero(dsc, struct ldb_sd_flags_control);
+ sdctr->secinfo_flags = 0xF;
+ ret = ldb_request_add_control(req, LDB_CONTROL_SD_FLAGS_OID, false, sdctr);
+ attrs = talloc_array(dsc, const char*, 4);
+ if (attrs == NULL) {
+ return ldb_operr(ldb);
+ }
+ attrs[0] = "*";
+ attrs[1] = "parentGUID";
+ attrs[2] = "replPropertyMetaData";
+ attrs[3] = NULL;
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ /*
+ * When no attributes are asked we in anycase expect at least 3 attributes:
+ * * instanceType
+ * * objectGUID
+ * * parentGUID
+ */
+
+ dsc->nbDefaultAttrs = 3;
+ }
+
+ /* check if there's an extended dn control */
+ extended = ldb_request_get_control(req, LDB_CONTROL_EXTENDED_DN_OID);
+ if (extended != NULL) {
+ struct ldb_extended_dn_control *extended_ctrl = NULL;
+
+ if (extended->data != NULL) {
+ extended_ctrl = talloc_get_type(extended->data,
+ struct ldb_extended_dn_control);
+ }
+ if (extended_ctrl != NULL) {
+ dsc->extended_type = extended_ctrl->type;
+ }
+ } else {
+ ret = ldb_request_add_control(req, LDB_CONTROL_EXTENDED_DN_OID, false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ dsc->noextended = true;
+ }
+
+ if (ldb_request_get_control(req, LDB_CONTROL_REVEAL_INTERNALS) == NULL) {
+ ret = ldb_request_add_control(req, LDB_CONTROL_REVEAL_INTERNALS, false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (ldb_request_get_control(req, LDB_CONTROL_SHOW_RECYCLED_OID) == NULL) {
+ ret = ldb_request_add_control(req, LDB_CONTROL_SHOW_RECYCLED_OID, false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (ldb_request_get_control(req, LDB_CONTROL_SHOW_DELETED_OID) == NULL) {
+ ret = ldb_request_add_control(req, LDB_CONTROL_SHOW_DELETED_OID, false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (dirsync_ctl->flags & LDAP_DIRSYNC_INCREMENTAL_VALUES) {
+ dsc->linkIncrVal = true;
+ } else {
+ dsc->linkIncrVal = false;
+ }
+
+ dsc->our_invocation_id = samdb_ntds_invocation_id(ldb);
+ if (dsc->our_invocation_id == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ if (dirsync_ctl->cookie_len > 0) {
+ struct ldapControlDirSyncCookie cookie;
+
+ blob.data = (uint8_t *)dirsync_ctl->cookie;
+ blob.length = dirsync_ctl->cookie_len;
+ ndr_err = ndr_pull_struct_blob(&blob, dsc, &cookie,
+ (ndr_pull_flags_fn_t)ndr_pull_ldapControlDirSyncCookie);
+
+ /* If we can't unmarshall the cookie into the correct structure we return
+ * unsupported critical extension
+ */
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ldb_error(ldb, LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION,
+ "Unable to unmarshall cookie as a ldapControlDirSyncCookie structure");
+ }
+
+ /*
+ * Let's search for the max usn within the cookie
+ */
+ if (GUID_equal(&(cookie.blob.guid1), dsc->our_invocation_id)) {
+ /*
+ * Ok, it's our invocation ID so we can treat the demand
+ * Let's take the highest usn from (tmp)highest_usn
+ */
+ dsc->fromreqUSN = cookie.blob.highwatermark.tmp_highest_usn;
+ dsc->localonly = true;
+
+ if (cookie.blob.highwatermark.highest_usn > cookie.blob.highwatermark.tmp_highest_usn) {
+ dsc->fromreqUSN = cookie.blob.highwatermark.highest_usn;
+ }
+ } else {
+ dsc->localonly = false;
+ }
+ if (cookie.blob.extra_length > 0 &&
+ cookie.blob.extra.uptodateness_vector.ctr.ctr1.count > 0) {
+ struct drsuapi_DsReplicaCursor cursor;
+ uint32_t p;
+ for (p=0; p < cookie.blob.extra.uptodateness_vector.ctr.ctr1.count; p++) {
+ cursor = cookie.blob.extra.uptodateness_vector.ctr.ctr1.cursors[p];
+ if (GUID_equal( &(cursor.source_dsa_invocation_id), dsc->our_invocation_id)) {
+ if (cursor.highest_usn > dsc->fromreqUSN) {
+ dsc->fromreqUSN = cursor.highest_usn;
+ }
+ }
+ }
+ dsc->cursors = talloc_steal(dsc,
+ cookie.blob.extra.uptodateness_vector.ctr.ctr1.cursors);
+ if (dsc->cursors == NULL) {
+ return ldb_oom(ldb);
+ }
+ dsc->cursor_size = p;
+ }
+ }
+
+ DEBUG(4, ("Dirsync: searching with min usn > %llu\n",
+ (long long unsigned int)dsc->fromreqUSN));
+ if (dsc->fromreqUSN > 0) {
+ /* FIXME it would be better to use PRId64 */
+ char *expression = talloc_asprintf(dsc, "(&%s(uSNChanged>=%llu))",
+ ldb_filter_from_tree(dsc,
+ req->op.search.tree),
+ (long long unsigned int)(dsc->fromreqUSN + 1));
+
+ if (expression == NULL) {
+ return ldb_oom(ldb);
+ }
+ new_tree = ldb_parse_tree(req, expression);
+ if (new_tree == NULL) {
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "Problem while parsing tree");
+ }
+
+ }
+ /*
+ * Mark dirsync control as uncritical (done)
+ *
+ * We need this so ranged_results knows how to behave with
+ * dirsync
+ */
+ control->critical = false;
+ dsc->schema = dsdb_get_schema(ldb, dsc);
+ /*
+ * At the beginning we make the hypothesis that we will return a
+ * complete result set.
+ */
+
+ dsc->partial = false;
+
+ /*
+ * 3.1.1.3.4.1.3 of MS-ADTS.pdf specify that if the scope is not subtree
+ * we treat the search as if subtree was specified
+ */
+
+ ret = ldb_build_search_req_ex(&down_req, ldb, dsc,
+ req->op.search.base,
+ LDB_SCOPE_SUBTREE,
+ new_tree,
+ attrs,
+ req->controls,
+ dsc, dirsync_search_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ /* perform the search */
+ return ldb_next_request(module, down_req);
+}
+
+static int dirsync_ldb_init(struct ldb_module *module)
+{
+ int ret;
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_DIRSYNC_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_ERROR,
+ "dirsync: Unable to register control with rootdse!\n");
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_dirsync_ldb_module_ops = {
+ .name = "dirsync",
+ .search = dirsync_ldb_search,
+ .init_context = dirsync_ldb_init,
+};
+
+/*
+ initialise the module
+ */
+_PUBLIC_ int ldb_dirsync_module_init(const char *version)
+{
+ int ret;
+ LDB_MODULE_CHECK_VERSION(version);
+ ret = ldb_register_module(&ldb_dirsync_ldb_module_ops);
+ return ret;
+}
diff --git a/source4/dsdb/samdb/ldb_modules/dns_notify.c b/source4/dsdb/samdb/ldb_modules/dns_notify.c
new file mode 100644
index 0000000..41973ef
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/dns_notify.c
@@ -0,0 +1,450 @@
+/*
+ ldb database library
+
+ Copyright (C) Samuel Cabrero <samuelcabrero@kernevil.me> 2014
+
+ 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 dns_notify module
+ *
+ * Description: Notify the DNS server when zones are changed, either by direct
+ * RPC management calls or DRS inbound replication.
+ *
+ * Author: Samuel Cabrero <samuelcabrero@kernevil.me>
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/proto.h"
+#include "librpc/gen_ndr/ndr_irpc.h"
+#include "lib/messaging/irpc.h"
+#include "librpc/gen_ndr/ndr_irpc_c.h"
+#include "param/param.h"
+#include "util/dlinklist.h"
+
+#undef strcasecmp
+
+struct dns_notify_watched_dn {
+ struct dns_notify_watched_dn *next, *prev;
+ struct ldb_dn *dn;
+};
+
+struct dns_notify_private {
+ struct dns_notify_watched_dn *watched;
+ bool reload_zones;
+};
+
+struct dns_notify_dnssrv_state {
+ struct imessaging_context *msg_ctx;
+ struct dnssrv_reload_dns_zones r;
+};
+
+static void dns_notify_dnssrv_done(struct tevent_req *req)
+{
+ NTSTATUS status;
+ struct dns_notify_dnssrv_state *state;
+
+ state = tevent_req_callback_data(req, struct dns_notify_dnssrv_state);
+
+ status = dcerpc_dnssrv_reload_dns_zones_r_recv(req, state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("%s: Error notifying dns server: %s\n",
+ __func__, nt_errstr(status)));
+ }
+ imessaging_cleanup(state->msg_ctx);
+
+ talloc_free(req);
+ talloc_free(state);
+}
+
+static void dns_notify_dnssrv_send(struct ldb_module *module)
+{
+ struct ldb_context *ldb;
+ struct loadparm_context *lp_ctx;
+ struct dns_notify_dnssrv_state *state;
+ struct dcerpc_binding_handle *handle;
+ struct tevent_req *req;
+
+ ldb = ldb_module_get_ctx(module);
+
+ lp_ctx = ldb_get_opaque(ldb, "loadparm");
+ if (lp_ctx == NULL) {
+ return;
+ }
+
+ state = talloc_zero(module, struct dns_notify_dnssrv_state);
+ if (state == NULL) {
+ return;
+ }
+
+ /* Initialize messaging client */
+ state->msg_ctx = imessaging_client_init(state, lp_ctx,
+ ldb_get_event_context(ldb));
+ if (state->msg_ctx == NULL) {
+ ldb_asprintf_errstring(ldb, "Failed to generate client messaging context in %s",
+ lpcfg_imessaging_path(state, lp_ctx));
+ talloc_free(state);
+ return;
+ }
+
+ /* Get a handle to notify the DNS server */
+ handle = irpc_binding_handle_by_name(state, state->msg_ctx,
+ "dnssrv",
+ &ndr_table_irpc);
+ if (handle == NULL) {
+ imessaging_cleanup(state->msg_ctx);
+ talloc_free(state);
+ return;
+ }
+
+ /* Send the notifications */
+ req = dcerpc_dnssrv_reload_dns_zones_r_send(state,
+ ldb_get_event_context(ldb),
+ handle,
+ &state->r);
+ if (req == NULL) {
+ imessaging_cleanup(state->msg_ctx);
+ talloc_free(state);
+ return;
+ }
+ tevent_req_set_callback(req, dns_notify_dnssrv_done, state);
+}
+
+static int dns_notify_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct dns_notify_private *data;
+ struct dns_notify_watched_dn *w;
+ struct dsdb_schema *schema;
+ const struct dsdb_class *objectclass;
+
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+ data = talloc_get_type(ldb_module_get_private(module),
+ struct dns_notify_private);
+ if (data == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ for (w = data->watched; w; w = w->next) {
+ if (ldb_dn_compare_base(w->dn, req->op.add.message->dn) == 0) {
+ schema = dsdb_get_schema(ldb, req);
+ if (schema == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ objectclass = dsdb_get_structural_oc_from_msg(schema, req->op.add.message);
+ if (objectclass == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, "dnsZone") == 0) {
+ data->reload_zones = true;
+ break;
+ }
+ }
+ }
+
+ return ldb_next_request(module, req);
+}
+
+static int dns_notify_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_context *ldb;
+ struct dns_notify_private *data;
+ struct dns_notify_watched_dn *w;
+ struct ldb_dn *dn;
+ struct ldb_result *res;
+ struct dsdb_schema *schema;
+ const struct dsdb_class *objectclass;
+ const char * const attrs[] = { "objectClass", NULL };
+ int ret;
+
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+ data = talloc_get_type(ldb_module_get_private(module),
+ struct dns_notify_private);
+ if (data == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ tmp_ctx = talloc_new(module);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ for (w = data->watched; w; w = w->next) {
+ if (ldb_dn_compare_base(w->dn, req->op.add.message->dn) == 0) {
+ dn = ldb_dn_copy(tmp_ctx, req->op.mod.message->dn);
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res, dn, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_REVEAL_INTERNALS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT, req);
+ if (ret != LDB_SUCCESS) {
+ /*
+ * We want the give the caller the
+ * error from trying the actual
+ * request, below
+ */
+ break;
+ }
+
+ schema = dsdb_get_schema(ldb, req);
+ if (schema == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ objectclass = dsdb_get_structural_oc_from_msg(schema, res->msgs[0]);
+ if (objectclass == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, "dnsZone") == 0) {
+ data->reload_zones = true;
+ break;
+ }
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return ldb_next_request(module, req);
+}
+
+static int dns_notify_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_context *ldb;
+ struct dns_notify_private *data;
+ struct dns_notify_watched_dn *w;
+ struct ldb_dn *old_dn;
+ struct ldb_result *res;
+ struct dsdb_schema *schema;
+ const struct dsdb_class *objectclass;
+ const char * const attrs[] = { "objectClass", NULL };
+ int ret;
+
+ if (ldb_dn_is_special(req->op.del.dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+ data = talloc_get_type(ldb_module_get_private(module),
+ struct dns_notify_private);
+ if (data == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ tmp_ctx = talloc_new(module);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ for (w = data->watched; w; w = w->next) {
+ if (ldb_dn_compare_base(w->dn, req->op.add.message->dn) == 0) {
+ old_dn = ldb_dn_copy(tmp_ctx, req->op.del.dn);
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res, old_dn, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_REVEAL_INTERNALS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT, req);
+ if (ret != LDB_SUCCESS) {
+ /*
+ * We want the give the caller the
+ * error from trying the actual
+ * request, below
+ */
+ break;
+ }
+
+ schema = dsdb_get_schema(ldb, req);
+ if (schema == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ objectclass = dsdb_get_structural_oc_from_msg(schema, res->msgs[0]);
+ if (objectclass == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, "dnsZone") == 0) {
+ data->reload_zones = true;
+ break;
+ }
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return ldb_next_request(module, req);
+}
+
+static int dns_notify_start_trans(struct ldb_module *module)
+{
+ struct ldb_context *ldb;
+ struct dns_notify_private *data;
+
+ ldb = ldb_module_get_ctx(module);
+ data = talloc_get_type(ldb_module_get_private(module),
+ struct dns_notify_private);
+ if (data == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ data->reload_zones = false;
+
+ return ldb_next_start_trans(module);
+}
+
+static int dns_notify_end_trans(struct ldb_module *module)
+{
+ struct ldb_context *ldb;
+ struct dns_notify_private *data;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+ data = talloc_get_type(ldb_module_get_private(module),
+ struct dns_notify_private);
+ if (data == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ ret = ldb_next_end_trans(module);
+ if (ret == LDB_SUCCESS) {
+ if (data->reload_zones) {
+ dns_notify_dnssrv_send(module);
+ }
+ }
+
+ return ret;
+}
+
+static int dns_notify_del_trans(struct ldb_module *module)
+{
+ struct ldb_context *ldb;
+ struct dns_notify_private *data;
+
+ ldb = ldb_module_get_ctx(module);
+ data = talloc_get_type(ldb_module_get_private(module),
+ struct dns_notify_private);
+ if (data == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ data->reload_zones = false;
+
+ return ldb_next_del_trans(module);
+}
+
+static int dns_notify_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb;
+ struct dns_notify_private *data;
+ struct dns_notify_watched_dn *watched;
+ struct ldb_dn *domain_dn;
+ struct ldb_dn *forest_dn;
+
+ ldb = ldb_module_get_ctx(module);
+
+ data = talloc_zero(module, struct dns_notify_private);
+ if (data == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ domain_dn = ldb_get_default_basedn(ldb);
+ forest_dn = ldb_get_root_basedn(ldb);
+
+ /* Register hook on domain partition */
+ watched = talloc_zero(data, struct dns_notify_watched_dn);
+ if (watched == NULL) {
+ talloc_free(data);
+ return ldb_oom(ldb);
+ }
+ watched->dn = ldb_dn_new_fmt(watched, ldb,
+ "CN=MicrosoftDNS,CN=System,%s",
+ ldb_dn_get_linearized(domain_dn));
+ if (watched->dn == NULL) {
+ talloc_free(data);
+ return ldb_oom(ldb);
+ }
+ DLIST_ADD(data->watched, watched);
+
+ /* Check for DomainDnsZones partition and register hook */
+ watched = talloc_zero(data, struct dns_notify_watched_dn);
+ if (watched == NULL) {
+ talloc_free(data);
+ return ldb_oom(ldb);
+ }
+ watched->dn = ldb_dn_new_fmt(watched, ldb, "CN=MicrosoftDNS,DC=DomainDnsZones,%s", ldb_dn_get_linearized(forest_dn));
+ DLIST_ADD(data->watched, watched);
+
+ /* Check for ForestDnsZones partition and register hook */
+ watched = talloc_zero(data, struct dns_notify_watched_dn);
+ if (watched == NULL) {
+ talloc_free(data);
+ return ldb_oom(ldb);
+ }
+ watched->dn = ldb_dn_new_fmt(watched, ldb, "CN=MicrosoftDNS,DC=ForestDnsZones,%s", ldb_dn_get_linearized(forest_dn));
+ DLIST_ADD(data->watched, watched);
+
+ ldb_module_set_private(module, data);
+
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_dns_notify_module_ops = {
+ .name = "dns_notify",
+ .init_context = dns_notify_init,
+ .add = dns_notify_add,
+ .modify = dns_notify_modify,
+ .del = dns_notify_delete,
+ .start_transaction = dns_notify_start_trans,
+ .end_transaction = dns_notify_end_trans,
+ .del_transaction = dns_notify_del_trans,
+};
+
+int ldb_dns_notify_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_dns_notify_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/dsdb_notification.c b/source4/dsdb/samdb/ldb_modules/dsdb_notification.c
new file mode 100644
index 0000000..ef92eac
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/dsdb_notification.c
@@ -0,0 +1,262 @@
+/*
+ notification control module
+
+ Copyright (C) Stefan Metzmacher 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+#include "ldb/include/ldb.h"
+#include "ldb/include/ldb_errors.h"
+#include "ldb/include/ldb_module.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+struct dsdb_notification_cookie {
+ uint64_t known_usn;
+};
+
+static int dsdb_notification_verify_tree(struct ldb_parse_tree *tree)
+{
+ unsigned int i;
+ int ret;
+ unsigned int num_ok = 0;
+ /*
+ * these attributes are present on every object
+ * and windows accepts them.
+ *
+ * While [MS-ADTS] says only '(objectClass=*)'
+ * would be allowed.
+ */
+ static const char * const attrs_ok[] = {
+ "objectClass",
+ "objectGUID",
+ "distinguishedName",
+ "name",
+ NULL,
+ };
+
+ switch (tree->operation) {
+ case LDB_OP_AND:
+ for (i = 0; i < tree->u.list.num_elements; i++) {
+ /*
+ * all elements need to be valid
+ */
+ ret = dsdb_notification_verify_tree(tree->u.list.elements[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ num_ok++;
+ }
+ break;
+ case LDB_OP_OR:
+ for (i = 0; i < tree->u.list.num_elements; i++) {
+ /*
+ * at least one element needs to be valid
+ */
+ ret = dsdb_notification_verify_tree(tree->u.list.elements[i]);
+ if (ret == LDB_SUCCESS) {
+ num_ok++;
+ break;
+ }
+ }
+ break;
+ case LDB_OP_NOT:
+ case LDB_OP_EQUALITY:
+ case LDB_OP_GREATER:
+ case LDB_OP_LESS:
+ case LDB_OP_APPROX:
+ case LDB_OP_SUBSTRING:
+ case LDB_OP_EXTENDED:
+ break;
+
+ case LDB_OP_PRESENT:
+ ret = ldb_attr_in_list(attrs_ok, tree->u.present.attr);
+ if (ret == 1) {
+ num_ok++;
+ }
+ break;
+ }
+
+ if (num_ok != 0) {
+ return LDB_SUCCESS;
+ }
+
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+}
+
+static int dsdb_notification_filter_search(struct ldb_module *module,
+ struct ldb_request *req,
+ struct ldb_control *control)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ char *filter_usn = NULL;
+ struct ldb_parse_tree *down_tree = NULL;
+ struct ldb_request *down_req = NULL;
+ struct dsdb_notification_cookie *cookie = NULL;
+ int ret;
+
+ if (req->op.search.tree == NULL) {
+ return dsdb_module_werror(module, LDB_ERR_OTHER,
+ WERR_DS_NOTIFY_FILTER_TOO_COMPLEX,
+ "Search filter missing.");
+ }
+
+ ret = dsdb_notification_verify_tree(req->op.search.tree);
+ if (ret != LDB_SUCCESS) {
+ return dsdb_module_werror(module, ret,
+ WERR_DS_NOTIFY_FILTER_TOO_COMPLEX,
+ "Search filter too complex.");
+ }
+
+ /*
+ * For now we use a very simple design:
+ *
+ * - We don't do fully async ldb_requests,
+ * the caller needs to retry periodically!
+ * - The only useful caller is the LDAP server, which is a long
+ * running task that can do periodic retries.
+ * - We use a cookie in order to transfer state between the
+ * retries.
+ * - We just search the available new objects each time we're
+ * called.
+ *
+ * As the only valid search filter is '(objectClass=*)' or
+ * something similar that matches every object, we simply
+ * replace it with (uSNChanged >= ) filter.
+ * We could improve this later if required...
+ */
+
+ /*
+ * The ldap_control_handler() decode_flag_request for
+ * LDB_CONTROL_NOTIFICATION_OID. This makes sure
+ * notification_control->data is NULL when comming from
+ * the client.
+ */
+ if (control->data == NULL) {
+ cookie = talloc_zero(control, struct dsdb_notification_cookie);
+ if (cookie == NULL) {
+ return ldb_module_oom(module);
+ }
+ control->data = (uint8_t *)cookie;
+
+ /* mark the control as done */
+ control->critical = 0;
+ }
+
+ cookie = talloc_get_type_abort(control->data,
+ struct dsdb_notification_cookie);
+
+ if (cookie->known_usn != 0) {
+ filter_usn = talloc_asprintf(req, "%llu",
+ (unsigned long long)(cookie->known_usn)+1);
+ if (filter_usn == NULL) {
+ return ldb_module_oom(module);
+ }
+ }
+
+ ret = ldb_sequence_number(ldb, LDB_SEQ_HIGHEST_SEQ,
+ &cookie->known_usn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (filter_usn == NULL) {
+ /*
+ * It's the first time, let the caller comeback later
+ * as we won't find any new objects.
+ */
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
+ }
+
+ down_tree = talloc_zero(req, struct ldb_parse_tree);
+ if (down_tree == NULL) {
+ return ldb_module_oom(module);
+ }
+ down_tree->operation = LDB_OP_GREATER;
+ down_tree->u.equality.attr = "uSNChanged";
+ down_tree->u.equality.value = data_blob_string_const(filter_usn);
+ (void)talloc_move(down_req, &filter_usn);
+
+ ret = ldb_build_search_req_ex(&down_req, ldb, req,
+ req->op.search.base,
+ req->op.search.scope,
+ down_tree,
+ req->op.search.attrs,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* perform the search */
+ return ldb_next_request(module, down_req);
+}
+
+static int dsdb_notification_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_control *control = NULL;
+
+ if (ldb_dn_is_special(req->op.search.base)) {
+ return ldb_next_request(module, req);
+ }
+
+ /*
+ * check if there's an extended dn control
+ */
+ control = ldb_request_get_control(req, LDB_CONTROL_NOTIFICATION_OID);
+ if (control == NULL) {
+ /* not found go on */
+ return ldb_next_request(module, req);
+ }
+
+ return dsdb_notification_filter_search(module, req, control);
+}
+
+static int dsdb_notification_init(struct ldb_module *module)
+{
+ int ret;
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_NOTIFICATION_OID);
+ if (ret != LDB_SUCCESS) {
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "notification: Unable to register control with rootdse!\n");
+ return ldb_module_operr(module);
+ }
+
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_dsdb_notification_module_ops = {
+ .name = "dsdb_notification",
+ .search = dsdb_notification_search,
+ .init_context = dsdb_notification_init,
+};
+
+/*
+ initialise the module
+ */
+_PUBLIC_ int ldb_dsdb_notification_module_init(const char *version)
+{
+ int ret;
+ LDB_MODULE_CHECK_VERSION(version);
+ ret = ldb_register_module(&ldb_dsdb_notification_module_ops);
+ return ret;
+}
diff --git a/source4/dsdb/samdb/ldb_modules/encrypted_secrets.c b/source4/dsdb/samdb/ldb_modules/encrypted_secrets.c
new file mode 100644
index 0000000..8c59418
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/encrypted_secrets.c
@@ -0,0 +1,1401 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
+
+ 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/>.
+*/
+
+/*
+ * Encrypt the samba secret attributes on disk. This is intended to
+ * mitigate the inadvertent disclosure of the sam.ldb file, and to mitigate
+ * memory read attacks.
+ *
+ * Currently the key file is stored in the same directory as sam.ldb but
+ * this could be changed at a later date to use an HSM or similar mechanism
+ * to protect the key.
+ *
+ * Data is encrypted with AES 128 GCM. The encryption uses gnutls where
+ * available and if it supports AES 128 GCM AEAD modes, otherwise the
+ * samba internal implementation is used.
+ *
+ */
+
+#include "includes.h"
+#include <ldb_module.h>
+
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+static const char * const secret_attributes[] = {DSDB_SECRET_ATTRIBUTES};
+static const size_t num_secret_attributes = ARRAY_SIZE(secret_attributes);
+
+#define SECRET_ATTRIBUTE_VERSION 1
+#define SECRET_ENCRYPTION_ALGORITHM ENC_SECRET_AES_128_AEAD
+#define NUMBER_OF_KEYS 1
+#define SECRETS_KEY_FILE "encrypted_secrets.key"
+
+#undef strcasecmp
+
+struct es_data {
+ /*
+ * Should secret attributes be encrypted and decrypted?
+ */
+ bool encrypt_secrets;
+ /*
+ * Encryption keys for secret attributes
+ */
+ DATA_BLOB keys[NUMBER_OF_KEYS];
+ /*
+ * The gnutls algorithm used to encrypt attributes
+ */
+ int encryption_algorithm;
+};
+
+/*
+ * @brief Get the key used to encrypt and decrypt secret attributes on disk.
+ *
+ * @param data the private context data for this module.
+ *
+ * @return A data blob containing the key.
+ * This should be treated as read only.
+ */
+static const DATA_BLOB get_key(const struct es_data *data) {
+
+ return data->keys[0];
+}
+
+/*
+ * @brief Get the directory containing the key files.
+ *
+ * @param ctx talloc memory context that will own the return value
+ * @param ldb ldb context, to allow logging
+ *
+ * @return zero terminated string, the directory containing the key file
+ * allocated on ctx.
+ *
+ */
+static const char* get_key_directory(TALLOC_CTX *ctx, struct ldb_context *ldb)
+{
+
+ const char *sam_ldb_path = NULL;
+ const char *private_dir = NULL;
+ char *p = NULL;
+
+
+ /*
+ * Work out where *our* key file is. It must be in
+ * the same directory as sam.ldb
+ */
+ sam_ldb_path = ldb_get_opaque(ldb, "ldb_url");
+ if (sam_ldb_path == NULL) {
+ ldb_set_errstring(ldb, "Unable to get ldb_url\n");
+ return NULL;
+ }
+
+ if (strncmp("tdb://", sam_ldb_path, 6) == 0) {
+ sam_ldb_path += 6;
+ }
+ else if (strncmp("ldb://", sam_ldb_path, 6) == 0) {
+ sam_ldb_path += 6;
+ }
+ else if (strncmp("mdb://", sam_ldb_path, 6) == 0) {
+ sam_ldb_path += 6;
+ }
+ private_dir = talloc_strdup(ctx, sam_ldb_path);
+ if (private_dir == NULL) {
+ ldb_set_errstring(ldb,
+ "Out of memory building encrypted "
+ "secrets key\n");
+ return NULL;
+ }
+
+ p = strrchr(private_dir, '/');
+ if (p != NULL) {
+ *p = '\0';
+ } else {
+ private_dir = talloc_strdup(ctx, ".");
+ }
+
+ return private_dir;
+}
+
+/*
+ * @brief log details of an error that set errno
+ *
+ * @param ldb ldb context, to allow logging.
+ * @param err the value of errno.
+ * @param desc extra text to help describe the error.
+ *
+ */
+static void log_error(struct ldb_context *ldb, int err, const char *desc)
+{
+ char buf[1024];
+ int e = strerror_r(err, buf, sizeof(buf));
+ if (e != 0) {
+ strlcpy(buf, "Unknown error", sizeof(buf)-1);
+ }
+ ldb_asprintf_errstring(ldb, "Error (%d) %s - %s\n", err, buf, desc);
+}
+
+/*
+ * @brief Load the keys into the encrypted secrets module context.
+ *
+ * @param module the current ldb module
+ * @param data the private data for the current module
+ *
+ * Currently the keys are stored in a binary file in the same directory
+ * as the database.
+ *
+ * @return an LDB result code.
+ *
+ */
+static int load_keys(struct ldb_module *module, struct es_data *data)
+{
+
+ const char *key_dir = NULL;
+ const char *key_path = NULL;
+
+ struct ldb_context *ldb = NULL;
+ FILE *fp = NULL;
+ const int key_size = 16;
+ int read;
+ DATA_BLOB key = data_blob_null;
+
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ldb = ldb_module_get_ctx(module);
+ key_dir = get_key_directory(frame, ldb);
+ if (key_dir == NULL) {
+ TALLOC_FREE(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ key_path = talloc_asprintf(frame, "%s/%s", key_dir, SECRETS_KEY_FILE);
+ if (key_path == NULL) {
+ TALLOC_FREE(frame);
+ return ldb_oom(ldb);
+ }
+
+
+ key = data_blob_talloc_zero(module, key_size);
+ key.length = key_size;
+
+ fp = fopen(key_path, "rb");
+ if (fp == NULL) {
+ TALLOC_FREE(frame);
+ data_blob_free(&key);
+ if (errno == ENOENT) {
+ ldb_debug(ldb,
+ LDB_DEBUG_WARNING,
+ "No encrypted secrets key file. "
+ "Secret attributes will not be encrypted or "
+ "decrypted\n");
+ data->encrypt_secrets = false;
+ return LDB_SUCCESS;
+ } else {
+ log_error(ldb,
+ errno,
+ "Opening encrypted_secrets key file\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ read = fread(key.data, 1, key.length, fp);
+ fclose(fp);
+ if (read == 0) {
+ TALLOC_FREE(frame);
+ ldb_debug(ldb,
+ LDB_DEBUG_WARNING,
+ "Zero length encrypted secrets key file. "
+ "Secret attributes will not be encrypted or "
+ "decrypted\n");
+ data->encrypt_secrets = false;
+ return LDB_SUCCESS;
+ }
+ if (read != key.length) {
+ TALLOC_FREE(frame);
+ if (errno) {
+ log_error(ldb,
+ errno,
+ "Reading encrypted_secrets key file\n");
+ } else {
+ ldb_debug(ldb,
+ LDB_DEBUG_ERROR,
+ "Invalid encrypted_secrets key file, "
+ "only %d bytes read should be %d bytes\n",
+ read,
+ key_size);
+ }
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ data->keys[0] = key;
+ data->encrypt_secrets = true;
+ data->encryption_algorithm = GNUTLS_CIPHER_AES_128_GCM;
+ TALLOC_FREE(frame);
+
+ return LDB_SUCCESS;
+
+}
+
+/*
+ * @brief should this element be encrypted.
+ *
+ * @param el the element to examine
+ *
+ * @return true if the element should be encrypted,
+ * false otherwise.
+ */
+static bool should_encrypt(const struct ldb_message_element *el)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(secret_attributes); i++) {
+ if (strcasecmp(secret_attributes[i], el->name) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * @brief Round a size up to a multiple of the encryption cipher block size.
+ *
+ * @param block_size The cipher block size
+ * @param size The size to round
+ *
+ * @return Size rounded up to the nearest multiple of block_size
+ */
+static size_t round_to_block_size(size_t block_size, size_t size)
+{
+ if ((size % block_size) == 0) {
+ return size;
+ } else {
+ return ((int)(size/block_size) + 1) * block_size;
+ }
+}
+
+/*
+ * @brief Create an new EncryptedSecret owned by the supplied talloc context.
+ *
+ * Create a new encrypted secret and initialise the header.
+ *
+ * @param ldb ldb context, to allow logging.
+ * @param ctx The talloc memory context that will own the new EncryptedSecret
+ *
+ * @return pointer to the new encrypted secret, or NULL if there was an error
+ */
+static struct EncryptedSecret *makeEncryptedSecret(struct ldb_context *ldb,
+ TALLOC_CTX *ctx)
+{
+ struct EncryptedSecret *es = NULL;
+
+ es = talloc_zero_size(ctx, sizeof(struct EncryptedSecret));
+ if (es == NULL) {
+ ldb_set_errstring(ldb,
+ "Out of memory, allocating "
+ "struct EncryptedSecret\n");
+ return NULL;
+ }
+ es->header.magic = ENCRYPTED_SECRET_MAGIC_VALUE;
+ es->header.version = SECRET_ATTRIBUTE_VERSION;
+ es->header.algorithm = SECRET_ENCRYPTION_ALGORITHM;
+ es->header.flags = 0;
+ return es;
+}
+
+/*
+ * @brief Allocate and populate a data blob with a PlaintextSecret structure.
+ *
+ * Allocate a new data blob and populate it with a serialised PlaintextSecret,
+ * containing the ldb_val
+ *
+ * @param ctx The talloc memory context that will own the allocated memory.
+ * @param ldb ldb context, to allow logging.
+ * @param val The ldb value to serialise.
+ *
+ * @return The populated data blob or data_blob_null if there was an error.
+ */
+static DATA_BLOB makePlainText(TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const struct ldb_val val)
+{
+ struct PlaintextSecret ps = { .cleartext = data_blob_null};
+ DATA_BLOB pt = data_blob_null;
+ int rc;
+
+ ps.cleartext.length = val.length;
+ ps.cleartext.data = val.data;
+
+ rc = ndr_push_struct_blob(&pt,
+ ctx,
+ &ps,
+ (ndr_push_flags_fn_t)
+ ndr_push_PlaintextSecret);
+ if (!NDR_ERR_CODE_IS_SUCCESS(rc)) {
+ ldb_set_errstring(ldb,
+ "Unable to ndr push PlaintextSecret\n");
+ return data_blob_null;
+ }
+ return pt;
+}
+
+
+/*
+ * Helper function converts a data blob to a gnutls_datum_t.
+ * Note that this does not copy the data.
+ * So the returned value should be treated as read only.
+ * And that changes to the length of the underlying DATA_BLOB
+ * will not be reflected in the returned object.
+ *
+ */
+static const gnutls_datum_t convert_from_data_blob(DATA_BLOB blob) {
+
+ const gnutls_datum_t datum = {
+ .size = blob.length,
+ .data = blob.data,
+ };
+ return datum;
+}
+
+/*
+ * @brief Get the gnutls algorithm needed to decrypt the EncryptedSecret
+ *
+ * @param ldb ldb context, to allow logging.
+ * @param es the encrypted secret
+ *
+ * @return The gnutls algoritm number, or 0 if there is no match.
+ *
+ */
+static int gnutls_get_algorithm(struct ldb_context *ldb,
+ struct EncryptedSecret *es) {
+
+ switch (es->header.algorithm) {
+ case ENC_SECRET_AES_128_AEAD:
+ return GNUTLS_CIPHER_AES_128_GCM;
+ default:
+ ldb_asprintf_errstring(ldb,
+ "Unsupported encryption algorithm %d\n",
+ es->header.algorithm);
+ return 0;
+ }
+}
+
+/*
+ *
+ * @param err Pointer to an error code, set to:
+ * LDB_SUCESS If the value was successfully encrypted
+ * LDB_ERR_OPERATIONS_ERROR If there was an error.
+ *
+ * @param ctx Talloc memory context the will own the memory allocated
+ * @param ldb ldb context, to allow logging.
+ * @param val The ldb value to encrypt, not altered or freed
+ * @param data The context data for this module.
+ *
+ * @return The encrypted ldb_val, or data_blob_null if there was an error.
+ */
+static struct ldb_val gnutls_encrypt_aead(int *err,
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const struct ldb_val val,
+ const struct es_data *data)
+{
+ struct EncryptedSecret *es = NULL;
+ struct ldb_val enc = data_blob_null;
+ DATA_BLOB pt = data_blob_null;
+ gnutls_aead_cipher_hd_t cipher_hnd;
+ int rc;
+
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ es = makeEncryptedSecret(ldb, frame);
+ if (es == NULL) {
+ goto error_exit;
+ }
+
+ pt = makePlainText(frame, ldb, val);
+ if (pt.length == 0) {
+ goto error_exit;
+ }
+
+ /*
+ * Set the encryption key and initialize the encryption handle.
+ */
+ {
+ const size_t key_size = gnutls_cipher_get_key_size(
+ data->encryption_algorithm);
+ gnutls_datum_t cipher_key;
+ DATA_BLOB key_blob = get_key(data);
+
+ if (key_blob.length != key_size) {
+ ldb_asprintf_errstring(ldb,
+ "Invalid EncryptedSecrets key "
+ "size, expected %zu bytes and "
+ "it is %zu bytes\n",
+ key_size,
+ key_blob.length);
+ goto error_exit;
+ }
+ cipher_key = convert_from_data_blob(key_blob);
+
+ rc = gnutls_aead_cipher_init(&cipher_hnd,
+ data->encryption_algorithm,
+ &cipher_key);
+ if (rc !=0) {
+ ldb_asprintf_errstring(ldb,
+ "gnutls_aead_cipher_init failed "
+ "%s - %s\n",
+ gnutls_strerror_name(rc),
+ gnutls_strerror(rc));
+ goto error_exit;
+ }
+
+ }
+
+ /*
+ * Set the initialisation vector
+ */
+ {
+ unsigned iv_size = gnutls_cipher_get_iv_size(
+ data->encryption_algorithm);
+ uint8_t *iv;
+
+ iv = talloc_zero_size(frame, iv_size);
+ if (iv == NULL) {
+ ldb_set_errstring(ldb,
+ "Out of memory allocating IV\n");
+ goto error_exit_handle;
+ }
+
+ rc = gnutls_rnd(GNUTLS_RND_NONCE, iv, iv_size);
+ if (rc !=0) {
+ ldb_asprintf_errstring(ldb,
+ "gnutls_rnd failed %s - %s\n",
+ gnutls_strerror_name(rc),
+ gnutls_strerror(rc));
+ goto error_exit_handle;
+ }
+ es->iv.length = iv_size;
+ es->iv.data = iv;
+ }
+
+ /*
+ * Encrypt the value.
+ */
+ {
+ const unsigned block_size = gnutls_cipher_get_block_size(
+ data->encryption_algorithm);
+ const unsigned tag_size = gnutls_cipher_get_tag_size(
+ data->encryption_algorithm);
+ const size_t ed_size = round_to_block_size(
+ block_size,
+ sizeof(struct PlaintextSecret) + val.length);
+ const size_t en_size = ed_size + tag_size;
+ uint8_t *ct = talloc_zero_size(frame, en_size);
+ size_t el = en_size;
+
+ if (ct == NULL) {
+ ldb_set_errstring(ldb,
+ "Out of memory allocation cipher "
+ "text\n");
+ goto error_exit_handle;
+ }
+
+ rc = gnutls_aead_cipher_encrypt(
+ cipher_hnd,
+ es->iv.data,
+ es->iv.length,
+ &es->header,
+ sizeof(struct EncryptedSecretHeader),
+ tag_size,
+ pt.data,
+ pt.length,
+ ct,
+ &el);
+ if (rc !=0) {
+ ldb_asprintf_errstring(ldb,
+ "gnutls_aead_cipher_encrypt '"
+ "failed %s - %s\n",
+ gnutls_strerror_name(rc),
+ gnutls_strerror(rc));
+ *err = LDB_ERR_OPERATIONS_ERROR;
+ return data_blob_null;
+ }
+ es->encrypted.length = el;
+ es->encrypted.data = ct;
+ gnutls_aead_cipher_deinit(cipher_hnd);
+ }
+
+ rc = ndr_push_struct_blob(&enc,
+ ctx,
+ es,
+ (ndr_push_flags_fn_t)
+ ndr_push_EncryptedSecret);
+ if (!NDR_ERR_CODE_IS_SUCCESS(rc)) {
+ ldb_set_errstring(ldb,
+ "Unable to ndr push EncryptedSecret\n");
+ goto error_exit;
+ }
+ TALLOC_FREE(frame);
+ return enc;
+
+error_exit_handle:
+ gnutls_aead_cipher_deinit(cipher_hnd);
+error_exit:
+ *err = LDB_ERR_OPERATIONS_ERROR;
+ TALLOC_FREE(frame);
+ return data_blob_null;
+}
+
+/*
+ * @brief Decrypt data encrypted using an aead algorithm.
+ *
+ * Decrypt the data in ed and insert it into ev. The data was encrypted
+ * with one of the gnutls aead compatable algorithms.
+ *
+ * @param err Pointer to an error code, set to:
+ * LDB_SUCESS If the value was successfully decrypted
+ * LDB_ERR_OPERATIONS_ERROR If there was an error.
+ *
+ * @param ctx The talloc context that will own the PlaintextSecret
+ * @param ldb ldb context, to allow logging.
+ * @param ev The value to be updated with the decrypted data.
+ * @param ed The data to decrypt.
+ * @param data The context data for this module.
+ *
+ * @return ev is updated with the unencrypted data.
+ */
+static void gnutls_decrypt_aead(int *err,
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ struct EncryptedSecret *es,
+ struct PlaintextSecret *ps,
+ const struct es_data *data)
+{
+
+ gnutls_aead_cipher_hd_t cipher_hnd;
+ DATA_BLOB pt = data_blob_null;
+ const unsigned tag_size =
+ gnutls_cipher_get_tag_size(es->header.algorithm);
+ int rc;
+
+ /*
+ * Get the encryption key and initialise the encryption handle
+ */
+ {
+ gnutls_datum_t cipher_key;
+ DATA_BLOB key_blob;
+ const int algorithm = gnutls_get_algorithm(ldb, es);
+ const size_t key_size = gnutls_cipher_get_key_size(algorithm);
+ key_blob = get_key(data);
+
+ if (algorithm == 0) {
+ goto error_exit;
+ }
+
+ if (key_blob.length != key_size) {
+ ldb_asprintf_errstring(ldb,
+ "Invalid EncryptedSecrets key "
+ "size, expected %zu bytes and "
+ "it is %zu bytes\n",
+ key_size,
+ key_blob.length);
+ goto error_exit;
+ }
+ cipher_key = convert_from_data_blob(key_blob);
+
+ rc = gnutls_aead_cipher_init(
+ &cipher_hnd,
+ algorithm,
+ &cipher_key);
+ if (rc != 0) {
+ ldb_asprintf_errstring(ldb,
+ "gnutls_aead_cipher_init failed "
+ "%s - %s\n",
+ gnutls_strerror_name(rc),
+ gnutls_strerror(rc));
+ goto error_exit;
+ }
+ }
+
+ /*
+ * Decrypt and validate the encrypted value
+ */
+
+ pt.length = es->encrypted.length;
+ pt.data = talloc_zero_size(ctx, es->encrypted.length);
+
+ if (pt.data == NULL) {
+ ldb_set_errstring(ldb,
+ "Out of memory allocating plain text\n");
+ goto error_exit_handle;
+ }
+
+ rc = gnutls_aead_cipher_decrypt(cipher_hnd,
+ es->iv.data,
+ es->iv.length,
+ &es->header,
+ sizeof(struct EncryptedSecretHeader),
+ tag_size,
+ es->encrypted.data,
+ es->encrypted.length,
+ pt.data,
+ &pt.length);
+ if (rc != 0) {
+ /*
+ * Typically this will indicate that the data has been
+ * corrupted i.e. the tag comparison has failed.
+ * At the moment gnutls does not provide a separate
+ * error code to indicate this
+ */
+ ldb_asprintf_errstring(ldb,
+ "gnutls_aead_cipher_decrypt failed "
+ "%s - %s. Data possibly corrupted or "
+ "altered\n",
+ gnutls_strerror_name(rc),
+ gnutls_strerror(rc));
+ goto error_exit_handle;
+ }
+ gnutls_aead_cipher_deinit(cipher_hnd);
+
+ rc = ndr_pull_struct_blob(&pt,
+ ctx,
+ ps,
+ (ndr_pull_flags_fn_t)
+ ndr_pull_PlaintextSecret);
+ if(!NDR_ERR_CODE_IS_SUCCESS(rc)) {
+ ldb_asprintf_errstring(ldb,
+ "Error(%d) unpacking decrypted data, "
+ "data possibly corrupted or altered\n",
+ rc);
+ goto error_exit;
+ }
+ return;
+
+error_exit_handle:
+ gnutls_aead_cipher_deinit(cipher_hnd);
+error_exit:
+ *err = LDB_ERR_OPERATIONS_ERROR;
+ return;
+}
+
+/*
+ * @brief Encrypt an attribute value using the default encryption algorithm.
+ *
+ * Returns an encrypted copy of the value, the original value is left intact.
+ * The original content of val is encrypted and wrapped in an encrypted_value
+ * structure.
+ *
+ * @param err Pointer to an error code, set to:
+ * LDB_SUCESS If the value was successfully encrypted
+ * LDB_ERR_OPERATIONS_ERROR If there was an error.
+ *
+ * @param ctx Talloc memory context the will own the memory allocated
+ * @param ldb ldb context, to allow logging.
+ * @param val The ldb value to encrypt, not altered or freed
+ * @param data The context data for this module.
+ *
+ * @return The encrypted ldb_val, or data_blob_null if there was an error.
+ */
+static struct ldb_val encrypt_value(int *err,
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const struct ldb_val val,
+ const struct es_data *data)
+{
+ return gnutls_encrypt_aead(err, ctx, ldb, val, data);
+}
+
+/*
+ * @brief Encrypt all the values on an ldb_message_element
+ *
+ * Returns a copy of the original attribute with all values encrypted
+ * by encrypt_value(), the original attribute is left intact.
+ *
+ * @param err Pointer to an error code, set to:
+ * LDB_SUCESS If the value was successfully encrypted
+ * LDB_ERR_OPERATIONS_ERROR If there was an error.
+ *
+ * @param ctx Talloc memory context the will own the memory allocated
+ * for the new ldb_message_element.
+ * @param ldb ldb context, to allow logging.
+ * @param el The ldb_message_elemen to encrypt, not altered or freed
+ * @param data The context data for this module.
+ *
+ * @return Pointer encrypted lsb_message_element, will be NULL if there was
+ * an error.
+ */
+static struct ldb_message_element *encrypt_element(
+ int *err,
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const struct ldb_message_element *el,
+ const struct es_data *data)
+{
+ struct ldb_message_element* enc;
+ unsigned int i;
+
+ enc = talloc_zero(ctx, struct ldb_message_element);
+ if (enc == NULL) {
+ ldb_set_errstring(ldb,
+ "Out of memory, allocating ldb_message_"
+ "element\n");
+ *err = LDB_ERR_OPERATIONS_ERROR;
+ return NULL;
+ }
+
+ enc->flags = el->flags;
+ enc->num_values = el->num_values;
+ enc->values = talloc_array(enc, struct ldb_val, enc->num_values);
+ if (enc->values == NULL) {
+ TALLOC_FREE(enc);
+ ldb_set_errstring(ldb,
+ "Out of memory, allocating values array\n");
+ *err = LDB_ERR_OPERATIONS_ERROR;
+ return NULL;
+ }
+
+ enc->name = talloc_strdup(enc, el->name);
+ if (enc->name == NULL) {
+ TALLOC_FREE(enc);
+ ldb_set_errstring(ldb,
+ "Out of memory, copying element name\n");
+ *err = LDB_ERR_OPERATIONS_ERROR;
+ return NULL;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ enc->values[i] =
+ encrypt_value(
+ err,
+ enc->values,
+ ldb,
+ el->values[i],
+ data);
+ if (*err != LDB_SUCCESS) {
+ TALLOC_FREE(enc);
+ return NULL;
+ }
+ }
+ return enc;
+}
+
+/*
+ * @brief Encrypt all the secret attributes on an ldb_message
+ *
+ * Encrypt all the secret attributes on an ldb_message. Any secret
+ * attributes are removed from message and encrypted copies of the
+ * attributes added. In the event of an error the contents of the
+ * message will be inconsistent.
+ *
+ * @param err Pointer to an error code, set to:
+ * LDB_SUCESS If the value was successfully encrypted
+ * LDB_ERR_OPERATIONS_ERROR If there was an error.
+ * @param ldb ldb context, to allow logging.
+ * @param msg The ldb_message to have it's secret attributes encrypted.
+ *
+ * @param data The context data for this module.
+ */
+static const struct ldb_message *encrypt_secret_attributes(
+ int *err,
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const struct ldb_message *msg,
+ const struct es_data *data)
+{
+ struct ldb_message *encrypted_msg = NULL;
+
+ unsigned int i;
+
+ if (ldb_dn_is_special(msg->dn)) {
+ return NULL;
+ }
+
+ for (i = 0; i < msg->num_elements; i++) {
+
+ const struct ldb_message_element *el = &msg->elements[i];
+ if (should_encrypt(el)) {
+ struct ldb_message_element* enc = NULL;
+ if (encrypted_msg == NULL) {
+ encrypted_msg = ldb_msg_copy_shallow(ctx, msg);
+ if (encrypted_msg == NULL) {
+ ldb_set_errstring(
+ ldb,
+ "Out of memory, allocating "
+ "ldb_message_element\n");
+ *err = LDB_ERR_OPERATIONS_ERROR;
+ return NULL;
+ }
+ encrypted_msg->dn = msg->dn;
+ }
+ enc = encrypt_element(err,
+ msg->elements,
+ ldb,
+ el,
+ data);
+ if (*err != LDB_SUCCESS) {
+ return NULL;
+ }
+ encrypted_msg->elements[i] = *enc;
+ }
+ }
+ return encrypted_msg;
+}
+
+/*
+ * @brief Check the encrypted secret header to ensure it's valid
+ *
+ * Check an Encrypted secret and ensure it's header is valid.
+ * A header is assumed to be valid if it:
+ * - it starts with the MAGIC_VALUE
+ * - The version number is valid
+ * - The algorithm is valid
+ *
+ * @param val The EncryptedSecret to check.
+ *
+ * @return true if the header is valid, false otherwise.
+ *
+ */
+static bool check_header(struct EncryptedSecret *es)
+{
+ struct EncryptedSecretHeader *eh;
+
+ eh = &es->header;
+ if (eh->magic != ENCRYPTED_SECRET_MAGIC_VALUE) {
+ /*
+ * Does not start with the magic value so not
+ * an encrypted_value
+ */
+ return false;
+ }
+
+ if (eh->version > SECRET_ATTRIBUTE_VERSION) {
+ /*
+ * Invalid version, so not an encrypted value
+ */
+ return false;
+ }
+
+ if (eh->algorithm != ENC_SECRET_AES_128_AEAD) {
+ /*
+ * Invalid algorithm, so not an encrypted value
+ */
+ return false;
+ }
+ /*
+ * Length looks ok, starts with magic value, and the version and
+ * algorithm are valid
+ */
+ return true;
+}
+/*
+ * @brief Decrypt an attribute value.
+ *
+ * Returns a decrypted copy of the value, the original value is left intact.
+ *
+ * @param err Pointer to an error code, set to:
+ * LDB_SUCESS If the value was successfully decrypted
+ * LDB_ERR_OPERATIONS_ERROR If there was an error.
+ *
+ * @param ctx Talloc memory context the will own the memory allocated
+ * @param ldb ldb context, to allow logging.
+ * @param val The ldb value to decrypt, not altered or freed
+ * @param data The context data for this module.
+ *
+ * @return The decrypted ldb_val, or data_blob_null if there was an error.
+ */
+static struct ldb_val decrypt_value(int *err,
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const struct ldb_val val,
+ const struct es_data *data)
+{
+
+ struct ldb_val dec;
+
+ struct EncryptedSecret es;
+ struct PlaintextSecret ps = { data_blob_null};
+ int rc;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ rc = ndr_pull_struct_blob(&val,
+ frame,
+ &es,
+ (ndr_pull_flags_fn_t)
+ ndr_pull_EncryptedSecret);
+ if(!NDR_ERR_CODE_IS_SUCCESS(rc)) {
+ ldb_asprintf_errstring(ldb,
+ "Error(%d) unpacking encrypted secret, "
+ "data possibly corrupted or altered\n",
+ rc);
+ *err = LDB_ERR_OPERATIONS_ERROR;
+ TALLOC_FREE(frame);
+ return data_blob_null;
+ }
+ if (!check_header(&es)) {
+ /*
+ * Header is invalid so can't be an encrypted value
+ */
+ ldb_set_errstring(ldb, "Invalid EncryptedSecrets header\n");
+ *err = LDB_ERR_OPERATIONS_ERROR;
+ return data_blob_null;
+ }
+ gnutls_decrypt_aead(err, frame, ldb, &es, &ps, data);
+
+ if (*err != LDB_SUCCESS) {
+ TALLOC_FREE(frame);
+ return data_blob_null;
+ }
+
+ dec = data_blob_talloc(ctx,
+ ps.cleartext.data,
+ ps.cleartext.length);
+ if (dec.data == NULL) {
+ TALLOC_FREE(frame);
+ ldb_set_errstring(ldb, "Out of memory, copying value\n");
+ *err = LDB_ERR_OPERATIONS_ERROR;
+ return data_blob_null;
+ }
+
+ TALLOC_FREE(frame);
+ return dec;
+}
+
+/*
+ * @brief Decrypt all the encrypted values on an ldb_message_element
+ *
+ * Returns a copy of the original attribute with all values decrypted by
+ * decrypt_value(), the original attribute is left intact.
+ *
+ * @param err Pointer to an error code, set to:
+ * LDB_SUCESS If the value was successfully encrypted
+ * LDB_ERR_OPERATIONS_ERROR If there was an error.
+ *
+ * @param ctx Talloc memory context the will own the memory allocated
+ * for the new ldb_message_element.
+ * @param ldb ldb context, to allow logging.
+ * @param el The ldb_message_elemen to decrypt, not altered or freed
+ * @param data The context data for this module.
+ *
+ * @return Pointer decrypted lsb_message_element, will be NULL if there was
+ * an error.
+ */
+static struct ldb_message_element *decrypt_element(
+ int *err,
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ struct ldb_message_element* el,
+ struct es_data *data)
+{
+ unsigned int i;
+ struct ldb_message_element* dec =
+ talloc_zero(ctx, struct ldb_message_element);
+
+ *err = LDB_SUCCESS;
+ if (dec == NULL) {
+ ldb_set_errstring(ldb,
+ "Out of memory, allocating "
+ "ldb_message_element\n");
+ *err = LDB_ERR_OPERATIONS_ERROR;
+ return NULL;
+ }
+ dec->num_values = el->num_values;
+
+ dec->values = talloc_array(dec, struct ldb_val, dec->num_values);
+ if (dec->values == NULL) {
+ TALLOC_FREE(dec);
+ ldb_set_errstring(ldb,
+ "Out of memory, allocating values array\n");
+ *err = LDB_ERR_OPERATIONS_ERROR;
+ return NULL;
+ }
+
+ dec->name = talloc_strdup(dec, el->name);
+ if (dec->name == NULL) {
+ TALLOC_FREE(dec);
+ ldb_set_errstring(ldb, "Out of memory, copying element name\n");
+ *err = LDB_ERR_OPERATIONS_ERROR;
+ return NULL;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ dec->values[i] =
+ decrypt_value(err,
+ el->values,
+ ldb,
+ el->values[i],
+ data);
+ if (*err != LDB_SUCCESS) {
+ TALLOC_FREE(dec);
+ return NULL;
+ }
+ }
+ return dec;
+}
+
+
+/*
+ * @brief Decrypt all the secret attributes on an ldb_message
+ *
+ * Decrypt all the secret attributes on an ldb_message. Any secret attributes
+ * are removed from message and decrypted copies of the attributes added.
+ * In the event of an error the contents of the message will be inconsistent.
+ *
+ * @param ldb ldb context, to allow logging.
+ * @param msg The ldb_message to have it's secret attributes encrypted.
+ * @param data The context data for this module.
+ *
+ * @returns ldb status code
+ * LDB_SUCESS If the value was successfully encrypted
+ * LDB_ERR_OPERATIONS_ERROR If there was an error.
+ */
+static int decrypt_secret_attributes(struct ldb_context *ldb,
+ struct ldb_message *msg,
+ struct es_data *data)
+{
+ size_t i;
+ int ret;
+
+ if (ldb_dn_is_special(msg->dn)) {
+ return LDB_SUCCESS;
+ }
+
+ for (i = 0; i < num_secret_attributes; i++) {
+ struct ldb_message_element *el =
+ ldb_msg_find_element(msg, secret_attributes[i]);
+ if (el != NULL) {
+ const int flags = el->flags;
+ struct ldb_message_element* dec =
+ decrypt_element(&ret,
+ msg->elements,
+ ldb,
+ el,
+ data);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ldb_msg_remove_element(msg, el);
+ ret = ldb_msg_add(msg, dec, flags);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+static int es_search_post_process(struct ldb_module *module,
+ struct ldb_message *msg)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct es_data *data =
+ talloc_get_type(ldb_module_get_private(module),
+ struct es_data);
+
+
+ /*
+ * Decrypt any encrypted secret attributes
+ */
+ if (data && data->encrypt_secrets) {
+ int err = decrypt_secret_attributes(ldb, msg, data);
+ if (err != LDB_SUCCESS) {
+ return err;
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+/*
+ hook search operations
+*/
+struct es_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+};
+
+static int es_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct es_context *ec;
+ int ret;
+
+
+ ec = talloc_get_type(req->context, struct es_context);
+
+ if (!ares) {
+ return ldb_module_done(ec->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ec->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ /*
+ * for each record returned decrypt any encrypted attributes
+ */
+ ret = es_search_post_process(ec->module, ares->message);
+ if (ret != 0) {
+ return ldb_module_done(ec->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ return ldb_module_send_entry(ec->req,
+ ares->message, ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(ec->req, ares->referral);
+
+ case LDB_REPLY_DONE:
+
+ return ldb_module_done(ec->req, ares->controls,
+ ares->response, LDB_SUCCESS);
+ }
+
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+static int es_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct es_context *ec;
+ struct ldb_request *down_req;
+ int ret;
+
+ /* There are no encrypted attributes on special DNs */
+ if (ldb_dn_is_special(req->op.search.base)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ ec = talloc(req, struct es_context);
+ if (ec == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ec->module = module;
+ ec->req = req;
+ ret = ldb_build_search_req_ex(&down_req,
+ ldb,
+ ec,
+ req->op.search.base,
+ req->op.search.scope,
+ req->op.search.tree,
+ req->op.search.attrs,
+ req->controls,
+ ec,
+ es_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ /* perform the search */
+ return ldb_next_request(module, down_req);
+}
+static int es_add(struct ldb_module *module, struct ldb_request *req)
+{
+
+ struct es_data *data =
+ talloc_get_type(ldb_module_get_private(module),
+ struct es_data);
+ const struct ldb_message *encrypted_msg = NULL;
+ struct ldb_context *ldb = NULL;
+ int rc = LDB_SUCCESS;
+
+ if (!data->encrypt_secrets) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+ encrypted_msg = encrypt_secret_attributes(&rc,
+ req,
+ ldb,
+ req->op.add.message,
+ data);
+ if (rc != LDB_SUCCESS) {
+ return rc;
+ }
+ /*
+ * If we did not encrypt any of the attributes
+ * continue on to the next module
+ */
+ if (encrypted_msg == NULL) {
+ return ldb_next_request(module, req);
+ }
+
+ /*
+ * Encrypted an attribute, now need to build a copy of the request
+ * so that we're not altering the original callers copy
+ */
+ {
+ struct ldb_request* new_req = NULL;
+ rc = ldb_build_add_req(&new_req,
+ ldb,
+ req,
+ encrypted_msg,
+ req->controls,
+ req,
+ dsdb_next_callback,
+ req);
+ if (rc != LDB_SUCCESS) {
+ return rc;
+ }
+ return ldb_next_request(module, new_req);
+ }
+}
+
+static int es_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct es_data *data =
+ talloc_get_type(ldb_module_get_private(module),
+ struct es_data);
+ const struct ldb_message *encrypted_msg = NULL;
+ struct ldb_context *ldb = NULL;
+ int rc = LDB_SUCCESS;
+
+ if (!data->encrypt_secrets) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+ encrypted_msg = encrypt_secret_attributes(&rc,
+ req,
+ ldb,
+ req->op.mod.message,
+ data);
+ if (rc != LDB_SUCCESS) {
+ return rc;
+ }
+ /*
+ * If we did not encrypt any of the attributes
+ * continue on to the next module
+ */
+ if (encrypted_msg == NULL) {
+ return ldb_next_request(module, req);
+ }
+
+
+ /*
+ * Encrypted an attribute, now need to build a copy of the request
+ * so that we're not altering the original callers copy
+ */
+ {
+ struct ldb_request* new_req = NULL;
+ rc = ldb_build_mod_req(&new_req,
+ ldb,
+ req,
+ encrypted_msg,
+ req->controls,
+ req,
+ dsdb_next_callback,
+ req);
+ if (rc != LDB_SUCCESS) {
+ return rc;
+ }
+ return ldb_next_request(module, new_req);
+ }
+}
+
+static int es_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ return ldb_next_request(module, req);
+}
+
+static int es_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ return ldb_next_request(module, req);
+}
+static int es_init(struct ldb_module *ctx)
+{
+ struct es_data *data;
+ int ret;
+
+ data = talloc_zero(ctx, struct es_data);
+ if (!data) {
+ return ldb_module_oom(ctx);
+ }
+
+ {
+ struct ldb_context *ldb = ldb_module_get_ctx(ctx);
+ struct ldb_dn *samba_dsdb_dn;
+ struct ldb_result *res;
+ static const char *samba_dsdb_attrs[] = {
+ SAMBA_REQUIRED_FEATURES_ATTR,
+ NULL
+ };
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ samba_dsdb_dn = ldb_dn_new(frame, ldb, "@SAMBA_DSDB");
+ if (!samba_dsdb_dn) {
+ TALLOC_FREE(frame);
+ return ldb_oom(ldb);
+ }
+ ret = dsdb_module_search_dn(ctx,
+ frame,
+ &res,
+ samba_dsdb_dn,
+ samba_dsdb_attrs,
+ DSDB_FLAG_NEXT_MODULE,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ data->encrypt_secrets =
+ ldb_msg_check_string_attribute(
+ res->msgs[0],
+ SAMBA_REQUIRED_FEATURES_ATTR,
+ SAMBA_ENCRYPTED_SECRETS_FEATURE);
+ if (data->encrypt_secrets) {
+ ret = load_keys(ctx, data);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ }
+ TALLOC_FREE(frame);
+ }
+ ldb_module_set_private(ctx, data);
+
+ ret = ldb_next_init(ctx);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return LDB_SUCCESS;
+}
+
+static const struct ldb_module_ops ldb_encrypted_secrets_module_ops = {
+ .name = "encrypted_secrets",
+ .search = es_search,
+ .add = es_add,
+ .modify = es_modify,
+ .del = es_delete,
+ .rename = es_rename,
+ .init_context = es_init
+};
+
+int ldb_encrypted_secrets_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_encrypted_secrets_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/extended_dn_in.c b/source4/dsdb/samdb/ldb_modules/extended_dn_in.c
new file mode 100644
index 0000000..248bb66
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/extended_dn_in.c
@@ -0,0 +1,801 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2005-2008
+ Copyright (C) Andrew 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 extended dn control module
+ *
+ * Description: this module interprets DNs of the form <SID=S-1-2-4456> into normal DNs.
+ *
+ * Authors: Simo Sorce
+ * Andrew Bartlett
+ */
+
+#include "includes.h"
+#include <ldb.h>
+#include <ldb_errors.h>
+#include <ldb_module.h>
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "lib/ldb-samba/ldb_matching_rules.h"
+
+#undef strncasecmp
+
+/*
+ TODO: if relax is not set then we need to reject the fancy RMD_* and
+ DELETED extended DN codes
+ */
+
+/* search */
+struct extended_search_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+ struct ldb_parse_tree *tree;
+ struct ldb_dn *basedn;
+ struct ldb_dn *dn;
+ char *wellknown_object;
+ int extended_type;
+};
+
+static const char *wkattr[] = {
+ "wellKnownObjects",
+ "otherWellKnownObjects",
+ NULL
+};
+
+static const struct ldb_module_ops ldb_extended_dn_in_openldap_module_ops;
+
+/* An extra layer of indirection because LDB does not allow the original request to be altered */
+
+static int extended_final_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ int ret = LDB_ERR_OPERATIONS_ERROR;
+ struct extended_search_context *ac;
+ ac = talloc_get_type(req->context, struct extended_search_context);
+
+ if (ares->error != LDB_SUCCESS) {
+ ret = ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ } else {
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+
+ ret = ldb_module_send_entry(ac->req, ares->message, ares->controls);
+ break;
+ case LDB_REPLY_REFERRAL:
+
+ ret = ldb_module_send_referral(ac->req, ares->referral);
+ break;
+ case LDB_REPLY_DONE:
+
+ ret = ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ break;
+ }
+ }
+ return ret;
+}
+
+static int extended_base_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct extended_search_context *ac;
+ struct ldb_request *down_req;
+ struct ldb_message_element *el;
+ int ret;
+ unsigned int i, j;
+ size_t wkn_len = 0;
+ char *valstr = NULL;
+ const char *found = NULL;
+
+ ac = talloc_get_type(req->context, struct extended_search_context);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ if (ac->basedn) {
+ /* we have more than one match! This can
+ happen as S-1-5-17 appears twice in a
+ normal provision. We need to return
+ NO_SUCH_OBJECT */
+ const char *str = talloc_asprintf(req, "Duplicate base-DN matches found for '%s'",
+ ldb_dn_get_extended_linearized(req, ac->dn, 1));
+ ldb_set_errstring(ldb_module_get_ctx(ac->module), str);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_NO_SUCH_OBJECT);
+ }
+
+ if (!ac->wellknown_object) {
+ ac->basedn = talloc_steal(ac, ares->message->dn);
+ break;
+ }
+
+ wkn_len = strlen(ac->wellknown_object);
+
+ for (j=0; wkattr[j]; j++) {
+
+ el = ldb_msg_find_element(ares->message, wkattr[j]);
+ if (!el) {
+ ac->basedn = NULL;
+ continue;
+ }
+
+ for (i=0; i < el->num_values; i++) {
+ valstr = talloc_strndup(ac,
+ (const char *)el->values[i].data,
+ el->values[i].length);
+ if (!valstr) {
+ ldb_oom(ldb_module_get_ctx(ac->module));
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (strncasecmp(valstr, ac->wellknown_object, wkn_len) != 0) {
+ talloc_free(valstr);
+ continue;
+ }
+
+ found = &valstr[wkn_len];
+ break;
+ }
+ if (found) {
+ break;
+ }
+ }
+
+ if (!found) {
+ break;
+ }
+
+ ac->basedn = ldb_dn_new(ac, ldb_module_get_ctx(ac->module), found);
+ talloc_free(valstr);
+ if (!ac->basedn) {
+ ldb_oom(ldb_module_get_ctx(ac->module));
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ break;
+
+ case LDB_REPLY_DONE:
+
+ if (!ac->basedn) {
+ const char *str = talloc_asprintf(req, "Base-DN '%s' not found",
+ ldb_dn_get_extended_linearized(req, ac->dn, 1));
+ ldb_set_errstring(ldb_module_get_ctx(ac->module), str);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_NO_SUCH_OBJECT);
+ }
+
+ switch (ac->req->operation) {
+ case LDB_SEARCH:
+ ret = ldb_build_search_req_ex(&down_req,
+ ldb_module_get_ctx(ac->module), ac->req,
+ ac->basedn,
+ ac->req->op.search.scope,
+ ac->tree,
+ ac->req->op.search.attrs,
+ ac->req->controls,
+ ac, extended_final_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(down_req);
+ break;
+ case LDB_ADD:
+ {
+ struct ldb_message *add_msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message);
+ if (!add_msg) {
+ ldb_oom(ldb_module_get_ctx(ac->module));
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ add_msg->dn = ac->basedn;
+
+ ret = ldb_build_add_req(&down_req,
+ ldb_module_get_ctx(ac->module), ac->req,
+ add_msg,
+ ac->req->controls,
+ ac, extended_final_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(down_req);
+ break;
+ }
+ case LDB_MODIFY:
+ {
+ struct ldb_message *mod_msg = ldb_msg_copy_shallow(ac, ac->req->op.mod.message);
+ if (!mod_msg) {
+ ldb_oom(ldb_module_get_ctx(ac->module));
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ mod_msg->dn = ac->basedn;
+
+ ret = ldb_build_mod_req(&down_req,
+ ldb_module_get_ctx(ac->module), ac->req,
+ mod_msg,
+ ac->req->controls,
+ ac, extended_final_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(down_req);
+ break;
+ }
+ case LDB_DELETE:
+ ret = ldb_build_del_req(&down_req,
+ ldb_module_get_ctx(ac->module), ac->req,
+ ac->basedn,
+ ac->req->controls,
+ ac, extended_final_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(down_req);
+ break;
+ case LDB_RENAME:
+ ret = ldb_build_rename_req(&down_req,
+ ldb_module_get_ctx(ac->module), ac->req,
+ ac->basedn,
+ ac->req->op.rename.newdn,
+ ac->req->controls,
+ ac, extended_final_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(down_req);
+ break;
+ default:
+ return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ return ldb_next_request(ac->module, down_req);
+ }
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+
+/*
+ windows ldap searchs don't allow a baseDN with more
+ than one extended component, or an extended
+ component and a string DN
+
+ We only enforce this over ldap, not for internal
+ use, as there are just too many places where we
+ internally want to use a DN that has come from a
+ search with extended DN enabled, or comes from a DRS
+ naming context.
+
+ Enforcing this would also make debugging samba much
+ harder, as we'd need to use ldb_dn_minimise() in a
+ lot of places, and that would lose the DN string
+ which is so useful for working out what a request is
+ for
+*/
+static bool ldb_dn_match_allowed(struct ldb_dn *dn, struct ldb_request *req)
+{
+ int num_components = ldb_dn_get_comp_num(dn);
+ int num_ex_components = ldb_dn_get_extended_comp_num(dn);
+
+ if (num_ex_components == 0) {
+ return true;
+ }
+
+ if ((num_components != 0 || num_ex_components != 1) &&
+ ldb_req_is_untrusted(req)) {
+ return false;
+ }
+ return true;
+}
+
+
+struct extended_dn_filter_ctx {
+ bool test_only;
+ bool matched;
+ struct ldb_module *module;
+ struct ldb_request *req;
+ struct dsdb_schema *schema;
+ uint32_t dsdb_flags;
+};
+
+/*
+ create a always non-matching node from a equality node
+ */
+static void set_parse_tree_false(struct ldb_parse_tree *tree)
+{
+ const char *attr = tree->u.equality.attr;
+ struct ldb_val value = tree->u.equality.value;
+ tree->operation = LDB_OP_EXTENDED;
+ tree->u.extended.attr = attr;
+ tree->u.extended.value = value;
+ tree->u.extended.rule_id = SAMBA_LDAP_MATCH_ALWAYS_FALSE;
+ tree->u.extended.dnAttributes = 0;
+}
+
+/*
+ called on all nodes in the parse tree
+ */
+static int extended_dn_filter_callback(struct ldb_parse_tree *tree, void *private_context)
+{
+ struct extended_dn_filter_ctx *filter_ctx;
+ int ret;
+ struct ldb_dn *dn = NULL;
+ const struct ldb_val *sid_val, *guid_val;
+ const char *no_attrs[] = { NULL };
+ struct ldb_result *res;
+ const struct dsdb_attribute *attribute = NULL;
+ bool has_extended_component = false;
+ enum ldb_scope scope;
+ struct ldb_dn *base_dn;
+ const char *expression;
+ uint32_t dsdb_flags;
+
+ if (tree->operation != LDB_OP_EQUALITY && tree->operation != LDB_OP_EXTENDED) {
+ return LDB_SUCCESS;
+ }
+
+ filter_ctx = talloc_get_type_abort(private_context, struct extended_dn_filter_ctx);
+
+ if (filter_ctx->test_only && filter_ctx->matched) {
+ /* the tree already matched */
+ return LDB_SUCCESS;
+ }
+
+ if (!filter_ctx->schema) {
+ /* Schema not setup yet */
+ return LDB_SUCCESS;
+ }
+ if (tree->operation == LDB_OP_EQUALITY) {
+ attribute = dsdb_attribute_by_lDAPDisplayName(filter_ctx->schema, tree->u.equality.attr);
+ } else if (tree->operation == LDB_OP_EXTENDED) {
+ attribute = dsdb_attribute_by_lDAPDisplayName(filter_ctx->schema, tree->u.extended.attr);
+ }
+ if (attribute == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ if (attribute->dn_format != DSDB_NORMAL_DN) {
+ return LDB_SUCCESS;
+ }
+
+ if (tree->operation == LDB_OP_EQUALITY) {
+ has_extended_component = (memchr(tree->u.equality.value.data, '<',
+ tree->u.equality.value.length) != NULL);
+ } else if (tree->operation == LDB_OP_EXTENDED) {
+ has_extended_component = (memchr(tree->u.extended.value.data, '<',
+ tree->u.extended.value.length) != NULL);
+ }
+
+ /*
+ * Don't turn it into an extended DN if we're talking to OpenLDAP.
+ * We just check the module_ops pointer instead of adding a private
+ * pointer and a boolean to tell us the exact same thing.
+ */
+ if (!has_extended_component) {
+ if (!attribute->one_way_link) {
+ return LDB_SUCCESS;
+ }
+
+ if (ldb_module_get_ops(filter_ctx->module) == &ldb_extended_dn_in_openldap_module_ops) {
+ return LDB_SUCCESS;
+ }
+ }
+
+ if (tree->operation == LDB_OP_EQUALITY) {
+ dn = ldb_dn_from_ldb_val(filter_ctx, ldb_module_get_ctx(filter_ctx->module), &tree->u.equality.value);
+ } else if (tree->operation == LDB_OP_EXTENDED
+ && (strcmp(tree->u.extended.rule_id, SAMBA_LDAP_MATCH_RULE_TRANSITIVE_EVAL) == 0)) {
+ dn = ldb_dn_from_ldb_val(filter_ctx, ldb_module_get_ctx(filter_ctx->module), &tree->u.extended.value);
+ }
+ if (dn == NULL) {
+ /* testing against windows shows that we don't raise
+ an error here */
+ return LDB_SUCCESS;
+ }
+
+ guid_val = ldb_dn_get_extended_component(dn, "GUID");
+ sid_val = ldb_dn_get_extended_component(dn, "SID");
+
+ /*
+ * Is the attribute indexed? By treating confidential attributes
+ * as unindexed, we force searches to go through the unindexed
+ * search path, avoiding observable timing differences.
+ */
+ if (!guid_val && !sid_val &&
+ (attribute->searchFlags & SEARCH_FLAG_ATTINDEX) &&
+ !(attribute->searchFlags & SEARCH_FLAG_CONFIDENTIAL))
+ {
+ /* if it is indexed, then fixing the string DN will do
+ no good here, as we will not find the attribute in
+ the index. So for now fall through to a standard DN
+ component comparison */
+ return LDB_SUCCESS;
+ }
+
+ if (filter_ctx->test_only) {
+ /* we need to copy the tree */
+ filter_ctx->matched = true;
+ return LDB_SUCCESS;
+ }
+
+ if (!ldb_dn_match_allowed(dn, filter_ctx->req)) {
+ /* we need to make this element of the filter always
+ be false */
+ set_parse_tree_false(tree);
+ return LDB_SUCCESS;
+ }
+
+ dsdb_flags = filter_ctx->dsdb_flags | DSDB_FLAG_NEXT_MODULE;
+
+ if (guid_val) {
+ expression = talloc_asprintf(filter_ctx, "objectGUID=%s", ldb_binary_encode(filter_ctx, *guid_val));
+ scope = LDB_SCOPE_SUBTREE;
+ base_dn = NULL;
+ dsdb_flags |= DSDB_SEARCH_SEARCH_ALL_PARTITIONS;
+ } else if (sid_val) {
+ expression = talloc_asprintf(filter_ctx, "objectSID=%s", ldb_binary_encode(filter_ctx, *sid_val));
+ scope = LDB_SCOPE_SUBTREE;
+ base_dn = NULL;
+ dsdb_flags |= DSDB_SEARCH_SEARCH_ALL_PARTITIONS;
+ } else {
+ /* fallback to searching using the string DN as the base DN */
+ expression = "objectClass=*";
+ base_dn = dn;
+ scope = LDB_SCOPE_BASE;
+ }
+
+ ret = dsdb_module_search(filter_ctx->module,
+ filter_ctx,
+ &res,
+ base_dn,
+ scope,
+ no_attrs,
+ dsdb_flags,
+ filter_ctx->req,
+ "%s", expression);
+ if (scope == LDB_SCOPE_BASE && ret == LDB_ERR_NO_SUCH_OBJECT) {
+ /* note that this will need to change for multi-domain
+ support */
+ set_parse_tree_false(tree);
+ return LDB_SUCCESS;
+ }
+
+ if (ret != LDB_SUCCESS) {
+ return LDB_SUCCESS;
+ }
+
+
+ if (res->count != 1) {
+ return LDB_SUCCESS;
+ }
+
+ /* replace the search expression element with the matching DN */
+ if (tree->operation == LDB_OP_EQUALITY) {
+ tree->u.equality.value.data =
+ (uint8_t *)talloc_strdup(tree, ldb_dn_get_extended_linearized(tree, res->msgs[0]->dn, 1));
+ if (tree->u.equality.value.data == NULL) {
+ return ldb_oom(ldb_module_get_ctx(filter_ctx->module));
+ }
+ tree->u.equality.value.length = strlen((const char *)tree->u.equality.value.data);
+ } else if (tree->operation == LDB_OP_EXTENDED) {
+ tree->u.extended.value.data =
+ (uint8_t *)talloc_strdup(tree, ldb_dn_get_extended_linearized(tree, res->msgs[0]->dn, 1));
+ if (tree->u.extended.value.data == NULL) {
+ return ldb_oom(ldb_module_get_ctx(filter_ctx->module));
+ }
+ tree->u.extended.value.length = strlen((const char *)tree->u.extended.value.data);
+ }
+ talloc_free(res);
+
+ filter_ctx->matched = true;
+ return LDB_SUCCESS;
+}
+
+/*
+ fix the parse tree to change any extended DN components to their
+ canonical form
+ */
+static int extended_dn_fix_filter(struct ldb_module *module,
+ struct ldb_request *req,
+ uint32_t default_dsdb_flags,
+ struct ldb_parse_tree **down_tree)
+{
+ struct extended_dn_filter_ctx *filter_ctx;
+ int ret;
+
+ *down_tree = NULL;
+
+ filter_ctx = talloc_zero(req, struct extended_dn_filter_ctx);
+ if (filter_ctx == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ /* first pass through the existing tree to see if anything
+ needs to be modified. Filtering DNs on the input side is rare,
+ so this avoids copying the parse tree in most cases */
+ filter_ctx->test_only = true;
+ filter_ctx->matched = false;
+ filter_ctx->module = module;
+ filter_ctx->req = req;
+ filter_ctx->schema = dsdb_get_schema(ldb_module_get_ctx(module), filter_ctx);
+ filter_ctx->dsdb_flags= default_dsdb_flags;
+
+ ret = ldb_parse_tree_walk(req->op.search.tree, extended_dn_filter_callback, filter_ctx);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(filter_ctx);
+ return ret;
+ }
+
+ if (!filter_ctx->matched) {
+ /* nothing matched, no need for a new parse tree */
+ talloc_free(filter_ctx);
+ return LDB_SUCCESS;
+ }
+
+ filter_ctx->test_only = false;
+ filter_ctx->matched = false;
+
+ *down_tree = ldb_parse_tree_copy_shallow(req, req->op.search.tree);
+ if (*down_tree == NULL) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ ret = ldb_parse_tree_walk(*down_tree, extended_dn_filter_callback, filter_ctx);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(filter_ctx);
+ return ret;
+ }
+
+ talloc_free(filter_ctx);
+ return LDB_SUCCESS;
+}
+
+/*
+ fix DNs and filter expressions to cope with the semantics of
+ extended DNs
+ */
+static int extended_dn_in_fix(struct ldb_module *module, struct ldb_request *req, struct ldb_dn *dn)
+{
+ struct extended_search_context *ac;
+ struct ldb_request *down_req = NULL;
+ struct ldb_parse_tree *down_tree = NULL;
+ int ret;
+ struct ldb_dn *base_dn = NULL;
+ enum ldb_scope base_dn_scope = LDB_SCOPE_BASE;
+ const char *base_dn_filter = NULL;
+ const char * const *base_dn_attrs = NULL;
+ char *wellknown_object = NULL;
+ static const char *no_attr[] = {
+ NULL
+ };
+ uint32_t dsdb_flags = DSDB_FLAG_AS_SYSTEM | DSDB_SEARCH_SHOW_EXTENDED_DN;
+
+ if (ldb_request_get_control(req, LDB_CONTROL_SHOW_DELETED_OID)) {
+ dsdb_flags |= DSDB_SEARCH_SHOW_DELETED;
+ }
+ if (ldb_request_get_control(req, LDB_CONTROL_SHOW_RECYCLED_OID)) {
+ dsdb_flags |= DSDB_SEARCH_SHOW_RECYCLED;
+ }
+ if (ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) {
+ dsdb_flags |= DSDB_SEARCH_SHOW_RECYCLED;
+ }
+
+ if (req->operation == LDB_SEARCH) {
+ ret = extended_dn_fix_filter(module, req, dsdb_flags, &down_tree);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (!ldb_dn_has_extended(dn)) {
+ /* Move along there isn't anything to see here */
+ if (down_tree == NULL) {
+ down_req = req;
+ } else {
+ ret = ldb_build_search_req_ex(&down_req,
+ ldb_module_get_ctx(module), req,
+ req->op.search.base,
+ req->op.search.scope,
+ down_tree,
+ req->op.search.attrs,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ LDB_REQ_SET_LOCATION(down_req);
+ }
+
+ return ldb_next_request(module, down_req);
+ } else {
+ /* It looks like we need to map the DN */
+ const struct ldb_val *sid_val, *guid_val, *wkguid_val;
+
+ if (!ldb_dn_match_allowed(dn, req)) {
+ return ldb_error(ldb_module_get_ctx(module),
+ LDB_ERR_INVALID_DN_SYNTAX, "invalid number of DN components");
+ }
+
+ sid_val = ldb_dn_get_extended_component(dn, "SID");
+ guid_val = ldb_dn_get_extended_component(dn, "GUID");
+ wkguid_val = ldb_dn_get_extended_component(dn, "WKGUID");
+
+ /*
+ prioritise the GUID - we have had instances of
+ duplicate SIDs in the database in the
+ ForeignSecurityPrinciples due to provision errors
+ */
+ if (guid_val) {
+ dsdb_flags |= DSDB_SEARCH_SEARCH_ALL_PARTITIONS;
+ base_dn = NULL;
+ base_dn_filter = talloc_asprintf(req, "(objectGUID=%s)",
+ ldb_binary_encode(req, *guid_val));
+ if (!base_dn_filter) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+ base_dn_scope = LDB_SCOPE_SUBTREE;
+ base_dn_attrs = no_attr;
+
+ } else if (sid_val) {
+ dsdb_flags |= DSDB_SEARCH_SEARCH_ALL_PARTITIONS;
+ base_dn = NULL;
+ base_dn_filter = talloc_asprintf(req, "(objectSid=%s)",
+ ldb_binary_encode(req, *sid_val));
+ if (!base_dn_filter) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+ base_dn_scope = LDB_SCOPE_SUBTREE;
+ base_dn_attrs = no_attr;
+
+ } else if (wkguid_val) {
+ char *wkguid_dup;
+ char *tail_str;
+ char *p;
+
+ wkguid_dup = talloc_strndup(req, (char *)wkguid_val->data, wkguid_val->length);
+
+ p = strchr(wkguid_dup, ',');
+ if (!p) {
+ return ldb_error(ldb_module_get_ctx(module), LDB_ERR_INVALID_DN_SYNTAX,
+ "Invalid WKGUID format");
+ }
+
+ p[0] = '\0';
+ p++;
+
+ wellknown_object = talloc_asprintf(req, "B:32:%s:", wkguid_dup);
+ if (!wellknown_object) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ tail_str = p;
+
+ base_dn = ldb_dn_new(req, ldb_module_get_ctx(module), tail_str);
+ talloc_free(wkguid_dup);
+ if (!base_dn) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+ base_dn_filter = talloc_strdup(req, "(objectClass=*)");
+ if (!base_dn_filter) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+ base_dn_scope = LDB_SCOPE_BASE;
+ base_dn_attrs = wkattr;
+ } else {
+ return ldb_error(ldb_module_get_ctx(module), LDB_ERR_INVALID_DN_SYNTAX,
+ "Invalid extended DN component");
+ }
+
+ ac = talloc_zero(req, struct extended_search_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ ac->module = module;
+ ac->req = req;
+ ac->tree = (down_tree != NULL) ? down_tree : req->op.search.tree;
+ ac->dn = dn;
+ ac->basedn = NULL; /* Filled in if the search finds the DN by SID/GUID etc */
+ ac->wellknown_object = wellknown_object;
+
+ /* If the base DN was an extended DN (perhaps a well known
+ * GUID) then search for that, so we can proceed with the original operation */
+
+ ret = ldb_build_search_req(&down_req,
+ ldb_module_get_ctx(module), ac,
+ base_dn,
+ base_dn_scope,
+ base_dn_filter,
+ base_dn_attrs,
+ NULL,
+ ac, extended_base_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ ret = dsdb_request_add_controls(down_req, dsdb_flags);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* perform the search */
+ return ldb_next_request(module, down_req);
+ }
+}
+
+static int extended_dn_in_search(struct ldb_module *module, struct ldb_request *req)
+{
+ return extended_dn_in_fix(module, req, req->op.search.base);
+}
+
+static int extended_dn_in_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ return extended_dn_in_fix(module, req, req->op.mod.message->dn);
+}
+
+static int extended_dn_in_del(struct ldb_module *module, struct ldb_request *req)
+{
+ return extended_dn_in_fix(module, req, req->op.del.dn);
+}
+
+static int extended_dn_in_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ return extended_dn_in_fix(module, req, req->op.rename.olddn);
+}
+
+static const struct ldb_module_ops ldb_extended_dn_in_module_ops = {
+ .name = "extended_dn_in",
+ .search = extended_dn_in_search,
+ .modify = extended_dn_in_modify,
+ .del = extended_dn_in_del,
+ .rename = extended_dn_in_rename,
+};
+
+static const struct ldb_module_ops ldb_extended_dn_in_openldap_module_ops = {
+ .name = "extended_dn_in_openldap",
+ .search = extended_dn_in_search,
+ .modify = extended_dn_in_modify,
+ .del = extended_dn_in_del,
+ .rename = extended_dn_in_rename,
+};
+
+int ldb_extended_dn_in_module_init(const char *version)
+{
+ int ret;
+ LDB_MODULE_CHECK_VERSION(version);
+ ret = ldb_register_module(&ldb_extended_dn_in_openldap_module_ops);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_register_module(&ldb_extended_dn_in_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/extended_dn_out.c b/source4/dsdb/samdb/ldb_modules/extended_dn_out.c
new file mode 100644
index 0000000..53cbe34
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/extended_dn_out.c
@@ -0,0 +1,648 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2005-2008
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007-2009
+
+ 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 extended dn control module
+ *
+ * Description: this module builds a special dn for returned search
+ * results, and fixes some other aspects of the result (returned case issues)
+ * values.
+ *
+ * Authors: Simo Sorce
+ * Andrew Bartlett
+ */
+
+#include "includes.h"
+#include <ldb.h>
+#include <ldb_errors.h>
+#include <ldb_module.h>
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "librpc/ndr/libndr.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+#undef strcasecmp
+#undef strncasecmp
+
+struct extended_dn_out_private {
+ bool dereference;
+ bool normalise;
+ const char **attrs;
+};
+
+static char **copy_attrs(void *mem_ctx, const char * const * attrs)
+{
+ char **nattrs;
+ unsigned int i, num;
+
+ for (num = 0; attrs[num]; num++);
+
+ nattrs = talloc_array(mem_ctx, char *, num + 1);
+ if (!nattrs) return NULL;
+
+ for(i = 0; i < num; i++) {
+ nattrs[i] = talloc_strdup(nattrs, attrs[i]);
+ if (!nattrs[i]) {
+ talloc_free(nattrs);
+ return NULL;
+ }
+ }
+ nattrs[i] = NULL;
+
+ return nattrs;
+}
+
+static bool add_attrs(void *mem_ctx, char ***attrs, const char *attr)
+{
+ char **nattrs;
+ unsigned int num;
+
+ for (num = 0; (*attrs)[num]; num++);
+
+ nattrs = talloc_realloc(mem_ctx, *attrs, char *, num + 2);
+ if (!nattrs) return false;
+
+ *attrs = nattrs;
+
+ nattrs[num] = talloc_strdup(nattrs, attr);
+ if (!nattrs[num]) return false;
+
+ nattrs[num + 1] = NULL;
+
+ return true;
+}
+
+/* Inject the extended DN components, so the DN cn=Adminstrator,cn=users,dc=samba,dc=example,dc=com becomes
+ <GUID=541203ae-f7d6-47ef-8390-bfcf019f9583>;<SID=S-1-5-21-4177067393-1453636373-93818737-500>;cn=Adminstrator,cn=users,dc=samba,dc=example,dc=com */
+
+static int inject_extended_dn_out(struct ldb_reply *ares,
+ struct ldb_context *ldb,
+ int type,
+ bool remove_guid,
+ bool remove_sid)
+{
+ int ret;
+ const DATA_BLOB *guid_blob;
+ const DATA_BLOB *sid_blob;
+
+ guid_blob = ldb_msg_find_ldb_val(ares->message, "objectGUID");
+ sid_blob = ldb_msg_find_ldb_val(ares->message, "objectSid");
+
+ if (!guid_blob) {
+ ldb_set_errstring(ldb, "Did not find objectGUID to inject into extended DN");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_dn_set_extended_component(ares->message->dn, "GUID", guid_blob);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (sid_blob) {
+ ret = ldb_dn_set_extended_component(ares->message->dn, "SID", sid_blob);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (remove_guid) {
+ ldb_msg_remove_attr(ares->message, "objectGUID");
+ }
+
+ if (sid_blob && remove_sid) {
+ ldb_msg_remove_attr(ares->message, "objectSid");
+ }
+
+ return LDB_SUCCESS;
+}
+
+/* search */
+struct extended_search_context {
+ struct ldb_module *module;
+ const struct dsdb_schema *schema;
+ struct ldb_request *req;
+ bool inject;
+ bool remove_guid;
+ bool remove_sid;
+ int extended_type;
+};
+
+
+/*
+ fix one-way links to have the right string DN, to cope with
+ renames of the target
+*/
+static int fix_one_way_link(struct extended_search_context *ac, struct ldb_dn *dn,
+ bool is_deleted_objects, bool *remove_value,
+ uint32_t linkID)
+{
+ struct GUID guid;
+ NTSTATUS status;
+ int ret;
+ struct ldb_dn *real_dn;
+ uint32_t search_flags;
+ TALLOC_CTX *tmp_ctx = talloc_new(ac);
+ const char *attrs[] = { NULL };
+ struct ldb_result *res;
+
+ (*remove_value) = false;
+
+ status = dsdb_get_extended_dn_guid(dn, &guid, "GUID");
+ if (!NT_STATUS_IS_OK(status)) {
+ /* this is a strange DN that doesn't have a GUID! just
+ return the current DN string?? */
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ search_flags = DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SEARCH_ALL_PARTITIONS | DSDB_SEARCH_ONE_ONLY;
+
+ if (linkID == 0) {
+ /* You must ALWAYS show one-way links regardless of the state of the target */
+ search_flags |= (DSDB_SEARCH_SHOW_DELETED | DSDB_SEARCH_SHOW_RECYCLED);
+ }
+
+ ret = dsdb_module_search(ac->module, tmp_ctx, &res, NULL, LDB_SCOPE_SUBTREE, attrs,
+ search_flags, ac->req, "objectguid=%s", GUID_string(tmp_ctx, &guid));
+ if (ret != LDB_SUCCESS || res->count != 1) {
+ /* if we can't resolve this GUID, then we don't
+ display the link. This could be a link to a NC that we don't
+ have, or it could be a link to a deleted object
+ */
+ (*remove_value) = true;
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+ real_dn = res->msgs[0]->dn;
+
+ if (strcmp(ldb_dn_get_linearized(dn), ldb_dn_get_linearized(real_dn)) == 0) {
+ /* its already correct */
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ /* fix the DN by replacing its components with those from the
+ * real DN
+ */
+ if (!ldb_dn_replace_components(dn, real_dn)) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb_module_get_ctx(ac->module));
+ }
+ talloc_free(tmp_ctx);
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ this is called to post-process the results from the search
+ */
+static int extended_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct extended_search_context *ac;
+ int ret;
+ unsigned int i, j, k;
+ struct ldb_message *msg;
+ struct extended_dn_out_private *p;
+ struct ldb_context *ldb;
+ bool have_reveal_control=false;
+
+ ac = talloc_get_type(req->context, struct extended_search_context);
+ p = talloc_get_type(ldb_module_get_private(ac->module), struct extended_dn_out_private);
+ ldb = ldb_module_get_ctx(ac->module);
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ msg = ares->message;
+
+ switch (ares->type) {
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(ac->req, ares->referral);
+
+ case LDB_REPLY_DONE:
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, LDB_SUCCESS);
+ case LDB_REPLY_ENTRY:
+ break;
+ }
+
+ if (p && p->normalise) {
+ ret = dsdb_fix_dn_rdncase(ldb, ares->message->dn);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ if (ac->inject) {
+ /* for each record returned post-process to add any derived
+ attributes that have been asked for */
+ ret = inject_extended_dn_out(ares, ldb,
+ ac->extended_type, ac->remove_guid,
+ ac->remove_sid);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ if ((p && p->normalise) || ac->inject) {
+ const struct ldb_val *val = ldb_msg_find_ldb_val(ares->message, "distinguishedName");
+ if (val) {
+ ldb_msg_remove_attr(ares->message, "distinguishedName");
+ if (ac->inject) {
+ ret = ldb_msg_add_steal_string(ares->message, "distinguishedName",
+ ldb_dn_get_extended_linearized(ares->message, ares->message->dn, ac->extended_type));
+ } else {
+ ret = ldb_msg_add_linearized_dn(ares->message,
+ "distinguishedName",
+ ares->message->dn);
+ }
+ if (ret != LDB_SUCCESS) {
+ return ldb_oom(ldb);
+ }
+ }
+ }
+
+ have_reveal_control =
+ dsdb_request_has_control(req, LDB_CONTROL_REVEAL_INTERNALS);
+
+ /*
+ * Shortcut for repl_meta_data. We asked for the data
+ * 'as-is', so stop processing here!
+ */
+ if (have_reveal_control && p->normalise == false && ac->inject == true) {
+ return ldb_module_send_entry(ac->req, msg, ares->controls);
+ }
+
+ /* Walk the returned elements (but only if we have a schema to
+ * interpret the list with) */
+ for (i = 0; ac->schema && i < msg->num_elements; i++) {
+ bool make_extended_dn;
+ const struct dsdb_attribute *attribute;
+
+ attribute = dsdb_attribute_by_lDAPDisplayName(ac->schema, msg->elements[i].name);
+ if (!attribute) {
+ continue;
+ }
+
+ if (p && p->normalise) {
+ /* If we are also in 'normalise' mode, then
+ * fix the attribute names to be in the
+ * correct case */
+ msg->elements[i].name = talloc_strdup(msg->elements, attribute->lDAPDisplayName);
+ if (!msg->elements[i].name) {
+ ldb_oom(ldb);
+ return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+
+ /* distinguishedName has been dealt with above */
+ if (ldb_attr_cmp(msg->elements[i].name, "distinguishedName") == 0) {
+ continue;
+ }
+
+ /* Look to see if this attributeSyntax is a DN */
+ if (attribute->dn_format == DSDB_INVALID_DN) {
+ continue;
+ }
+
+ make_extended_dn = ac->inject;
+
+ /* Always show plain DN in case of Object(OR-Name) syntax */
+ if (make_extended_dn) {
+ make_extended_dn = (strcmp(attribute->syntax->ldap_oid, DSDB_SYNTAX_OR_NAME) != 0);
+ }
+
+ for (k = 0, j = 0; j < msg->elements[i].num_values; j++) {
+ const char *dn_str;
+ struct ldb_dn *dn;
+ struct dsdb_dn *dsdb_dn = NULL;
+ struct ldb_val *plain_dn = &msg->elements[i].values[j];
+ bool is_deleted_objects = false;
+
+ /* this is a fast method for detecting deleted
+ linked attributes, working on the unparsed
+ ldb_val */
+ if (dsdb_dn_is_deleted_val(plain_dn) && !have_reveal_control) {
+ /* it's a deleted linked attribute,
+ and we don't have the reveal control */
+ /* we won't keep this one, so not incrementing k */
+ continue;
+ }
+
+
+ dsdb_dn = dsdb_dn_parse_trusted(msg, ldb, plain_dn, attribute->syntax->ldap_oid);
+
+ if (!dsdb_dn) {
+ ldb_asprintf_errstring(ldb,
+ "could not parse %.*s in %s on %s as a %s DN",
+ (int)plain_dn->length, plain_dn->data,
+ msg->elements[i].name, ldb_dn_get_linearized(msg->dn),
+ attribute->syntax->ldap_oid);
+ talloc_free(dsdb_dn);
+ return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_INVALID_DN_SYNTAX);
+ }
+ dn = dsdb_dn->dn;
+
+ /* we need to know if this is a link to the
+ deleted objects container for fixing one way
+ links */
+ if (dsdb_dn->extra_part.length == 16) {
+ char *hex_string = data_blob_hex_string_upper(req, &dsdb_dn->extra_part);
+ if (hex_string && strcmp(hex_string, DS_GUID_DELETED_OBJECTS_CONTAINER) == 0) {
+ is_deleted_objects = true;
+ }
+ talloc_free(hex_string);
+ }
+
+ if (p->normalise) {
+ ret = dsdb_fix_dn_rdncase(ldb, dn);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(dsdb_dn);
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ /* Look for this value in the attribute */
+
+ /* note that we don't fixup objectCategory as
+ it should not be possible to move
+ objectCategory elements in the schema */
+ if (attribute->one_way_link &&
+ strcasecmp(attribute->lDAPDisplayName, "objectCategory") != 0) {
+ bool remove_value;
+ ret = fix_one_way_link(ac, dn, is_deleted_objects, &remove_value,
+ attribute->linkID);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(dsdb_dn);
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ if (remove_value &&
+ !ldb_request_get_control(req, LDB_CONTROL_REVEAL_INTERNALS)) {
+ /* we show these with REVEAL
+ to allow dbcheck to find and
+ cleanup these orphaned links */
+ /* we won't keep this one, so not incrementing k */
+ continue;
+ }
+ }
+
+ if (make_extended_dn) {
+ if (!ldb_dn_validate(dsdb_dn->dn)) {
+ ldb_asprintf_errstring(ldb,
+ "could not parse %.*s in %s on %s as a %s DN",
+ (int)plain_dn->length, plain_dn->data,
+ msg->elements[i].name, ldb_dn_get_linearized(msg->dn),
+ attribute->syntax->ldap_oid);
+ talloc_free(dsdb_dn);
+ return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_INVALID_DN_SYNTAX);
+ }
+ /* don't let users see the internal extended
+ GUID components */
+ if (!have_reveal_control) {
+ const char *accept[] = { "GUID", "SID", NULL };
+ ldb_dn_extended_filter(dn, accept);
+ }
+ dn_str = dsdb_dn_get_extended_linearized(msg->elements[i].values,
+ dsdb_dn, ac->extended_type);
+ } else {
+ dn_str = dsdb_dn_get_linearized(msg->elements[i].values,
+ dsdb_dn);
+ }
+
+ if (!dn_str) {
+ ldb_oom(ldb);
+ talloc_free(dsdb_dn);
+ return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+ msg->elements[i].values[k] = data_blob_string_const(dn_str);
+ talloc_free(dsdb_dn);
+ k++;
+ }
+
+ if (k == 0) {
+ /* we've deleted all of the values from this
+ * element - remove the element */
+ ldb_msg_remove_element(msg, &msg->elements[i]);
+ i--;
+ } else {
+ msg->elements[i].num_values = k;
+ }
+ }
+ return ldb_module_send_entry(ac->req, msg, ares->controls);
+}
+
+static int extended_callback_ldb(struct ldb_request *req, struct ldb_reply *ares)
+{
+ return extended_callback(req, ares);
+}
+
+static int extended_dn_out_search(struct ldb_module *module, struct ldb_request *req,
+ int (*callback)(struct ldb_request *req, struct ldb_reply *ares))
+{
+ struct ldb_control *control;
+ struct ldb_control *storage_format_control;
+ struct ldb_extended_dn_control *extended_ctrl = NULL;
+ struct extended_search_context *ac;
+ struct ldb_request *down_req;
+ char **new_attrs;
+ const char * const *const_attrs;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret;
+
+ struct extended_dn_out_private *p = talloc_get_type(ldb_module_get_private(module), struct extended_dn_out_private);
+
+ /* The schema manipulation does not apply to special DNs */
+ if (ldb_dn_is_special(req->op.search.base)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* check if there's an extended dn control */
+ control = ldb_request_get_control(req, LDB_CONTROL_EXTENDED_DN_OID);
+ if (control && control->data) {
+ extended_ctrl = talloc_get_type(control->data, struct ldb_extended_dn_control);
+ if (!extended_ctrl) {
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+ }
+
+ /* Look to see if, as we are in 'store DN+GUID+SID' mode, the
+ * client is after the storage format (to fill in linked
+ * attributes) */
+ storage_format_control = ldb_request_get_control(req, DSDB_CONTROL_DN_STORAGE_FORMAT_OID);
+ if (!control && storage_format_control && storage_format_control->data) {
+ extended_ctrl = talloc_get_type(storage_format_control->data, struct ldb_extended_dn_control);
+ if (!extended_ctrl) {
+ ldb_set_errstring(ldb, "extended_dn_out: extended_ctrl was of the wrong data type");
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+ }
+
+ ac = talloc_zero(req, struct extended_search_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ac->module = module;
+ ac->schema = dsdb_get_schema(ldb, ac);
+ ac->req = req;
+ ac->inject = false;
+ ac->remove_guid = false;
+ ac->remove_sid = false;
+
+ const_attrs = req->op.search.attrs;
+
+ /* We only need to do special processing if we were asked for
+ * the extended DN, or we are 'store DN+GUID+SID'
+ * (!dereference) mode. (This is the normal mode for LDB on
+ * tdb). */
+ if (control || (storage_format_control && p)) {
+ ac->inject = true;
+ if (extended_ctrl) {
+ ac->extended_type = extended_ctrl->type;
+ } else {
+ ac->extended_type = 0;
+ }
+
+ /* check if attrs only is specified, in that case check whether we need to modify them */
+ if (req->op.search.attrs && !is_attr_in_list(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(ac, req->op.search.attrs);
+ if (new_attrs == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ if (ac->remove_guid) {
+ if (!add_attrs(ac, &new_attrs, "objectGUID"))
+ return ldb_operr(ldb);
+ }
+ if (ac->remove_sid) {
+ if (!add_attrs(ac, &new_attrs, "objectSid"))
+ return ldb_operr(ldb);
+ }
+ const_attrs = (const char * const *)new_attrs;
+ }
+ }
+ }
+
+ ret = ldb_build_search_req_ex(&down_req,
+ ldb, ac,
+ req->op.search.base,
+ req->op.search.scope,
+ req->op.search.tree,
+ const_attrs,
+ req->controls,
+ ac, callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* mark extended DN and storage format controls as done */
+ if (control) {
+ control->critical = 0;
+ }
+
+ if (storage_format_control) {
+ storage_format_control->critical = 0;
+ }
+
+ /* perform the search */
+ return ldb_next_request(module, down_req);
+}
+
+static int extended_dn_out_ldb_search(struct ldb_module *module, struct ldb_request *req)
+{
+ return extended_dn_out_search(module, req, extended_callback_ldb);
+}
+
+static int extended_dn_out_ldb_init(struct ldb_module *module)
+{
+ int ret;
+
+ struct extended_dn_out_private *p = talloc(module, struct extended_dn_out_private);
+ struct dsdb_extended_dn_store_format *dn_format;
+
+ ldb_module_set_private(module, p);
+
+ if (!p) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ dn_format = talloc(p, struct dsdb_extended_dn_store_format);
+ if (!dn_format) {
+ talloc_free(p);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ dn_format->store_extended_dn_in_ldb = true;
+ ret = ldb_set_opaque(ldb_module_get_ctx(module), DSDB_EXTENDED_DN_STORE_FORMAT_OPAQUE_NAME, dn_format);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(p);
+ return ret;
+ }
+
+ p->dereference = false;
+ p->normalise = false;
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_EXTENDED_DN_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_ERROR,
+ "extended_dn_out: Unable to register control with rootdse!\n");
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_extended_dn_out_ldb_module_ops = {
+ .name = "extended_dn_out_ldb",
+ .search = extended_dn_out_ldb_search,
+ .init_context = extended_dn_out_ldb_init,
+};
+
+/*
+ initialise the module
+ */
+_PUBLIC_ int ldb_extended_dn_out_module_init(const char *version)
+{
+ int ret;
+ LDB_MODULE_CHECK_VERSION(version);
+ ret = ldb_register_module(&ldb_extended_dn_out_ldb_module_ops);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return LDB_SUCCESS;
+}
diff --git a/source4/dsdb/samdb/ldb_modules/extended_dn_store.c b/source4/dsdb/samdb/ldb_modules/extended_dn_store.c
new file mode 100644
index 0000000..0b0e186
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/extended_dn_store.c
@@ -0,0 +1,830 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2005-2008
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007-2009
+
+ 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 extended dn control module
+ *
+ * Description: this module builds a special dn for returned search
+ * results nad creates the special DN in the backend store for new
+ * values.
+ *
+ * This also has the curious result that we convert <SID=S-1-2-345>
+ * in an attribute value into a normal DN for the rest of the stack
+ * to process
+ *
+ * Authors: Simo Sorce
+ * Andrew Bartlett
+ */
+
+#include "includes.h"
+#include <ldb.h>
+#include <ldb_errors.h>
+#include <ldb_module.h>
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "dsdb/samdb/samdb.h"
+#include "libcli/security/security.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include <time.h>
+
+struct extended_dn_replace_list {
+ struct extended_dn_replace_list *next;
+ struct dsdb_dn *dsdb_dn;
+ TALLOC_CTX *mem_ctx;
+ struct ldb_val *replace_dn;
+ struct extended_dn_context *ac;
+ struct ldb_request *search_req;
+ bool fpo_enabled;
+ bool require_object;
+ bool got_entry;
+};
+
+
+struct extended_dn_context {
+ const struct dsdb_schema *schema;
+ struct ldb_module *module;
+ struct ldb_context *ldb;
+ struct ldb_request *req;
+ struct ldb_request *new_req;
+
+ struct extended_dn_replace_list *ops;
+ struct extended_dn_replace_list *cur;
+
+ /*
+ * Used by the FPO-enabled attribute validation.
+ */
+ struct dsdb_trust_routing_table *routing_table;
+};
+
+
+static struct extended_dn_context *extended_dn_context_init(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct extended_dn_context *ac;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ ac = talloc_zero(req, struct extended_dn_context);
+ if (ac == NULL) {
+ ldb_oom(ldb);
+ return NULL;
+ }
+
+ ac->schema = dsdb_get_schema(ldb, ac);
+ ac->module = module;
+ ac->ldb = ldb;
+ ac->req = req;
+
+ return ac;
+}
+
+static int extended_replace_dn(struct extended_dn_replace_list *os,
+ struct ldb_dn *dn)
+{
+ struct dsdb_dn *dsdb_dn = NULL;
+ const char *str = NULL;
+
+ /*
+ * Rebuild with the string or binary 'extra part' the
+ * DN may have had as a prefix
+ */
+ dsdb_dn = dsdb_dn_construct(os, dn,
+ os->dsdb_dn->extra_part,
+ os->dsdb_dn->oid);
+ if (dsdb_dn == NULL) {
+ return ldb_module_operr(os->ac->module);
+ }
+
+ str = dsdb_dn_get_extended_linearized(os->mem_ctx,
+ dsdb_dn, 1);
+ if (str == NULL) {
+ return ldb_module_operr(os->ac->module);
+ }
+
+ /*
+ * Replace the DN with the extended version of the DN
+ * (ie, add SID and GUID)
+ */
+ *os->replace_dn = data_blob_string_const(str);
+ os->got_entry = true;
+ return LDB_SUCCESS;
+}
+
+static int extended_dn_handle_fpo_attr(struct extended_dn_replace_list *os)
+{
+ struct dom_sid target_sid = { 0, };
+ struct dom_sid target_domain = { 0, };
+ struct ldb_message *fmsg = NULL;
+ char *fsid = NULL;
+ const struct dom_sid *domain_sid = NULL;
+ struct ldb_dn *domain_dn = NULL;
+ const struct lsa_TrustDomainInfoInfoEx *tdo = NULL;
+ uint32_t trust_attributes = 0;
+ const char *no_attrs[] = { NULL, };
+ struct ldb_result *res = NULL;
+ NTSTATUS status;
+ bool match;
+ bool ok;
+ int ret;
+
+ /*
+ * DN doesn't exist yet
+ *
+ * Check if a foreign SID is specified,
+ * which would trigger the creation
+ * of a foreignSecurityPrincipal.
+ */
+ status = dsdb_get_extended_dn_sid(os->dsdb_dn->dn,
+ &target_sid,
+ "SID");
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * No SID specified
+ */
+ return dsdb_module_werror(os->ac->module,
+ LDB_ERR_NO_SUCH_OBJECT,
+ WERR_NO_SUCH_USER,
+ "specified dn doesn't exist");
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ldb_module_operr(os->ac->module);
+ }
+ if (ldb_dn_get_extended_comp_num(os->dsdb_dn->dn) != 1) {
+ return dsdb_module_werror(os->ac->module,
+ LDB_ERR_NO_SUCH_OBJECT,
+ WERR_NO_SUCH_USER,
+ "specified extended component other than SID");
+ }
+ if (ldb_dn_get_comp_num(os->dsdb_dn->dn) != 0) {
+ return dsdb_module_werror(os->ac->module,
+ LDB_ERR_NO_SUCH_OBJECT,
+ WERR_NO_SUCH_USER,
+ "specified more the SID");
+ }
+
+ target_domain = target_sid;
+ sid_split_rid(&target_domain, NULL);
+
+ match = dom_sid_equal(&global_sid_Builtin, &target_domain);
+ if (match) {
+ /*
+ * Non existing BUILTIN sid
+ */
+ return dsdb_module_werror(os->ac->module,
+ LDB_ERR_NO_SUCH_OBJECT,
+ WERR_NO_SUCH_MEMBER,
+ "specified sid doesn't exist in BUILTIN");
+ }
+
+ domain_sid = samdb_domain_sid(os->ac->ldb);
+ if (domain_sid == NULL) {
+ return ldb_module_operr(os->ac->module);
+ }
+ match = dom_sid_equal(domain_sid, &target_domain);
+ if (match) {
+ /*
+ * Non existing SID in our domain.
+ */
+ return dsdb_module_werror(os->ac->module,
+ LDB_ERR_UNWILLING_TO_PERFORM,
+ WERR_DS_INVALID_GROUP_TYPE,
+ "specified sid doesn't exist in domain");
+ }
+
+ if (os->ac->routing_table == NULL) {
+ status = dsdb_trust_routing_table_load(os->ac->ldb, os->ac,
+ &os->ac->routing_table);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ldb_module_operr(os->ac->module);
+ }
+ }
+
+ tdo = dsdb_trust_domain_by_sid(os->ac->routing_table,
+ &target_domain, NULL);
+ if (tdo != NULL) {
+ trust_attributes = tdo->trust_attributes;
+ }
+
+ if (trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) {
+ return dsdb_module_werror(os->ac->module,
+ LDB_ERR_UNWILLING_TO_PERFORM,
+ WERR_DS_INVALID_GROUP_TYPE,
+ "specified sid doesn't exist in forest");
+ }
+
+ fmsg = ldb_msg_new(os);
+ if (fmsg == NULL) {
+ return ldb_module_oom(os->ac->module);
+ }
+
+ fsid = dom_sid_string(fmsg, &target_sid);
+ if (fsid == NULL) {
+ return ldb_module_oom(os->ac->module);
+ }
+
+ domain_dn = ldb_get_default_basedn(os->ac->ldb);
+ if (domain_dn == NULL) {
+ return ldb_module_operr(os->ac->module);
+ }
+
+ fmsg->dn = ldb_dn_copy(fmsg, domain_dn);
+ if (fmsg->dn == NULL) {
+ return ldb_module_oom(os->ac->module);
+ }
+
+ ok = ldb_dn_add_child_fmt(fmsg->dn,
+ "CN=%s,CN=ForeignSecurityPrincipals",
+ fsid);
+ if (!ok) {
+ return ldb_module_oom(os->ac->module);
+ }
+
+ ret = ldb_msg_add_string(fmsg, "objectClass", "foreignSecurityPrincipal");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = dsdb_module_add(os->ac->module, fmsg,
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_FLAG_NEXT_MODULE,
+ os->ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = dsdb_module_search_dn(os->ac->module, fmsg, &res,
+ fmsg->dn, no_attrs,
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ os->ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ * dsdb_module_search_dn() garantees exactly one result message
+ * on success.
+ */
+ ret = extended_replace_dn(os, res->msgs[0]->dn);
+ TALLOC_FREE(fmsg);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+/* An extra layer of indirection because LDB does not allow the original request to be altered */
+
+static int extended_final_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ int ret = LDB_ERR_OPERATIONS_ERROR;
+ struct extended_dn_context *ac;
+ ac = talloc_get_type(req->context, struct extended_dn_context);
+
+ if (ares->error != LDB_SUCCESS) {
+ ret = ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ } else {
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+
+ ret = ldb_module_send_entry(ac->req, ares->message, ares->controls);
+ break;
+ case LDB_REPLY_REFERRAL:
+
+ ret = ldb_module_send_referral(ac->req, ares->referral);
+ break;
+ case LDB_REPLY_DONE:
+
+ ret = ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ break;
+ }
+ }
+ return ret;
+}
+
+static int extended_replace_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct extended_dn_replace_list *os = talloc_get_type(req->context,
+ struct extended_dn_replace_list);
+
+ if (!ares) {
+ return ldb_module_done(os->ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error == LDB_ERR_NO_SUCH_OBJECT) {
+ if (os->got_entry) {
+ /* This is in internal error... */
+ int ret = ldb_module_operr(os->ac->module);
+ return ldb_module_done(os->ac->req, NULL, NULL, ret);
+ }
+
+ if (os->require_object && os->fpo_enabled) {
+ int ret;
+
+ ret = extended_dn_handle_fpo_attr(os);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(os->ac->req, NULL, NULL,
+ ret);
+ }
+ /* os->got_entry is true at this point... */
+ }
+
+ if (!os->got_entry && os->require_object) {
+ /*
+ * It's an error if the target doesn't exist,
+ * unless it's a delete.
+ */
+ int ret = dsdb_module_werror(os->ac->module,
+ LDB_ERR_CONSTRAINT_VIOLATION,
+ WERR_DS_NAME_REFERENCE_INVALID,
+ "Referenced object not found");
+ return ldb_module_done(os->ac->req, NULL, NULL, ret);
+ }
+
+ /* Don't worry too much about dangling references */
+
+ ldb_reset_err_string(os->ac->ldb);
+ if (os->next) {
+ struct extended_dn_replace_list *next;
+
+ next = os->next;
+
+ talloc_free(os);
+
+ os = next;
+ return ldb_next_request(os->ac->module, next->search_req);
+ } else {
+ /* Otherwise, we are done - let's run the
+ * request now we have swapped the DNs for the
+ * full versions */
+ return ldb_next_request(os->ac->module, os->ac->new_req);
+ }
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(os->ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ /* Only entries are interesting, and we only want the olddn */
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ {
+ /* This *must* be the right DN, as this is a base
+ * search. We can't check, as it could be an extended
+ * DN, so a module below will resolve it */
+ int ret;
+
+ ret = extended_replace_dn(os, ares->message->dn);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(os->ac->req, NULL, NULL, ret);
+ }
+ /* os->got_entry is true at this point */
+ break;
+ }
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+
+ talloc_free(ares);
+
+ if (!os->got_entry && os->require_object && os->fpo_enabled) {
+ int ret;
+
+ ret = extended_dn_handle_fpo_attr(os);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(os->ac->req, NULL, NULL,
+ ret);
+ }
+ /* os->got_entry is true at this point... */
+ }
+
+ if (!os->got_entry && os->require_object) {
+ /*
+ * It's an error if the target doesn't exist,
+ * unless it's a delete.
+ */
+ int ret = dsdb_module_werror(os->ac->module,
+ LDB_ERR_CONSTRAINT_VIOLATION,
+ WERR_DS_NAME_REFERENCE_INVALID,
+ "Referenced object not found");
+ return ldb_module_done(os->ac->req, NULL, NULL, ret);
+ }
+
+ /* Run the next search */
+
+ if (os->next) {
+ struct extended_dn_replace_list *next;
+
+ next = os->next;
+
+ talloc_free(os);
+
+ os = next;
+ return ldb_next_request(os->ac->module, next->search_req);
+ } else {
+ /* Otherwise, we are done - let's run the
+ * request now we have swapped the DNs for the
+ * full versions */
+ return ldb_next_request(os->ac->module, os->ac->new_req);
+ }
+ }
+
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+/* We have a 'normal' DN in the inbound request. We need to find out
+ * what the GUID and SID are on the DN it points to, so we can
+ * construct an extended DN for storage.
+ *
+ * This creates a list of DNs to look up, and the plain DN to replace
+ */
+
+static int extended_store_replace(struct extended_dn_context *ac,
+ TALLOC_CTX *callback_mem_ctx,
+ struct ldb_dn *self_dn,
+ struct ldb_val *plain_dn,
+ bool is_delete,
+ const struct dsdb_attribute *schema_attr)
+{
+ const char *oid = schema_attr->syntax->ldap_oid;
+ int ret;
+ struct extended_dn_replace_list *os;
+ static const char *attrs[] = {
+ "objectSid",
+ "objectGUID",
+ NULL
+ };
+ uint32_t ctrl_flags = 0;
+ bool is_untrusted = ldb_req_is_untrusted(ac->req);
+
+ os = talloc_zero(ac, struct extended_dn_replace_list);
+ if (!os) {
+ return ldb_oom(ac->ldb);
+ }
+
+ os->ac = ac;
+
+ os->mem_ctx = callback_mem_ctx;
+
+ os->dsdb_dn = dsdb_dn_parse(os, ac->ldb, plain_dn, oid);
+ if (!os->dsdb_dn || !ldb_dn_validate(os->dsdb_dn->dn)) {
+ talloc_free(os);
+ ldb_asprintf_errstring(ac->ldb,
+ "could not parse %.*s as a %s DN", (int)plain_dn->length, plain_dn->data,
+ oid);
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+
+ if (self_dn != NULL) {
+ ret = ldb_dn_compare(self_dn, os->dsdb_dn->dn);
+ if (ret == 0) {
+ /*
+ * If this is a reference to the object
+ * itself during an 'add', we won't
+ * be able to find the object.
+ */
+ talloc_free(os);
+ return LDB_SUCCESS;
+ }
+ }
+
+ if (is_delete && !ldb_dn_has_extended(os->dsdb_dn->dn)) {
+ /* NO need to figure this DN out, this element is
+ * going to be deleted anyway, and becuase it's not
+ * extended, we have enough information to do the
+ * delete */
+ talloc_free(os);
+ return LDB_SUCCESS;
+ }
+
+
+ os->replace_dn = plain_dn;
+
+ /* The search request here might happen to be for an
+ * 'extended' style DN, such as <GUID=abced...>. The next
+ * module in the stack will convert this into a normal DN for
+ * processing */
+ ret = ldb_build_search_req(&os->search_req,
+ ac->ldb, os, os->dsdb_dn->dn, LDB_SCOPE_BASE, NULL,
+ attrs, NULL, os, extended_replace_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(os->search_req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(os);
+ return ret;
+ }
+
+ /*
+ * By default we require the presence of the target.
+ */
+ os->require_object = true;
+
+ /*
+ * Handle FPO-enabled attributes, see
+ * [MS-ADTS] 3.1.1.5.2.3 Special Classes and Attributes:
+ *
+ * FPO-enabled attributes: member, msDS-MembersForAzRole,
+ * msDS-NeverRevealGroup, msDS-NonMembers, msDS-RevealOnDemandGroup,
+ * msDS-ServiceAccount.
+ *
+ * Note there's no msDS-ServiceAccount in any schema (only
+ * msDS-HostServiceAccount and that's not an FPO-enabled attribute
+ * at least not in W2008R2)
+ *
+ * msDS-NonMembers always generates NOT_SUPPORTED against W2008R2.
+ *
+ * See also [MS-SAMR] 3.1.1.8.9 member.
+ */
+ switch (schema_attr->attributeID_id) {
+ case DRSUAPI_ATTID_member:
+ case DRSUAPI_ATTID_msDS_MembersForAzRole:
+ case DRSUAPI_ATTID_msDS_NeverRevealGroup:
+ case DRSUAPI_ATTID_msDS_RevealOnDemandGroup:
+ os->fpo_enabled = true;
+ break;
+
+ case DRSUAPI_ATTID_msDS_HostServiceAccount:
+ /* This is NOT a FPO-enabled attribute */
+ break;
+
+ case DRSUAPI_ATTID_msDS_NonMembers:
+ return dsdb_module_werror(os->ac->module,
+ LDB_ERR_UNWILLING_TO_PERFORM,
+ WERR_NOT_SUPPORTED,
+ "msDS-NonMembers is not supported");
+ }
+
+ if (schema_attr->linkID == 0) {
+ /*
+ * None linked attributes allow references
+ * to deleted objects.
+ */
+ ctrl_flags |= DSDB_SEARCH_SHOW_RECYCLED;
+ }
+
+ if (is_delete) {
+ /*
+ * On delete want to be able to
+ * find a deleted object, but
+ * it's not a problem if they doesn't
+ * exist.
+ */
+ ctrl_flags |= DSDB_SEARCH_SHOW_RECYCLED;
+ os->require_object = false;
+ }
+
+ if (!is_untrusted) {
+ struct ldb_control *ctrl = NULL;
+
+ /*
+ * During provision or dbcheck we may not find
+ * an object.
+ */
+
+ ctrl = ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID);
+ if (ctrl != NULL) {
+ os->require_object = false;
+ }
+ ctrl = ldb_request_get_control(ac->req, DSDB_CONTROL_DBCHECK);
+ if (ctrl != NULL) {
+ os->require_object = false;
+ }
+ }
+
+ ret = dsdb_request_add_controls(os->search_req,
+ DSDB_FLAG_AS_SYSTEM |
+ ctrl_flags |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(os);
+ return ret;
+ }
+
+ if (ac->ops) {
+ ac->cur->next = os;
+ } else {
+ ac->ops = os;
+ }
+ ac->cur = os;
+
+ return LDB_SUCCESS;
+}
+
+
+/* add */
+static int extended_dn_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct extended_dn_context *ac;
+ int ret;
+ unsigned int i, j;
+
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ ac = extended_dn_context_init(module, req);
+ if (!ac) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ if (!ac->schema) {
+ /* without schema, this doesn't make any sense */
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+
+ 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(ac->schema, el->name);
+ if (!schema_attr) {
+ continue;
+ }
+
+ /* We only setup an extended DN GUID on DN elements */
+ if (schema_attr->dn_format == DSDB_INVALID_DN) {
+ continue;
+ }
+
+ if (schema_attr->attributeID_id == DRSUAPI_ATTID_distinguishedName) {
+ /* distinguishedName values are ignored */
+ continue;
+ }
+
+ /* Before we setup a procedure to modify the incoming message, we must copy it */
+ if (!ac->new_req) {
+ struct ldb_message *msg = ldb_msg_copy(ac, req->op.add.message);
+ if (!msg) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ ret = ldb_build_add_req(&ac->new_req, ac->ldb, ac, msg, req->controls, ac, extended_final_callback, req);
+ LDB_REQ_SET_LOCATION(ac->new_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ /* Re-calculate el */
+ el = &ac->new_req->op.add.message->elements[i];
+ for (j = 0; j < el->num_values; j++) {
+ ret = extended_store_replace(ac, ac->new_req,
+ req->op.add.message->dn,
+ &el->values[j],
+ false, schema_attr);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ /* if no DNs were set continue */
+ if (ac->ops == NULL) {
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+
+ /* start with the searches */
+ return ldb_next_request(module, ac->ops->search_req);
+}
+
+/* modify */
+static int extended_dn_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 */
+
+ unsigned int i, j;
+ struct extended_dn_context *ac;
+ struct ldb_control *fix_links_control = NULL;
+ struct ldb_control *fix_link_sid_ctrl = NULL;
+ int ret;
+
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ ac = extended_dn_context_init(module, req);
+ if (!ac) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ if (!ac->schema) {
+ talloc_free(ac);
+ /* without schema, this doesn't make any sense */
+ return ldb_next_request(module, req);
+ }
+
+ fix_links_control = ldb_request_get_control(req,
+ DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS);
+ if (fix_links_control != NULL) {
+ return ldb_next_request(module, req);
+ }
+
+ fix_link_sid_ctrl = ldb_request_get_control(ac->req,
+ DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID);
+ if (fix_link_sid_ctrl != NULL) {
+ return ldb_next_request(module, req);
+ }
+
+ for (i=0; i < req->op.mod.message->num_elements; i++) {
+ const struct ldb_message_element *el = &req->op.mod.message->elements[i];
+ const struct dsdb_attribute *schema_attr
+ = dsdb_attribute_by_lDAPDisplayName(ac->schema, el->name);
+ if (!schema_attr) {
+ continue;
+ }
+
+ /* We only setup an extended DN GUID on these particular DN objects */
+ if (schema_attr->dn_format == DSDB_INVALID_DN) {
+ continue;
+ }
+
+ if (schema_attr->attributeID_id == DRSUAPI_ATTID_distinguishedName) {
+ /* distinguishedName values are ignored */
+ continue;
+ }
+
+ /* Before we setup a procedure to modify the incoming message, we must copy it */
+ if (!ac->new_req) {
+ struct ldb_message *msg = ldb_msg_copy(ac, req->op.mod.message);
+ if (!msg) {
+ talloc_free(ac);
+ return ldb_oom(ac->ldb);
+ }
+
+ ret = ldb_build_mod_req(&ac->new_req, ac->ldb, ac, msg, req->controls, ac, extended_final_callback, req);
+ LDB_REQ_SET_LOCATION(ac->new_req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ }
+ /* Re-calculate el */
+ el = &ac->new_req->op.mod.message->elements[i];
+ /* For each value being added, we need to setup the lookups to fill in the extended DN */
+ for (j = 0; j < el->num_values; j++) {
+ /* If we are just going to delete this
+ * element, only do a lookup if
+ * extended_store_replace determines it's an
+ * input of an extended DN */
+ bool is_delete = (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE);
+
+ ret = extended_store_replace(ac, ac->new_req,
+ NULL, /* self_dn to be ignored */
+ &el->values[j],
+ is_delete, schema_attr);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ }
+ }
+
+ /* if DNs were set continue */
+ if (ac->ops == NULL) {
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+
+ /* start with the searches */
+ return ldb_next_request(module, ac->ops->search_req);
+}
+
+static const struct ldb_module_ops ldb_extended_dn_store_module_ops = {
+ .name = "extended_dn_store",
+ .add = extended_dn_add,
+ .modify = extended_dn_modify,
+};
+
+int ldb_extended_dn_store_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_extended_dn_store_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/group_audit.c b/source4/dsdb/samdb/ldb_modules/group_audit.c
new file mode 100644
index 0000000..a6ca25e
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/group_audit.c
@@ -0,0 +1,1555 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+
+ 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/>.
+*/
+
+/*
+ * Provide an audit log of changes made to group memberships
+ *
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "lib/audit_logging/audit_logging.h"
+#include "librpc/gen_ndr/windows_event_ids.h"
+
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "dsdb/samdb/ldb_modules/audit_util_proto.h"
+#include "libcli/security/dom_sid.h"
+#include "auth/common_auth.h"
+#include "param/param.h"
+
+#define AUDIT_JSON_TYPE "groupChange"
+#define AUDIT_HR_TAG "Group Change"
+#define AUDIT_MAJOR 1
+#define AUDIT_MINOR 1
+#define GROUP_LOG_LVL 5
+
+static const char *const group_attrs[] = {"member", "groupType", NULL};
+static const char *const group_type_attr[] = {"groupType", NULL};
+static const char * const primary_group_attr[] = {
+ "primaryGroupID",
+ "objectSID",
+ NULL};
+
+struct audit_context {
+ bool send_events;
+ struct imessaging_context *msg_ctx;
+};
+
+struct audit_callback_context {
+ struct ldb_request *request;
+ struct ldb_module *module;
+ struct ldb_message_element *members;
+ uint32_t primary_group;
+ void (*log_changes)(
+ struct audit_callback_context *acc,
+ const int status);
+};
+
+/*
+ * @brief get the transaction id.
+ *
+ * Get the id of the transaction that the current request is contained in.
+ *
+ * @param req the request.
+ *
+ * @return the transaction id GUID, or NULL if it is not there.
+ */
+static struct GUID *get_transaction_id(
+ const struct ldb_request *request)
+{
+ struct ldb_control *control;
+ struct dsdb_control_transaction_identifier *transaction_id;
+
+ control = ldb_request_get_control(
+ discard_const(request),
+ DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID);
+ if (control == NULL) {
+ return NULL;
+ }
+ transaction_id = talloc_get_type(
+ control->data,
+ struct dsdb_control_transaction_identifier);
+ if (transaction_id == NULL) {
+ return NULL;
+ }
+ return &transaction_id->transaction_guid;
+}
+
+/*
+ * @brief generate a JSON log entry for a group change.
+ *
+ * Generate a JSON object containing details of a users group change.
+ *
+ * @param module the ldb module
+ * @param request the ldb_request
+ * @param action the change action being performed
+ * @param user the user name
+ * @param group the group name
+ * @param status the ldb status code for the ldb operation.
+ *
+ * @return A json object containing the details.
+ * NULL if an error was detected
+ */
+static struct json_object audit_group_json(const struct ldb_module *module,
+ const struct ldb_request *request,
+ const char *action,
+ const char *user,
+ const char *group,
+ const enum event_id_type event_id,
+ const int status)
+{
+ struct ldb_context *ldb = NULL;
+ const struct dom_sid *sid = NULL;
+ struct json_object wrapper = json_empty_object;
+ struct json_object audit = json_empty_object;
+ const struct tsocket_address *remote = NULL;
+ const struct GUID *unique_session_token = NULL;
+ struct GUID *transaction_id = NULL;
+ int rc = 0;
+
+ ldb = ldb_module_get_ctx(discard_const(module));
+
+ remote = dsdb_audit_get_remote_address(ldb);
+ sid = dsdb_audit_get_user_sid(module);
+ unique_session_token = dsdb_audit_get_unique_session_token(module);
+ transaction_id = get_transaction_id(request);
+
+ audit = json_new_object();
+ if (json_is_invalid(&audit)) {
+ goto failure;
+ }
+ rc = json_add_version(&audit, AUDIT_MAJOR, AUDIT_MINOR);
+ if (rc != 0) {
+ goto failure;
+ }
+ if (event_id != EVT_ID_NONE) {
+ rc = json_add_int(&audit, "eventId", event_id);
+ if (rc != 0) {
+ goto failure;
+ }
+ }
+ rc = json_add_int(&audit, "statusCode", status);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "status", ldb_strerror(status));
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "action", action);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_address(&audit, "remoteAddress", remote);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_sid(&audit, "userSid", sid);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "group", group);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_guid(&audit, "transactionId", transaction_id);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_guid(&audit, "sessionId", unique_session_token);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&audit, "user", user);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ wrapper = json_new_object();
+ if (json_is_invalid(&wrapper)) {
+ goto failure;
+ }
+ rc = json_add_timestamp(&wrapper);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&wrapper, "type", AUDIT_JSON_TYPE);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_object(&wrapper, AUDIT_JSON_TYPE, &audit);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ return wrapper;
+failure:
+ /*
+ * On a failure audit will not have been added to wrapper so it
+ * needs to free it to avoid a leak.
+ *
+ * wrapper is freed to invalidate it as it will have only been
+ * partially constructed and may be inconsistent.
+ *
+ * All the json manipulation routines handle a freed object correctly
+ */
+ json_free(&audit);
+ json_free(&wrapper);
+ DBG_ERR("Failed to create group change JSON log message\n");
+ return wrapper;
+}
+
+/*
+ * @brief generate a human readable log entry for a group change.
+ *
+ * Generate a human readable log entry containing details of a users group
+ * change.
+ *
+ * @param ctx the talloc context owning the returned log entry
+ * @param module the ldb module
+ * @param request the ldb_request
+ * @param action the change action being performed
+ * @param user the user name
+ * @param group the group name
+ * @param status the ldb status code for the ldb operation.
+ *
+ * @return A human readable log line.
+ */
+static char *audit_group_human_readable(
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_module *module,
+ const struct ldb_request *request,
+ const char *action,
+ const char *user,
+ const char *group,
+ const int status)
+{
+ struct ldb_context *ldb = NULL;
+ const char *remote_host = NULL;
+ const struct dom_sid *sid = NULL;
+ const char *user_sid = NULL;
+ const char *timestamp = NULL;
+ char *log_entry = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_module_get_ctx(discard_const(module));
+
+ remote_host = dsdb_audit_get_remote_host(ldb, ctx);
+ sid = dsdb_audit_get_user_sid(module);
+ user_sid = dom_sid_string(ctx, sid);
+ timestamp = audit_get_timestamp(ctx);
+
+ log_entry = talloc_asprintf(
+ mem_ctx,
+ "[%s] at [%s] status [%s] "
+ "Remote host [%s] SID [%s] Group [%s] User [%s]",
+ action,
+ timestamp,
+ ldb_strerror(status),
+ remote_host,
+ user_sid,
+ group,
+ user);
+ TALLOC_FREE(ctx);
+ return log_entry;
+}
+
+/*
+ * @brief generate an array of parsed_dns, deferring the actual parsing.
+ *
+ * Get an array of 'struct parsed_dns' without the parsing.
+ * The parsed_dns are parsed only when needed to avoid the expense of parsing.
+ *
+ * This procedure assumes that the dn's are sorted in GUID order and contains
+ * no duplicates. This should be valid as the module sits below repl_meta_data
+ * which ensures this.
+ *
+ * @param mem_ctx The memory context that will own the generated array
+ * @param el The message element used to generate the array.
+ *
+ * @return an array of struct parsed_dns, or NULL in the event of an error
+ */
+static struct parsed_dn *get_parsed_dns(
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *el)
+{
+ int ret;
+ struct parsed_dn *pdn = NULL;
+
+ if (el == NULL || el->num_values == 0) {
+ return NULL;
+ }
+
+ ret = get_parsed_dns_trusted(mem_ctx, el, &pdn);
+ if (ret == LDB_ERR_OPERATIONS_ERROR) {
+ DBG_ERR("Out of memory\n");
+ return NULL;
+ }
+ return pdn;
+
+}
+
+enum dn_compare_result {
+ LESS_THAN,
+ BINARY_EQUAL,
+ EQUAL,
+ GREATER_THAN
+};
+/*
+ * @brief compare parsed_dn, using GUID ordering
+ *
+ * Compare two parsed_dn structures, using GUID ordering.
+ * To avoid the overhead of parsing the DN's this function does a binary
+ * compare first. The DN's tre only parsed if they are not equal at a binary
+ * level.
+ *
+ * @param ctx talloc context that will own the parsed dsdb_dn
+ * @param ldb ldb_context
+ * @param dn1 The first dn
+ * @param dn2 The second dn
+ *
+ * @return BINARY_EQUAL values are equal at a binary level
+ * EQUAL DN's are equal but the meta data is different
+ * LESS_THAN dn1's GUID is less than dn2's GUID
+ * GREATER_THAN dn1's GUID is greater than dn2's GUID
+ *
+ */
+static enum dn_compare_result dn_compare(
+ TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ struct parsed_dn *dn1,
+ struct parsed_dn *dn2) {
+
+ int res = 0;
+
+ /*
+ * Do a binary compare first to avoid unnecessary parsing
+ */
+ if (data_blob_cmp(dn1->v, dn2->v) == 0) {
+ /*
+ * Values are equal at a binary level so no need
+ * for further processing
+ */
+ return BINARY_EQUAL;
+ }
+ /*
+ * Values not equal at the binary level, so lets
+ * do a GUID ordering compare. To do this we will need to ensure
+ * that the dn's have been parsed.
+ */
+ if (dn1->dsdb_dn == NULL) {
+ really_parse_trusted_dn(
+ mem_ctx,
+ ldb,
+ dn1,
+ LDB_SYNTAX_DN);
+ }
+ if (dn2->dsdb_dn == NULL) {
+ really_parse_trusted_dn(
+ mem_ctx,
+ ldb,
+ dn2,
+ LDB_SYNTAX_DN);
+ }
+
+ res = ndr_guid_compare(&dn1->guid, &dn2->guid);
+ if (res < 0) {
+ return LESS_THAN;
+ } else if (res == 0) {
+ return EQUAL;
+ } else {
+ return GREATER_THAN;
+ }
+}
+
+/*
+ * @brief Get the DN of a users primary group as a printable string.
+ *
+ * Get the DN of a users primary group as a printable string.
+ *
+ * @param mem_ctx Talloc context the the returned string will be allocated on.
+ * @param module The ldb module
+ * @param account_sid The SID for the uses account.
+ * @param primary_group_rid The RID for the users primary group.
+ *
+ * @return a formatted DN, or null if there is an error.
+ */
+static const char *get_primary_group_dn(
+ TALLOC_CTX *mem_ctx,
+ struct ldb_module *module,
+ struct dom_sid *account_sid,
+ uint32_t primary_group_rid)
+{
+ NTSTATUS status;
+
+ struct ldb_context *ldb = NULL;
+ struct dom_sid *domain_sid = NULL;
+ struct dom_sid *primary_group_sid = NULL;
+ char *sid = NULL;
+ struct ldb_dn *dn = NULL;
+ struct ldb_message *msg = NULL;
+ int rc;
+
+ ldb = ldb_module_get_ctx(module);
+
+ status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ primary_group_sid = dom_sid_add_rid(
+ mem_ctx,
+ domain_sid,
+ primary_group_rid);
+ if (!primary_group_sid) {
+ return NULL;
+ }
+
+ sid = dom_sid_string(mem_ctx, primary_group_sid);
+ if (sid == NULL) {
+ return NULL;
+ }
+
+ dn = ldb_dn_new_fmt(mem_ctx, ldb, "<SID=%s>", sid);
+ if(dn == NULL) {
+ return sid;
+ }
+ rc = dsdb_search_one(
+ ldb,
+ mem_ctx,
+ &msg,
+ dn,
+ LDB_SCOPE_BASE,
+ NULL,
+ 0,
+ NULL);
+ if (rc != LDB_SUCCESS) {
+ return NULL;
+ }
+
+ return ldb_dn_get_linearized(msg->dn);
+}
+
+/*
+ * @brief Log details of a change to a users primary group.
+ *
+ * Log details of a change to a users primary group.
+ * There is no windows event id associated with a Primary Group change.
+ * However for a new user we generate an added to group event.
+ *
+ * @param module The ldb module.
+ * @param request The request being logged.
+ * @param action Description of the action being performed.
+ * @param group The linearized for of the group DN
+ * @param status the LDB status code for the processing of the request.
+ *
+ */
+static void log_primary_group_change(
+ struct ldb_module *module,
+ const struct ldb_request *request,
+ const char *action,
+ const char *group,
+ const int status)
+{
+ const char *user = NULL;
+
+ struct audit_context *ac =
+ talloc_get_type(
+ ldb_module_get_private(module),
+ struct audit_context);
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ user = dsdb_audit_get_primary_dn(request);
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL)) {
+ char *message = NULL;
+ message = audit_group_human_readable(
+ ctx,
+ module,
+ request,
+ action,
+ user,
+ group,
+ status);
+ audit_log_human_text(
+ AUDIT_HR_TAG,
+ message,
+ DBGC_DSDB_GROUP_AUDIT,
+ GROUP_LOG_LVL);
+ TALLOC_FREE(message);
+ }
+
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
+ (ac->msg_ctx && ac->send_events)) {
+
+ struct json_object json;
+ json = audit_group_json(
+ module, request, action, user, group, EVT_ID_NONE, status);
+ audit_log_json(
+ &json,
+ DBGC_DSDB_GROUP_AUDIT_JSON,
+ GROUP_LOG_LVL);
+ if (ac->send_events) {
+ audit_message_send(
+ ac->msg_ctx,
+ DSDB_GROUP_EVENT_NAME,
+ MSG_GROUP_LOG,
+ &json);
+ }
+ json_free(&json);
+ if (request->operation == LDB_ADD) {
+ /*
+ * Have just added a user, generate a groupChange
+ * message indicating the user has been added to thier
+ * new PrimaryGroup.
+ */
+ }
+ }
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * @brief Log details of a single change to a users group membership.
+ *
+ * Log details of a change to a users group membership, except for changes
+ * to their primary group which is handled by log_primary_group_change.
+ *
+ * @param module The ldb module.
+ * @param request The request being logged.
+ * @param action Description of the action being performed.
+ * @param user The linearized form of the users DN
+ * @param status the LDB status code for the processing of the request.
+ *
+ */
+static void log_membership_change(struct ldb_module *module,
+ const struct ldb_request *request,
+ const char *action,
+ const char *user,
+ const enum event_id_type event_id,
+ const int status)
+{
+ const char *group = NULL;
+ struct audit_context *ac =
+ talloc_get_type(
+ ldb_module_get_private(module),
+ struct audit_context);
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ group = dsdb_audit_get_primary_dn(request);
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL)) {
+ char *message = NULL;
+ message = audit_group_human_readable(
+ ctx,
+ module,
+ request,
+ action,
+ user,
+ group,
+ status);
+ audit_log_human_text(
+ AUDIT_HR_TAG,
+ message,
+ DBGC_DSDB_GROUP_AUDIT,
+ GROUP_LOG_LVL);
+ TALLOC_FREE(message);
+ }
+
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
+ (ac->msg_ctx && ac->send_events)) {
+ struct json_object json;
+ json = audit_group_json(
+ module, request, action, user, group, event_id, status);
+ audit_log_json(
+ &json,
+ DBGC_DSDB_GROUP_AUDIT_JSON,
+ GROUP_LOG_LVL);
+ if (ac->send_events) {
+ audit_message_send(
+ ac->msg_ctx,
+ DSDB_GROUP_EVENT_NAME,
+ MSG_GROUP_LOG,
+ &json);
+ }
+ json_free(&json);
+ }
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * @brief Get the windows event type id for removing a user from a group type.
+ *
+ * @param group_type the type of the current group, see libds/common/flags.h
+ *
+ * @return the Windows Event Id
+ *
+ */
+static enum event_id_type get_remove_member_event(uint32_t group_type)
+{
+
+ switch (group_type) {
+ case GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
+ return EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP;
+ case GTYPE_SECURITY_GLOBAL_GROUP:
+ return EVT_ID_USER_REMOVED_FROM_GLOBAL_SEC_GROUP;
+ case GTYPE_SECURITY_DOMAIN_LOCAL_GROUP:
+ return EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP;
+ case GTYPE_SECURITY_UNIVERSAL_GROUP:
+ return EVT_ID_USER_REMOVED_FROM_UNIVERSAL_SEC_GROUP;
+ case GTYPE_DISTRIBUTION_GLOBAL_GROUP:
+ return EVT_ID_USER_REMOVED_FROM_GLOBAL_GROUP;
+ case GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP:
+ return EVT_ID_USER_REMOVED_FROM_LOCAL_GROUP;
+ case GTYPE_DISTRIBUTION_UNIVERSAL_GROUP:
+ return EVT_ID_USER_REMOVED_FROM_UNIVERSAL_GROUP;
+ default:
+ return EVT_ID_NONE;
+ }
+}
+
+/*
+ * @brief Get the windows event type id for adding a user to a group type.
+ *
+ * @param group_type the type of the current group, see libds/common/flags.h
+ *
+ * @return the Windows Event Id
+ *
+ */
+static enum event_id_type get_add_member_event(uint32_t group_type)
+{
+
+ switch (group_type) {
+ case GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
+ return EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP;
+ case GTYPE_SECURITY_GLOBAL_GROUP:
+ return EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP;
+ case GTYPE_SECURITY_DOMAIN_LOCAL_GROUP:
+ return EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP;
+ case GTYPE_SECURITY_UNIVERSAL_GROUP:
+ return EVT_ID_USER_ADDED_TO_UNIVERSAL_SEC_GROUP;
+ case GTYPE_DISTRIBUTION_GLOBAL_GROUP:
+ return EVT_ID_USER_ADDED_TO_GLOBAL_GROUP;
+ case GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP:
+ return EVT_ID_USER_ADDED_TO_LOCAL_GROUP;
+ case GTYPE_DISTRIBUTION_UNIVERSAL_GROUP:
+ return EVT_ID_USER_ADDED_TO_UNIVERSAL_GROUP;
+ default:
+ return EVT_ID_NONE;
+ }
+}
+
+/*
+ * @brief Log all the changes to a users group membership.
+ *
+ * Log details of a change to a users group memberships, except for changes
+ * to their primary group which is handled by log_primary_group_change.
+ *
+ * @param module The ldb module.
+ * @param request The request being logged.
+ * @param action Description of the action being performed.
+ * @param user The linearized form of the users DN
+ * @param status the LDB status code for the processing of the request.
+ *
+ */
+static void log_membership_changes(struct ldb_module *module,
+ const struct ldb_request *request,
+ struct ldb_message_element *el,
+ struct ldb_message_element *old_el,
+ uint32_t group_type,
+ int status)
+{
+ unsigned int i, old_i, new_i;
+ unsigned int old_num_values;
+ unsigned int max_num_values;
+ unsigned int new_num_values;
+ struct parsed_dn *old_val = NULL;
+ struct parsed_dn *new_val = NULL;
+ struct parsed_dn *new_values = NULL;
+ struct parsed_dn *old_values = NULL;
+ struct ldb_context *ldb = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ old_num_values = old_el ? old_el->num_values : 0;
+ new_num_values = el ? el->num_values : 0;
+ max_num_values = old_num_values + new_num_values;
+
+ if (max_num_values == 0) {
+ /*
+ * There is nothing to do!
+ */
+ TALLOC_FREE(ctx);
+ return;
+ }
+
+ old_values = get_parsed_dns(ctx, old_el);
+ new_values = get_parsed_dns(ctx, el);
+ ldb = ldb_module_get_ctx(module);
+
+ old_i = 0;
+ new_i = 0;
+ for (i = 0; i < max_num_values; i++) {
+ enum dn_compare_result cmp;
+ if (old_i < old_num_values && new_i < new_num_values) {
+ /*
+ * Both list have values, so compare the values
+ */
+ old_val = &old_values[old_i];
+ new_val = &new_values[new_i];
+ cmp = dn_compare(ctx, ldb, old_val, new_val);
+ } else if (old_i < old_num_values) {
+ /*
+ * the new list is empty, read the old list
+ */
+ old_val = &old_values[old_i];
+ new_val = NULL;
+ cmp = LESS_THAN;
+ } else if (new_i < new_num_values) {
+ /*
+ * the old list is empty, read new list
+ */
+ old_val = NULL;
+ new_val = &new_values[new_i];
+ cmp = GREATER_THAN;
+ } else {
+ break;
+ }
+
+ if (cmp == LESS_THAN) {
+ /*
+ * Have an entry in the original record that is not in
+ * the new record. So it's been deleted
+ */
+ const char *user = NULL;
+ enum event_id_type event_id;
+ if (old_val->dsdb_dn == NULL) {
+ really_parse_trusted_dn(
+ ctx,
+ ldb,
+ old_val,
+ LDB_SYNTAX_DN);
+ }
+ user = ldb_dn_get_linearized(old_val->dsdb_dn->dn);
+ event_id = get_remove_member_event(group_type);
+ log_membership_change(
+ module, request, "Removed", user, event_id, status);
+ old_i++;
+ } else if (cmp == BINARY_EQUAL) {
+ /*
+ * DN's unchanged at binary level so nothing to do.
+ */
+ old_i++;
+ new_i++;
+ } else if (cmp == EQUAL) {
+ /*
+ * DN is unchanged now need to check the flags to
+ * determine if a record has been deleted or undeleted
+ */
+ uint32_t old_flags;
+ uint32_t new_flags;
+ if (old_val->dsdb_dn == NULL) {
+ really_parse_trusted_dn(
+ ctx,
+ ldb,
+ old_val,
+ LDB_SYNTAX_DN);
+ }
+ if (new_val->dsdb_dn == NULL) {
+ really_parse_trusted_dn(
+ ctx,
+ ldb,
+ new_val,
+ LDB_SYNTAX_DN);
+ }
+
+ dsdb_get_extended_dn_uint32(
+ old_val->dsdb_dn->dn,
+ &old_flags,
+ "RMD_FLAGS");
+ dsdb_get_extended_dn_uint32(
+ new_val->dsdb_dn->dn,
+ &new_flags,
+ "RMD_FLAGS");
+ if (new_flags == old_flags) {
+ /*
+ * No changes to the Repl meta data so can
+ * no need to log the change
+ */
+ old_i++;
+ new_i++;
+ continue;
+ }
+ if (new_flags & DSDB_RMD_FLAG_DELETED) {
+ /*
+ * DN has been deleted.
+ */
+ const char *user = NULL;
+ enum event_id_type event_id;
+ user = ldb_dn_get_linearized(
+ old_val->dsdb_dn->dn);
+ event_id = get_remove_member_event(group_type);
+ log_membership_change(module,
+ request,
+ "Removed",
+ user,
+ event_id,
+ status);
+ } else {
+ /*
+ * DN has been re-added
+ */
+ const char *user = NULL;
+ enum event_id_type event_id;
+ user = ldb_dn_get_linearized(
+ new_val->dsdb_dn->dn);
+ event_id = get_add_member_event(group_type);
+ log_membership_change(module,
+ request,
+ "Added",
+ user,
+ event_id,
+ status);
+ }
+ old_i++;
+ new_i++;
+ } else {
+ /*
+ * Member in the updated record that's not in the
+ * original, so it must have been added.
+ */
+ const char *user = NULL;
+ enum event_id_type event_id;
+ if ( new_val->dsdb_dn == NULL) {
+ really_parse_trusted_dn(
+ ctx,
+ ldb,
+ new_val,
+ LDB_SYNTAX_DN);
+ }
+ user = ldb_dn_get_linearized(new_val->dsdb_dn->dn);
+ event_id = get_add_member_event(group_type);
+ log_membership_change(
+ module, request, "Added", user, event_id, status);
+ new_i++;
+ }
+ }
+
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * @brief log a group change message for a newly added user.
+ *
+ * When a user is added we need to generate a GroupChange Add message to
+ * log that the user has been added to their PrimaryGroup
+ */
+static void log_new_user_added_to_primary_group(
+ TALLOC_CTX *ctx,
+ struct audit_callback_context *acc,
+ const char *group,
+ const int status)
+{
+ uint32_t group_type;
+ enum event_id_type event_id = EVT_ID_NONE;
+ struct ldb_result *res = NULL;
+ struct ldb_dn *group_dn = NULL;
+ struct ldb_context *ldb = NULL;
+ int ret;
+
+ ldb = ldb_module_get_ctx(acc->module);
+ group_dn = ldb_dn_new(ctx, ldb, group);
+ ret = dsdb_module_search_dn(acc->module,
+ ctx,
+ &res,
+ group_dn,
+ group_type_attr,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_REVEAL_INTERNALS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ NULL);
+ if (ret == LDB_SUCCESS) {
+ const char *user = NULL;
+ group_type =
+ ldb_msg_find_attr_as_uint(res->msgs[0], "groupType", 0);
+ event_id = get_add_member_event(group_type);
+ user = dsdb_audit_get_primary_dn(acc->request);
+ log_membership_change(
+ acc->module, acc->request, "Added", user, event_id, status);
+ }
+}
+
+/*
+ * @brief Log the details of a primary group change.
+ *
+ * Retrieve the users primary groupo after the operation has completed
+ * and call log_primary_group_change to log the actual changes.
+ *
+ * @param acc details of the primary group before the operation.
+ * @param status The status code returned by the operation.
+ *
+ * @return an LDB status code.
+ */
+static void log_user_primary_group_change(
+ struct audit_callback_context *acc,
+ const int status)
+{
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ uint32_t new_rid = UINT32_MAX;
+ struct dom_sid *account_sid = NULL;
+ int ret;
+ const struct ldb_message *msg = dsdb_audit_get_message(acc->request);
+
+ if (status == LDB_SUCCESS && msg != NULL) {
+ struct ldb_result *res = NULL;
+ ret = dsdb_module_search_dn(
+ acc->module,
+ ctx,
+ &res,
+ msg->dn,
+ primary_group_attr,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_REVEAL_INTERNALS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ NULL);
+ if (ret == LDB_SUCCESS) {
+ new_rid = ldb_msg_find_attr_as_uint(
+ msg,
+ "primaryGroupID",
+ ~0);
+ account_sid = samdb_result_dom_sid(
+ ctx,
+ res->msgs[0],
+ "objectSid");
+ }
+ }
+ /*
+ * If we don't have a new value then the user has been deleted
+ * which we currently do not log.
+ * Otherwise only log if the primary group has actually changed.
+ */
+ if (account_sid != NULL &&
+ new_rid != UINT32_MAX &&
+ acc->primary_group != new_rid) {
+ const char* group = get_primary_group_dn(
+ ctx,
+ acc->module,
+ account_sid,
+ new_rid);
+ log_primary_group_change(
+ acc->module,
+ acc->request,
+ "PrimaryGroup",
+ group,
+ status);
+ /*
+ * Are we adding a new user with the primaryGroupID
+ * set. If so and we're generating JSON audit logs, will need to
+ * generate an "Add" message with the appropriate windows
+ * event id.
+ */
+ if (acc->request->operation == LDB_ADD) {
+ log_new_user_added_to_primary_group(
+ ctx, acc, group, status);
+ }
+ }
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * @brief log the changes to users group membership.
+ *
+ * Retrieve the users group memberships after the operation has completed
+ * and call log_membership_changes to log the actual changes.
+ *
+ * @param acc details of the group memberships before the operation.
+ * @param status The status code returned by the operation.
+ *
+ */
+static void log_group_membership_changes(
+ struct audit_callback_context *acc,
+ const int status)
+{
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ struct ldb_message_element *new_val = NULL;
+ int ret;
+ uint32_t group_type = 0;
+ const struct ldb_message *msg = dsdb_audit_get_message(acc->request);
+ if (status == LDB_SUCCESS && msg != NULL) {
+ struct ldb_result *res = NULL;
+ ret = dsdb_module_search_dn(
+ acc->module,
+ ctx,
+ &res,
+ msg->dn,
+ group_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_REVEAL_INTERNALS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ NULL);
+ if (ret == LDB_SUCCESS) {
+ new_val = ldb_msg_find_element(res->msgs[0], "member");
+ group_type = ldb_msg_find_attr_as_uint(
+ res->msgs[0], "groupType", 0);
+ log_membership_changes(acc->module,
+ acc->request,
+ new_val,
+ acc->members,
+ group_type,
+ status);
+ TALLOC_FREE(ctx);
+ return;
+ }
+ }
+ /*
+ * If we get here either
+ * one of the lower level modules failed and the group record did
+ * not get updated
+ * or
+ * the updated group record could not be read.
+ *
+ * In both cases it does not make sense to log individual membership
+ * changes so we log a group membership change "Failure" message.
+ *
+ */
+ log_membership_change(acc->module,
+ acc->request,
+ "Failure",
+ "",
+ EVT_ID_NONE,
+ status);
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * @brief call back function to log changes to the group memberships.
+ *
+ * Call back function to log changes to the uses broup memberships.
+ *
+ * @param req the ldb request.
+ * @param ares the ldb result
+ *
+ * @return am LDB status code.
+ */
+static int group_audit_callback(
+ struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct audit_callback_context *ac = NULL;
+
+ ac = talloc_get_type(
+ req->context,
+ struct audit_callback_context);
+
+ if (!ares) {
+ return ldb_module_done(
+ ac->request, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* pass on to the callback */
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ return ldb_module_send_entry(
+ ac->request,
+ ares->message,
+ ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(
+ ac->request,
+ ares->referral);
+
+ case LDB_REPLY_DONE:
+ /*
+ * Log on DONE now we have a result code
+ */
+ ac->log_changes(ac, ares->error);
+ return ldb_module_done(
+ ac->request,
+ ares->controls,
+ ares->response,
+ ares->error);
+ break;
+
+ default:
+ /* Can't happen */
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+}
+
+/*
+ * @brief Does this request change the primary group.
+ *
+ * Does the request change the primary group, i.e. does it contain the
+ * primaryGroupID attribute.
+ *
+ * @param req the request to examine.
+ *
+ * @return True if the request modifies the primary group.
+ */
+static bool has_primary_group_id(struct ldb_request *req)
+{
+ struct ldb_message_element *el = NULL;
+ const struct ldb_message *msg = NULL;
+
+ msg = dsdb_audit_get_message(req);
+ el = ldb_msg_find_element(msg, "primaryGroupID");
+
+ return (el != NULL);
+}
+
+/*
+ * @brief Does this request change group membership.
+ *
+ * Does the request change the ses group memberships, i.e. does it contain the
+ * member attribute.
+ *
+ * @param req the request to examine.
+ *
+ * @return True if the request modifies the users group memberships.
+ */
+static bool has_group_membership_changes(struct ldb_request *req)
+{
+ struct ldb_message_element *el = NULL;
+ const struct ldb_message *msg = NULL;
+
+ msg = dsdb_audit_get_message(req);
+ el = ldb_msg_find_element(msg, "member");
+
+ return (el != NULL);
+}
+
+
+
+/*
+ * @brief Install the callback function to log an add request.
+ *
+ * Install the callback function to log an add request changing the users
+ * group memberships. As we want to log the returned status code, we need to
+ * register a callback function that will be called once the operation has
+ * completed.
+ *
+ * This function reads the current user record so that we can log the before
+ * and after state.
+ *
+ * @param module The ldb module.
+ * @param req The modify request.
+ *
+ * @return and LDB status code.
+ */
+static int set_group_membership_add_callback(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct audit_callback_context *context = NULL;
+ struct ldb_request *new_req = NULL;
+ struct ldb_context *ldb = NULL;
+ int ret;
+ /*
+ * Adding group memberships so will need to log the changes.
+ */
+ ldb = ldb_module_get_ctx(module);
+ context = talloc_zero(req, struct audit_callback_context);
+
+ if (context == NULL) {
+ return ldb_oom(ldb);
+ }
+ context->request = req;
+ context->module = module;
+ context->log_changes = log_group_membership_changes;
+ /*
+ * We want to log the return code status, so we need to register
+ * a callback function to get the actual result.
+ * We need to take a new copy so that we don't alter the callers copy
+ */
+ ret = ldb_build_add_req(
+ &new_req,
+ ldb,
+ req,
+ req->op.add.message,
+ req->controls,
+ context,
+ group_audit_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(module, new_req);
+}
+
+
+/*
+ * @brief Install the callback function to log a modify request.
+ *
+ * Install the callback function to log a modify request changing the primary
+ * group . As we want to log the returned status code, we need to register a
+ * callback function that will be called once the operation has completed.
+ *
+ * This function reads the current user record so that we can log the before
+ * and after state.
+ *
+ * @param module The ldb module.
+ * @param req The modify request.
+ *
+ * @return and LDB status code.
+ */
+static int set_primary_group_modify_callback(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct audit_callback_context *context = NULL;
+ struct ldb_request *new_req = NULL;
+ struct ldb_context *ldb = NULL;
+ const struct ldb_message *msg = NULL;
+ struct ldb_result *res = NULL;
+ int ret;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_module_get_ctx(module);
+
+ context = talloc_zero(req, struct audit_callback_context);
+ if (context == NULL) {
+ ret = ldb_oom(ldb);
+ goto exit;
+ }
+ context->request = req;
+ context->module = module;
+ context->log_changes = log_user_primary_group_change;
+
+ msg = dsdb_audit_get_message(req);
+ ret = dsdb_module_search_dn(
+ module,
+ ctx,
+ &res,
+ msg->dn,
+ primary_group_attr,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_REVEAL_INTERNALS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ NULL);
+ if (ret == LDB_SUCCESS) {
+ uint32_t pg;
+ pg = ldb_msg_find_attr_as_uint(
+ res->msgs[0],
+ "primaryGroupID",
+ ~0);
+ context->primary_group = pg;
+ }
+ /*
+ * We want to log the return code status, so we need to register
+ * a callback function to get the actual result.
+ * We need to take a new copy so that we don't alter the callers copy
+ */
+ ret = ldb_build_mod_req(
+ &new_req,
+ ldb,
+ req,
+ req->op.add.message,
+ req->controls,
+ context,
+ group_audit_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ goto exit;
+ }
+ ret = ldb_next_request(module, new_req);
+exit:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+/*
+ * @brief Install the callback function to log an add request.
+ *
+ * Install the callback function to log an add request changing the primary
+ * group . As we want to log the returned status code, we need to register a
+ * callback function that will be called once the operation has completed.
+ *
+ * This function reads the current user record so that we can log the before
+ * and after state.
+ *
+ * @param module The ldb module.
+ * @param req The modify request.
+ *
+ * @return and LDB status code.
+ */
+static int set_primary_group_add_callback(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct audit_callback_context *context = NULL;
+ struct ldb_request *new_req = NULL;
+ struct ldb_context *ldb = NULL;
+ int ret;
+ /*
+ * Adding a user with a primary group.
+ */
+ ldb = ldb_module_get_ctx(module);
+ context = talloc_zero(req, struct audit_callback_context);
+
+ if (context == NULL) {
+ return ldb_oom(ldb);
+ }
+ context->request = req;
+ context->module = module;
+ context->log_changes = log_user_primary_group_change;
+ /*
+ * We want to log the return code status, so we need to register
+ * a callback function to get the actual result.
+ * We need to take a new copy so that we don't alter the callers copy
+ */
+ ret = ldb_build_add_req(
+ &new_req,
+ ldb,
+ req,
+ req->op.add.message,
+ req->controls,
+ context,
+ group_audit_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(module, new_req);
+}
+
+/*
+ * @brief Module handler for add operations.
+ *
+ * Inspect the current add request, and if needed log any group membership
+ * changes.
+ *
+ * @param module The ldb module.
+ * @param req The modify request.
+ *
+ * @return and LDB status code.
+ */
+static int group_add(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+
+ struct audit_context *ac =
+ talloc_get_type(
+ ldb_module_get_private(module),
+ struct audit_context);
+ /*
+ * Currently we don't log replicated group changes
+ */
+ if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
+ return ldb_next_request(module, req);
+ }
+
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL) ||
+ CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
+ (ac->msg_ctx && ac->send_events)) {
+ /*
+ * Avoid the overheads of logging unless it has been
+ * enabled
+ */
+ if (has_group_membership_changes(req)) {
+ return set_group_membership_add_callback(module, req);
+ }
+ if (has_primary_group_id(req)) {
+ return set_primary_group_add_callback(module, req);
+ }
+ }
+ return ldb_next_request(module, req);
+}
+
+/*
+ * @brief Module handler for delete operations.
+ *
+ * Currently there is no logging for delete operations.
+ *
+ * @param module The ldb module.
+ * @param req The modify request.
+ *
+ * @return and LDB status code.
+ */
+static int group_delete(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+ return ldb_next_request(module, req);
+}
+
+/*
+ * @brief Install the callback function to log a modify request.
+ *
+ * Install the callback function to log a modify request. As we want to log the
+ * returned status code, we need to register a callback function that will be
+ * called once the operation has completed.
+ *
+ * This function reads the current user record so that we can log the before
+ * and after state.
+ *
+ * @param module The ldb module.
+ * @param req The modify request.
+ *
+ * @return and LDB status code.
+ */
+static int set_group_modify_callback(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct audit_callback_context *context = NULL;
+ struct ldb_request *new_req = NULL;
+ struct ldb_context *ldb = NULL;
+ struct ldb_result *res = NULL;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+ context = talloc_zero(req, struct audit_callback_context);
+
+ if (context == NULL) {
+ return ldb_oom(ldb);
+ }
+ context->request = req;
+ context->module = module;
+ context->log_changes = log_group_membership_changes;
+
+ /*
+ * About to change the group memberships need to read
+ * the current state from the database.
+ */
+ ret = dsdb_module_search_dn(
+ module,
+ context,
+ &res,
+ req->op.add.message->dn,
+ group_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_REVEAL_INTERNALS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ NULL);
+ if (ret == LDB_SUCCESS) {
+ context->members = ldb_msg_find_element(res->msgs[0], "member");
+ }
+
+ ret = ldb_build_mod_req(
+ &new_req,
+ ldb,
+ req,
+ req->op.mod.message,
+ req->controls,
+ context,
+ group_audit_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(module, new_req);
+}
+
+/*
+ * @brief Module handler for modify operations.
+ *
+ * Inspect the current modify request, and if needed log any group membership
+ * changes.
+ *
+ * @param module The ldb module.
+ * @param req The modify request.
+ *
+ * @return and LDB status code.
+ */
+static int group_modify(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+
+ struct audit_context *ac =
+ talloc_get_type(
+ ldb_module_get_private(module),
+ struct audit_context);
+ /*
+ * Currently we don't log replicated group changes
+ */
+ if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
+ return ldb_next_request(module, req);
+ }
+
+ if (CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT, GROUP_LOG_LVL) ||
+ CHECK_DEBUGLVLC(DBGC_DSDB_GROUP_AUDIT_JSON, GROUP_LOG_LVL) ||
+ (ac->msg_ctx && ac->send_events)) {
+ /*
+ * Avoid the overheads of logging unless it has been
+ * enabled
+ */
+ if (has_group_membership_changes(req)) {
+ return set_group_modify_callback(module, req);
+ }
+ if (has_primary_group_id(req)) {
+ return set_primary_group_modify_callback(module, req);
+ }
+ }
+ return ldb_next_request(module, req);
+}
+
+/*
+ * @brief ldb module initialisation
+ *
+ * Initialise the module, loading the private data etc.
+ *
+ * @param module The ldb module to initialise.
+ *
+ * @return An LDB status code.
+ */
+static int group_init(struct ldb_module *module)
+{
+
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct audit_context *context = NULL;
+ struct loadparm_context *lp_ctx
+ = talloc_get_type_abort(
+ ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+ struct tevent_context *ev = ldb_get_event_context(ldb);
+
+ context = talloc_zero(module, struct audit_context);
+ if (context == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ if (lp_ctx && lpcfg_dsdb_group_change_notification(lp_ctx)) {
+ context->send_events = true;
+ context->msg_ctx = imessaging_client_init(context,
+ lp_ctx,
+ ev);
+ }
+
+ ldb_module_set_private(module, context);
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_group_audit_log_module_ops = {
+ .name = "group_audit_log",
+ .add = group_add,
+ .modify = group_modify,
+ .del = group_delete,
+ .init_context = group_init,
+};
+
+int ldb_group_audit_log_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_group_audit_log_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/instancetype.c b/source4/dsdb/samdb/ldb_modules/instancetype.c
new file mode 100644
index 0000000..9a3fd11
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/instancetype.c
@@ -0,0 +1,173 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2004-2008
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 2005
+ 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 instancetype module
+ *
+ * Description: add an instanceType onto every new record
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb.h"
+#include "ldb_module.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "dsdb/samdb/samdb.h"
+#include "../libds/common/flags.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+/* add_record: add instancetype attribute */
+static int instancetype_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ struct ldb_message_element *el;
+ uint32_t instanceType;
+ int ret;
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "instancetype_add\n");
+
+ el = ldb_msg_find_element(req->op.add.message, "instanceType");
+ if (el != NULL) {
+ if (el->num_values != 1) {
+ ldb_set_errstring(ldb, "instancetype: the 'instanceType' attribute is single-valued!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ instanceType = ldb_msg_find_attr_as_uint(req->op.add.message,
+ "instanceType", 0);
+ if (!(instanceType & INSTANCE_TYPE_IS_NC_HEAD)) {
+ /*
+ * If we have no NC add operation (no TYPE_IS_NC_HEAD)
+ * then "instanceType" can only be "0" or "TYPE_WRITE".
+ */
+ if ((instanceType != 0) &&
+ ((instanceType & INSTANCE_TYPE_WRITE) == 0)) {
+ ldb_set_errstring(ldb, "instancetype: if TYPE_IS_NC_HEAD wasn't set, then only TYPE_WRITE or 0 are allowed!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ } else {
+ /*
+ * If we have a NC add operation then we need also the
+ * "TYPE_WRITE" flag in order to succeed,
+ * unless this NC is not instantiated
+ */
+ if (ldb_request_get_control(req, DSDB_CONTROL_PARTIAL_REPLICA)) {
+ if (!(instanceType & INSTANCE_TYPE_UNINSTANT)) {
+ ldb_set_errstring(ldb, "instancetype: if TYPE_IS_NC_HEAD "
+ "was set, and we are creating a new NC "
+ "over DsAddEntry then also TYPE_UNINSTANT is requested!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ } else {
+ if (!(instanceType & INSTANCE_TYPE_WRITE)) {
+ ldb_set_errstring(ldb, "instancetype: if TYPE_IS_NC_HEAD "
+ "was set, then also TYPE_WRITE is requested!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+
+ /*
+ * TODO: Confirm we are naming master or start
+ * a remote call to the naming master to
+ * create the crossRef object
+ */
+ }
+
+ /* we did only tests, so proceed with the original request */
+ return ldb_next_request(module, req);
+ }
+
+ /* we have to copy the message as the caller might have it as a const */
+ msg = ldb_msg_copy_shallow(req, req->op.add.message);
+ if (msg == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /*
+ * TODO: calculate correct instance type
+ */
+ instanceType = INSTANCE_TYPE_WRITE;
+
+ ret = samdb_msg_add_uint(ldb, msg, msg, "instanceType", instanceType);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_build_add_req(&down_req, ldb, req,
+ msg,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* go on with the call chain */
+ return ldb_next_request(module, down_req);
+}
+
+/* deny instancetype modification */
+static int instancetype_mod(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_message_element *el;
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "instancetype_mod\n");
+
+ el = ldb_msg_find_element(req->op.mod.message, "instanceType");
+ if (el != NULL) {
+ /* Except to allow dbcheck to fix things, this must never be modified */
+ if (!ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) {
+ ldb_set_errstring(ldb, "instancetype: the 'instanceType' attribute can never be changed!");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+ return ldb_next_request(module, req);
+}
+
+static const struct ldb_module_ops ldb_instancetype_module_ops = {
+ .name = "instancetype",
+ .add = instancetype_add,
+ .modify = instancetype_mod
+};
+
+int ldb_instancetype_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_instancetype_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/lazy_commit.c b/source4/dsdb/samdb/ldb_modules/lazy_commit.c
new file mode 100644
index 0000000..24fc6dd
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/lazy_commit.c
@@ -0,0 +1,128 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009
+
+ 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 lazy_commit module
+ *
+ * Description: module to pretend to support the 'lazy commit' control
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+static int unlazy_op(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+ struct ldb_request *new_req;
+ struct ldb_control *control = ldb_request_get_control(req, LDB_CONTROL_SERVER_LAZY_COMMIT);
+ if (!control) {
+ return ldb_next_request(module, req);
+ }
+
+ switch (req->operation) {
+ case LDB_SEARCH:
+ ret = ldb_build_search_req_ex(&new_req, ldb_module_get_ctx(module),
+ req,
+ req->op.search.base,
+ req->op.search.scope,
+ req->op.search.tree,
+ req->op.search.attrs,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(new_req);
+ break;
+ case LDB_ADD:
+ ret = ldb_build_add_req(&new_req, ldb_module_get_ctx(module), req,
+ req->op.add.message,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(new_req);
+ break;
+ case LDB_MODIFY:
+ ret = ldb_build_mod_req(&new_req, ldb_module_get_ctx(module), req,
+ req->op.mod.message,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(new_req);
+ break;
+ case LDB_DELETE:
+ ret = ldb_build_del_req(&new_req, ldb_module_get_ctx(module), req,
+ req->op.del.dn,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(new_req);
+ break;
+ case LDB_RENAME:
+ ret = ldb_build_rename_req(&new_req, ldb_module_get_ctx(module), req,
+ req->op.rename.olddn,
+ req->op.rename.newdn,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(new_req);
+ break;
+ case LDB_EXTENDED:
+ ret = ldb_build_extended_req(&new_req, ldb_module_get_ctx(module),
+ req,
+ req->op.extended.oid,
+ req->op.extended.data,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(new_req);
+ break;
+ default:
+ ldb_set_errstring(ldb_module_get_ctx(module),
+ "Unsupported request type!");
+ ret = LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ control->critical = 0;
+ return ldb_next_request(module, new_req);
+}
+
+static const struct ldb_module_ops ldb_lazy_commit_module_ops = {
+ .name = "lazy_commit",
+ .search = unlazy_op,
+ .add = unlazy_op,
+ .modify = unlazy_op,
+ .del = unlazy_op,
+ .rename = unlazy_op,
+ .request = unlazy_op,
+ .extended = unlazy_op,
+};
+
+int ldb_lazy_commit_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_lazy_commit_module_ops);
+}
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 0000000..317df9d
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/linked_attributes.c
@@ -0,0 +1,1581 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007
+ Copyright (C) Simo Sorce <idra@samba.org> 2008
+ Copyright (C) Matthieu Patou <mat@matws.net> 2011
+ Copyright (C) Andrew Tridgell 2009
+
+ 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 (i.e. forward-links
+ * and backlinks) remain in sync.
+ *
+ * Backlinks are 'plain' links (without extra metadata). When the link target
+ * object is modified (e.g. renamed), we use the backlinks to keep the link
+ * source object updated. Note there are some cases where we can't do this:
+ * - one-way links, which don't have a corresponding backlink
+ * - two-way deactivated links, i.e. when a user is removed from a group,
+ * the forward 'member' link still exists (but is inactive), however, the
+ * 'memberOf' backlink is deleted.
+ * In these cases, we can end up with a dangling forward link which is
+ * incorrect (i.e. the target has been renamed or deleted). We have dbcheck
+ * rules to detect and fix this, and cope otherwise by filtering at runtime
+ * (i.e. in the extended_dn module).
+ *
+ * See also repl_meta_data.c, which handles updating links for deleted
+ * objects, as well as link changes received from another DC.
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "util/dlinklist.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+#undef strcasecmp
+
+struct la_private_transaction {
+ struct la_context *la_list;
+};
+
+
+struct la_private {
+ struct la_private_transaction *transaction;
+ bool sorted_links;
+};
+
+struct la_op_store {
+ struct la_op_store *next;
+ struct la_op_store *prev;
+ enum la_op {LA_OP_ADD, LA_OP_DEL} op;
+ struct GUID guid;
+ char *name;
+};
+
+struct replace_context {
+ struct la_context *ac;
+ unsigned int num_elements;
+ struct ldb_message_element *el;
+};
+
+struct la_context {
+ struct la_context *next, *prev;
+ const struct dsdb_schema *schema;
+ struct ldb_module *module;
+ struct ldb_request *req;
+ struct ldb_dn *mod_dn;
+ struct replace_context *rc;
+ struct la_op_store *ops;
+ struct ldb_extended *op_response;
+ struct ldb_control **op_controls;
+ /*
+ * For futur use
+ * will tell which GC to use for resolving links
+ */
+ char *gc_dns_name;
+};
+
+
+static int handle_verify_name_control(TALLOC_CTX *ctx, struct ldb_context *ldb,
+ struct ldb_control *control, struct la_context *ac)
+{
+ /*
+ * If we are a GC let's remove the control,
+ * if there is a specified GC check that is us.
+ */
+ struct ldb_verify_name_control *lvnc = talloc_get_type_abort(control->data, struct ldb_verify_name_control);
+ if (samdb_is_gc(ldb)) {
+ /* Because we can't easily talloc a struct ldb_dn*/
+ struct ldb_dn **dn = talloc_array(ctx, struct ldb_dn *, 1);
+ int ret = samdb_server_reference_dn(ldb, ctx, dn);
+ const char *dns;
+
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ dns = samdb_dn_to_dnshostname(ldb, ctx, *dn);
+ if (!dns) {
+ return ldb_operr(ldb);
+ }
+ if (!lvnc->gc || strcasecmp(dns, lvnc->gc) == 0) {
+ if (!ldb_save_controls(control, ctx, NULL)) {
+ return ldb_operr(ldb);
+ }
+ } else {
+ control->critical = true;
+ }
+ talloc_free(dn);
+ } else {
+ /* For the moment we don't remove the control is this case in order
+ * to fail the request. It's better than having the client thinking
+ * that we honnor its control.
+ * Hopefully only a very small set of usecase should hit this problem.
+ */
+ if (lvnc->gc) {
+ ac->gc_dns_name = talloc_strdup(ac, lvnc->gc);
+ }
+ control->critical = true;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static struct la_context *linked_attributes_init(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct la_context *ac;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = talloc_zero(req, struct la_context);
+ if (ac == NULL) {
+ ldb_oom(ldb);
+ return NULL;
+ }
+
+ ac->schema = dsdb_get_schema(ldb, ac);
+ ac->module = module;
+ ac->req = req;
+
+ return ac;
+}
+
+/*
+ turn a DN into a GUID
+ */
+static int la_guid_from_dn(struct ldb_module *module,
+ struct ldb_request *parent,
+ struct ldb_dn *dn, struct GUID *guid)
+{
+ NTSTATUS status;
+ int ret;
+
+ status = dsdb_get_extended_dn_guid(dn, guid, "GUID");
+ if (NT_STATUS_IS_OK(status)) {
+ return LDB_SUCCESS;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ DEBUG(4,(__location__ ": Unable to parse GUID for dn %s\n",
+ ldb_dn_get_linearized(dn)));
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ ret = dsdb_module_guid_by_dn(module, dn, guid, parent);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(4,(__location__ ": Failed to find GUID for dn %s\n",
+ ldb_dn_get_linearized(dn)));
+ return ret;
+ }
+ return LDB_SUCCESS;
+}
+
+
+/* Common routine to handle reading the attributes and creating a
+ * series of modify requests */
+static int la_store_op(struct la_context *ac,
+ enum la_op op,
+ const struct dsdb_attribute *schema_attr,
+ struct ldb_val *dn,
+ const char *name)
+{
+ struct ldb_context *ldb;
+ struct la_op_store *os;
+ struct ldb_dn *op_dn;
+ struct dsdb_dn *dsdb_dn;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+
+ os = talloc_zero(ac, struct la_op_store);
+ if (!os) {
+ return ldb_oom(ldb);
+ }
+
+ dsdb_dn = dsdb_dn_parse(os, ldb, dn, schema_attr->syntax->ldap_oid);
+
+ if (!dsdb_dn) {
+ ldb_asprintf_errstring(ldb,
+ "could not parse attribute as a DN");
+ TALLOC_FREE(os);
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+
+ op_dn = dsdb_dn->dn;
+
+ os->op = op;
+
+ ret = la_guid_from_dn(ac->module, ac->req, op_dn, &os->guid);
+ talloc_free(op_dn);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT && ac->req->operation == LDB_DELETE) {
+ /* we are deleting an object, and we've found it has a
+ * forward link to a target that no longer
+ * exists. This is not an error in the delete, and we
+ * should just not do the deferred delete of the
+ * target attribute
+ */
+ talloc_free(os);
+ return LDB_SUCCESS;
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ os->name = talloc_strdup(os, name);
+ if (!os->name) {
+ return ldb_oom(ldb);
+ }
+
+ /* Do deletes before adds */
+ if (op == LA_OP_ADD) {
+ DLIST_ADD_END(ac->ops, os);
+ } else {
+ /* By adding to the head of the list, we do deletes before
+ * adds when processing a replace */
+ DLIST_ADD(ac->ops, os);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int la_queue_mod_request(struct la_context *ac);
+static int la_down_req(struct la_context *ac);
+
+
+
+/* add */
+static int linked_attributes_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ const struct dsdb_attribute *target_attr;
+ struct la_context *ac;
+ const char *attr_name;
+ struct ldb_control *ctrl;
+ unsigned int i, j;
+ struct ldb_control *control;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ ac = linked_attributes_init(module, req);
+ if (!ac) {
+ return ldb_operr(ldb);
+ }
+
+ control = ldb_request_get_control(req, LDB_CONTROL_VERIFY_NAME_OID);
+ if (control != NULL && control->data != NULL) {
+ ret = handle_verify_name_control(req, ldb, control, ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+ }
+
+ if (!(ctrl = ldb_request_get_control(req, DSDB_CONTROL_APPLY_LINKS))) {
+ /* don't do anything special for linked attributes, repl_meta_data has done it */
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+ ctrl->critical = false;
+
+ if (!ac->schema) {
+ /* without schema, this doesn't make any sense */
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+
+
+ /* 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(ac->schema, el->name);
+ if (!schema_attr) {
+ ldb_asprintf_errstring(ldb,
+ "%s: attribute %s is not a valid attribute in schema",
+ __FUNCTION__,
+ el->name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* this could be a link with no partner, in which case
+ there is no special work to do */
+ if (schema_attr->linkID == 0) {
+ continue;
+ }
+
+ /* this part of the code should only be handling forward links */
+ SMB_ASSERT((schema_attr->linkID & 1) == 0);
+
+ /* Even link IDs are for the originating attribute */
+ target_attr = dsdb_attribute_by_linkID(ac->schema, schema_attr->linkID ^ 1);
+ if (!target_attr) {
+ /*
+ * windows 2003 has a broken schema where
+ * the definition of msDS-IsDomainFor
+ * is missing (which is supposed to be
+ * the backlink of the msDS-HasDomainNCs
+ * attribute
+ */
+ continue;
+ }
+
+ attr_name = target_attr->lDAPDisplayName;
+
+ for (j = 0; j < el->num_values; j++) {
+ ret = la_store_op(ac, LA_OP_ADD,
+ schema_attr,
+ &el->values[j],
+ attr_name);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ /* if no linked attributes are present continue */
+ if (ac->ops == NULL) {
+ /* nothing to do for this module, proceed */
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+
+ /* start with the original request */
+ return la_down_req(ac);
+}
+
+/* For a delete or rename, we need to find out what linked attributes
+ * are currently on this DN, and then deal with them. This is the
+ * callback to the base search */
+
+static int la_mod_search_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ const struct dsdb_attribute *schema_attr;
+ const struct dsdb_attribute *target_attr;
+ struct ldb_message_element *search_el;
+ struct replace_context *rc;
+ struct la_context *ac;
+ const char *attr_name;
+ unsigned int i, j;
+ int ret = LDB_SUCCESS;
+
+ ac = talloc_get_type(req->context, struct la_context);
+ ldb = ldb_module_get_ctx(ac->module);
+ rc = ac->rc;
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ /* Only entries are interesting, and we only want the olddn */
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+
+ if (ldb_dn_compare(ares->message->dn, ac->req->op.mod.message->dn) != 0) {
+ ldb_asprintf_errstring(ldb,
+ "linked_attributes: %s is not the DN we were looking for",
+ ldb_dn_get_linearized(ares->message->dn));
+ /* Guh? We only asked for this DN */
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ac->mod_dn = talloc_steal(ac, ares->message->dn);
+
+ /* We don't populate 'rc' for ADD - it can't be deleting elements anyway */
+ for (i = 0; rc && i < rc->num_elements; i++) {
+
+ schema_attr = dsdb_attribute_by_lDAPDisplayName(ac->schema, rc->el[i].name);
+ if (!schema_attr) {
+ ldb_asprintf_errstring(ldb,
+ "%s: attribute %s is not a valid attribute in schema",
+ __FUNCTION__,
+ rc->el[i].name);
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OBJECT_CLASS_VIOLATION);
+ }
+
+ search_el = ldb_msg_find_element(ares->message,
+ rc->el[i].name);
+
+ /* See if this element already exists */
+ /* otherwise just ignore as
+ * the add has already been scheduled */
+ if ( ! search_el) {
+ continue;
+ }
+
+ target_attr = dsdb_attribute_by_linkID(ac->schema, schema_attr->linkID ^ 1);
+ if (!target_attr) {
+ /*
+ * windows 2003 has a broken schema where
+ * the definition of msDS-IsDomainFor
+ * is missing (which is supposed to be
+ * the backlink of the msDS-HasDomainNCs
+ * attribute
+ */
+ continue;
+ }
+ attr_name = target_attr->lDAPDisplayName;
+
+ /* Now we know what was there, we can remove it for the re-add */
+ for (j = 0; j < search_el->num_values; j++) {
+ ret = la_store_op(ac, LA_OP_DEL,
+ schema_attr,
+ &search_el->values[j],
+ attr_name);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req,
+ NULL, NULL, ret);
+ }
+ }
+ }
+
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+
+ talloc_free(ares);
+
+ if (ac->req->operation == LDB_ADD) {
+ /* Start the modifies to the backlinks */
+ ret = la_queue_mod_request(ac);
+
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ ret);
+ }
+ } else {
+ /* Start with the original request */
+ ret = la_down_req(ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+ return LDB_SUCCESS;
+ }
+
+ talloc_free(ares);
+ return ret;
+}
+
+
+/* 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 */
+
+ struct ldb_control *control;
+ struct ldb_context *ldb;
+ unsigned int i, j;
+ struct la_context *ac;
+ struct ldb_request *search_req;
+ const char **attrs;
+ struct ldb_control *ctrl;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ 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(module, req);
+ if (!ac) {
+ return ldb_operr(ldb);
+ }
+
+ control = ldb_request_get_control(req, LDB_CONTROL_VERIFY_NAME_OID);
+ if (control != NULL && control->data != NULL) {
+ ret = handle_verify_name_control(req, ldb, control, ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+ }
+
+ if (!(ctrl = ldb_request_get_control(req, DSDB_CONTROL_APPLY_LINKS))) {
+ /* don't do anything special for linked attributes, repl_meta_data has done it */
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+ ctrl->critical = false;
+
+ if (!ac->schema) {
+ /* without schema, this doesn't make any sense */
+ return ldb_next_request(module, req);
+ }
+
+ ac->rc = talloc_zero(ac, struct replace_context);
+ if (!ac->rc) {
+ return ldb_oom(ldb);
+ }
+
+ for (i=0; i < req->op.mod.message->num_elements; i++) {
+ bool store_el = false;
+ const char *attr_name;
+ 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(ac->schema, el->name);
+ if (!schema_attr) {
+ ldb_asprintf_errstring(ldb,
+ "%s: attribute %s is not a valid attribute in schema",
+ __FUNCTION__,
+ el->name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ /* We have a valid attribute, now find out if it is a forward link
+ (Even link IDs are for the originating attribute) */
+ if (schema_attr->linkID == 0) {
+ continue;
+ }
+
+ /* this part of the code should only be handling forward links */
+ SMB_ASSERT((schema_attr->linkID & 1) == 0);
+
+ /* Now find the target attribute */
+ target_attr = dsdb_attribute_by_linkID(ac->schema, schema_attr->linkID ^ 1);
+ if (!target_attr) {
+ /*
+ * windows 2003 has a broken schema where
+ * the definition of msDS-IsDomainFor
+ * is missing (which is supposed to be
+ * the backlink of the msDS-HasDomainNCs
+ * attribute
+ */
+ continue;
+ }
+
+ attr_name = target_attr->lDAPDisplayName;
+
+ switch (el->flags & LDB_FLAG_MOD_MASK) {
+ case LDB_FLAG_MOD_REPLACE:
+ /* treat as just a normal add the delete part is handled by the callback */
+ store_el = true;
+
+ FALL_THROUGH;
+ case LDB_FLAG_MOD_ADD:
+
+ /* For each value being added, we need to setup the adds */
+ for (j = 0; j < el->num_values; j++) {
+ ret = la_store_op(ac, LA_OP_ADD,
+ schema_attr,
+ &el->values[j],
+ attr_name);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ break;
+
+ case LDB_FLAG_MOD_DELETE:
+
+ if (el->num_values) {
+ /* For each value being deleted, we need to setup the delete */
+ for (j = 0; j < el->num_values; j++) {
+ ret = la_store_op(ac, LA_OP_DEL,
+ schema_attr,
+ &el->values[j],
+ attr_name);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ } else {
+ /* Flag that there was a DELETE
+ * without a value specified, so we
+ * need to look for the old value */
+ store_el = true;
+ }
+
+ break;
+ }
+
+ if (store_el) {
+ struct ldb_message_element *search_el;
+
+ search_el = talloc_realloc(ac->rc, ac->rc->el,
+ struct ldb_message_element,
+ ac->rc->num_elements +1);
+ if (!search_el) {
+ return ldb_oom(ldb);
+ }
+ ac->rc->el = search_el;
+
+ ac->rc->el[ac->rc->num_elements] = *el;
+ ac->rc->num_elements++;
+ }
+ }
+
+ if (ac->ops || ac->rc->el) {
+ /* both replace and delete without values are handled in the callback
+ * after the search on the entry to be modified is performed */
+
+ attrs = talloc_array(ac->rc, const char *, ac->rc->num_elements + 1);
+ if (!attrs) {
+ return ldb_oom(ldb);
+ }
+ for (i = 0; i < ac->rc->num_elements; i++) {
+ attrs[i] = ac->rc->el[i].name;
+ }
+ attrs[i] = NULL;
+
+ /* The callback does all the hard work here */
+ ret = ldb_build_search_req(&search_req, ldb, ac,
+ req->op.mod.message->dn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)", attrs,
+ NULL,
+ ac, la_mod_search_callback,
+ req);
+ LDB_REQ_SET_LOCATION(search_req);
+
+ /* We need to figure out our own extended DN, to fill in as the backlink target */
+ if (ret == LDB_SUCCESS) {
+ ret = dsdb_request_add_controls(search_req,
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SHOW_EXTENDED_DN);
+ }
+ if (ret == LDB_SUCCESS) {
+ talloc_steal(search_req, attrs);
+
+ ret = ldb_next_request(module, search_req);
+ }
+
+ } else {
+ /* nothing to do for this module, proceed */
+ talloc_free(ac);
+ ret = ldb_next_request(module, req);
+ }
+
+ return ret;
+}
+
+
+static int linked_attributes_fix_link_slow(struct ldb_module *module,
+ struct ldb_request *parent,
+ struct ldb_message *msg,
+ struct ldb_dn *new_dn,
+ struct GUID self_guid,
+ const char *syntax_oid,
+ const char *reverse_syntax_oid)
+{
+ int ret;
+ unsigned int i;
+ struct GUID link_guid;
+ struct ldb_message_element *el = &msg->elements[0];
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ bool has_unique_value = strcmp(reverse_syntax_oid, LDB_SYNTAX_DN) == 0;
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ if (tmp_ctx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ /*
+ * The msg has one element (el) containing links of one particular
+ * type from the remote object. We know that at least one of those
+ * links points to the object being renamed (identified by self_guid,
+ * renamed to new_dn). Usually only one of the links will point back
+ * to renamed object, but there can be more when the reverse link is a
+ * DN+Binary link.
+ *
+ * This is used for unsorted links, which is to say back links and
+ * forward links on old databases. It necessarily involves a linear
+ * search, though when the link is a plain DN link, we can skip
+ * checking as soon as we find it.
+ *
+ * NOTE: if there are duplicate links, the extra ones will end up as
+ * dangling links to the old DN. This may or may not be worse than
+ * leaving them as duplicate links.
+ */
+ for (i = 0; i < el->num_values; i++) {
+ struct dsdb_dn *dsdb_dn = dsdb_dn_parse(msg,
+ ldb,
+ &el->values[i],
+ syntax_oid);
+ if (dsdb_dn == NULL) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+
+ ret = la_guid_from_dn(module, parent, dsdb_dn->dn, &link_guid);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /*
+ * By comparing using the GUID we ensure that even if somehow
+ * the name has got out of sync, this rename will fix it.
+ *
+ * If somehow we don't have a GUID on the DN in the DB, the
+ * la_guid_from_dn call will be more costly, but still give us
+ * a GUID. dbcheck will fix this if run.
+ */
+ if (!GUID_equal(&self_guid, &link_guid)) {
+ continue;
+ }
+
+ ret = ldb_dn_update_components(dsdb_dn->dn, new_dn);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ el->values[i] = data_blob_string_const(
+ dsdb_dn_get_extended_linearized(el->values, dsdb_dn, 1));
+ if (has_unique_value) {
+ break;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+
+static int linked_attributes_fix_forward_link(struct ldb_module *module,
+ struct ldb_message *msg,
+ struct ldb_dn *new_dn,
+ struct GUID self_guid,
+ const char *syntax_oid)
+{
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct parsed_dn *pdn_list = NULL;
+ struct parsed_dn *exact = NULL;
+ struct parsed_dn *next = NULL;
+ bool is_plain_dn;
+ struct ldb_message_element *el = &msg->elements[0];
+ unsigned int num_parsed_dns = el->num_values;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ if (tmp_ctx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * The msg has a single element (el) containing forward links which we
+ * trust are sorted in GUID order. We know that at least one of those
+ * links points to the object being renamed (identified by self_guid,
+ * renamed to new_dn), because that object has a backlink pointing
+ * here.
+ *
+ * In most cases we assume there will only be one forward link, which
+ * is found by parsed_dn_find(), but in the case of DN+Binary links
+ * (e.g. msDS-RevealedUsers) there may be many forward links that
+ * share the same DN/GUID but differ in the binary part. For those we
+ * need to look around the link found by parsed_dn_find() and convert
+ * them all -- there is no way to know which forward link belongs to
+ * which backlink.
+ */
+
+ ret = get_parsed_dns_trusted(tmp_ctx, el, &pdn_list);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "get_parsed_dn_trusted() "
+ "error fixing %s links for %s",
+ el->name,
+ ldb_dn_get_linearized(msg->dn));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* find our DN in the values */
+ ret = parsed_dn_find(ldb, pdn_list, num_parsed_dns,
+ &self_guid,
+ NULL,
+ data_blob_null, 0,
+ &exact, &next,
+ syntax_oid,
+ false);
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "parsed_dn_find() "
+ "error fixing %s links for %s",
+ el->name,
+ ldb_dn_get_linearized(msg->dn));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (exact == NULL) {
+ ldb_asprintf_errstring(
+ ldb,
+ "parsed_dn_find could not find %s link for %s",
+ el->name,
+ ldb_dn_get_linearized(msg->dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ is_plain_dn = strcmp(syntax_oid, LDB_SYNTAX_DN) == 0;
+
+ if (is_plain_dn) {
+ /*
+ * The common case -- we only have to update a single link
+ */
+ ret = ldb_dn_update_components(exact->dsdb_dn->dn, new_dn);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("could not update components %s %s\n",
+ ldb_dn_get_linearized(exact->dsdb_dn->dn),
+ ldb_dn_get_linearized(new_dn)
+ );
+
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ *(exact->v) = data_blob_string_const(
+ dsdb_dn_get_extended_linearized(el->values,
+ exact->dsdb_dn,
+ 1));
+ } else {
+ /*
+ * The forward link is a DN+Binary (or in some alternate
+ * universes, DN+String), which means the parsed_dns are keyed
+ * on GUID+Binary. We don't know the binary part, which means
+ * from our point of view the list can have entries with
+ * duplicate GUIDs that we can't tell apart. We don't know
+ * which backlink belongs to which GUID+binary, and the binary
+ * search will always find the same one. That means one link
+ * link will get fixed n times, whil n-1 links get fixed
+ * never.
+ *
+ * If we instead fixing all the possible links, we end up
+ * fixing n links n times, which at least works and is
+ * probably not too costly because n is probably small.
+ */
+ struct parsed_dn *first = exact;
+ struct parsed_dn *last = exact;
+ struct parsed_dn *p = NULL;
+ int cmp;
+ while (first > pdn_list) {
+ p = first - 1;
+ if (p->dsdb_dn == NULL) {
+ ret = really_parse_trusted_dn(tmp_ctx,
+ ldb, p,
+ syntax_oid);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+ cmp = ndr_guid_compare(&exact->guid, &p->guid);
+ if (cmp != 0) {
+ break;
+ }
+ first = p;
+ }
+
+ while (last < pdn_list + num_parsed_dns - 1) {
+ p = last + 1;
+ if (p->dsdb_dn == NULL) {
+ ret = really_parse_trusted_dn(tmp_ctx,
+ ldb, p,
+ syntax_oid);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+ cmp = ndr_guid_compare(&exact->guid, &p->guid);
+ if (cmp != 0) {
+ break;
+ }
+ last = p;
+ }
+
+ for (p = first; p <= last; p++) {
+ ret = ldb_dn_update_components(p->dsdb_dn->dn, new_dn);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("could not update components %s %s\n",
+ ldb_dn_get_linearized(p->dsdb_dn->dn),
+ ldb_dn_get_linearized(new_dn)
+ );
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ *(p->v) = data_blob_string_const(
+ dsdb_dn_get_extended_linearized(el->values,
+ p->dsdb_dn,
+ 1));
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+
+static int linked_attributes_fix_links(struct ldb_module *module,
+ struct GUID self_guid,
+ struct ldb_dn *old_dn,
+ struct ldb_dn *new_dn,
+ struct ldb_message_element *el,
+ struct dsdb_schema *schema,
+ const struct dsdb_attribute *schema_attr,
+ struct ldb_request *parent)
+{
+ unsigned int i;
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const struct dsdb_attribute *target = NULL;
+ const char *attrs[2];
+ int ret;
+ struct la_private *la_private = NULL;
+
+ target = dsdb_attribute_by_linkID(schema, schema_attr->linkID ^ 1);
+ if (target == NULL) {
+ /* there is no counterpart link to change */
+ return LDB_SUCCESS;
+ }
+
+ tmp_ctx = talloc_new(module);
+ if (tmp_ctx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ la_private = talloc_get_type(ldb_module_get_private(module),
+ struct la_private);
+ if (la_private == NULL) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ attrs[0] = target->lDAPDisplayName;
+ attrs[1] = NULL;
+
+ for (i=0; i<el->num_values; i++) {
+ struct dsdb_dn *dsdb_dn = NULL;
+ struct ldb_result *res = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_message_element *el2 = NULL;
+ struct GUID link_guid;
+ char *link_guid_str = NULL;
+
+ dsdb_dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i],
+ schema_attr->syntax->ldap_oid);
+ if (dsdb_dn == NULL) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+
+ ret = la_guid_from_dn(module, parent, dsdb_dn->dn, &link_guid);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Linked attribute %s->%s between %s and %s - GUID not found - %s",
+ el->name, target->lDAPDisplayName,
+ ldb_dn_get_linearized(old_dn),
+ ldb_dn_get_linearized(dsdb_dn->dn),
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ link_guid_str = GUID_string(tmp_ctx, &link_guid);
+ if (link_guid_str == NULL) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * get the existing message from the db for the object with
+ * this GUID, returning attribute being modified. We will then
+ * use this msg as the basis for a modify call
+ */
+
+ ret = dsdb_module_search(module, tmp_ctx, &res, NULL, LDB_SCOPE_SUBTREE, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SEARCH_ALL_PARTITIONS |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
+ DSDB_SEARCH_REVEAL_INTERNALS,
+ parent,
+ "objectGUID=%s", link_guid_str);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Linked attribute %s->%s between %s and %s - target GUID %s not found - %s",
+ el->name, target->lDAPDisplayName,
+ ldb_dn_get_linearized(old_dn),
+ ldb_dn_get_linearized(dsdb_dn->dn),
+ link_guid_str,
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ if (res->count == 0) {
+ /* Forward link without backlink object remaining - nothing to do here */
+ continue;
+ }
+ if (res->count != 1) {
+ ldb_asprintf_errstring(ldb, "Linked attribute %s->%s between %s and %s - target GUID %s found more than once!",
+ el->name, target->lDAPDisplayName,
+ ldb_dn_get_linearized(old_dn),
+ ldb_dn_get_linearized(dsdb_dn->dn),
+ link_guid_str);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ msg = res->msgs[0];
+
+ if (msg->num_elements == 0) {
+ /* Forward link without backlink remaining - nothing to do here */
+ continue;
+ } else if (msg->num_elements != 1) {
+ ldb_asprintf_errstring(ldb, "Bad msg elements - got %u elements, expected one element to be returned in linked_attributes_fix_links for %s",
+ msg->num_elements, ldb_dn_get_linearized(msg->dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (ldb_attr_cmp(msg->elements[0].name, target->lDAPDisplayName) != 0) {
+ ldb_asprintf_errstring(ldb, "Bad returned attribute in linked_attributes_fix_links: got %s, expected %s for %s", msg->elements[0].name, target->lDAPDisplayName, ldb_dn_get_linearized(msg->dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ el2 = &msg->elements[0];
+
+ el2->flags = LDB_FLAG_MOD_REPLACE;
+
+ if (target->linkID & 1 ||
+ ! la_private->sorted_links) {
+ /* handle backlinks (which aren't sorted in the DB)
+ and forward links in old unsorted databases. */
+ ret = linked_attributes_fix_link_slow(
+ module,
+ parent,
+ msg,
+ new_dn,
+ self_guid,
+ target->syntax->ldap_oid,
+ schema_attr->syntax->ldap_oid);
+ } else {
+ /* we can binary search to find forward links */
+ ret = linked_attributes_fix_forward_link(
+ module,
+ msg,
+ new_dn,
+ self_guid,
+ target->syntax->ldap_oid);
+ }
+ ret = dsdb_check_single_valued_link(target, el2);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* we may be putting multiple values in an attribute -
+ disable checking for this attribute */
+ el2->flags |= LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK;
+
+ ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Linked attribute %s->%s between %s and %s - update failed - %s",
+ el->name, target->lDAPDisplayName,
+ ldb_dn_get_linearized(old_dn),
+ ldb_dn_get_linearized(dsdb_dn->dn),
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+
+/* rename */
+static int linked_attributes_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_result *res;
+ struct ldb_message *msg;
+ unsigned int i;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct dsdb_schema *schema;
+ int ret;
+ struct GUID guid;
+
+ /*
+ - load the current msg
+ - find any linked attributes
+ - if its a link then find the target object
+ - modify the target linked attributes with the new DN
+ */
+ ret = dsdb_module_search_dn(module, req, &res, req->op.rename.olddn,
+ NULL,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_EXTENDED_DN |
+ DSDB_SEARCH_SHOW_RECYCLED, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ schema = dsdb_get_schema(ldb, res);
+ if (!schema) {
+ return ldb_oom(ldb);
+ }
+
+ msg = res->msgs[0];
+
+ ret = la_guid_from_dn(module, req, msg->dn, &guid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ for (i=0; i<msg->num_elements; i++) {
+ struct ldb_message_element *el = &msg->elements[i];
+ const struct dsdb_attribute *schema_attr
+ = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (!schema_attr || schema_attr->linkID == 0) {
+ continue;
+ }
+ ret = linked_attributes_fix_links(module, guid, msg->dn, req->op.rename.newdn, el,
+ schema, schema_attr, req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(res);
+ return ret;
+ }
+ }
+
+ talloc_free(res);
+
+ return ldb_next_request(module, req);
+}
+
+
+/* queue a linked attributes modify request in the la_private
+ structure */
+static int la_queue_mod_request(struct la_context *ac)
+{
+ struct la_private *la_private =
+ talloc_get_type(ldb_module_get_private(ac->module),
+ struct la_private);
+
+ if (la_private == NULL || la_private->transaction == NULL) {
+ ldb_debug(ldb_module_get_ctx(ac->module),
+ LDB_DEBUG_ERROR,
+ __location__ ": No la_private transaction setup\n");
+ return ldb_operr(ldb_module_get_ctx(ac->module));
+ }
+
+ talloc_steal(la_private->transaction, ac);
+ DLIST_ADD(la_private->transaction->la_list, ac);
+
+ return ldb_module_done(ac->req, ac->op_controls,
+ ac->op_response, LDB_SUCCESS);
+}
+
+/* Having done the original operation, then try to fix up all the linked attributes for modify and delete */
+static int la_mod_del_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct la_context *ac;
+ struct ldb_context *ldb;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct la_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ ldb_set_errstring(ldb,
+ "invalid reply type in linked attributes delete callback");
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ac->op_controls = talloc_steal(ac, ares->controls);
+ ac->op_response = talloc_steal(ac, ares->response);
+
+ /* If we have modfies to make, this is the time to do them for modify and delete */
+ ret = la_queue_mod_request(ac);
+
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ talloc_free(ares);
+
+ /* la_queue_mod_request has already sent the callbacks */
+ return LDB_SUCCESS;
+
+}
+
+/* Having done the original add, then try to fix up all the linked attributes
+
+ This is done after the add so the links can get the extended DNs correctly.
+ */
+static int la_add_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct la_context *ac;
+ struct ldb_context *ldb;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct la_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ ldb_set_errstring(ldb,
+ "invalid reply type in linked attributes add callback");
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ac->ops) {
+ struct ldb_request *search_req;
+ static const char *attrs[] = { NULL };
+
+ /* The callback does all the hard work here - we need
+ * the objectGUID and SID of the added record */
+ ret = ldb_build_search_req(&search_req, ldb, ac,
+ ac->req->op.add.message->dn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)", attrs,
+ NULL,
+ ac, la_mod_search_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(search_req);
+
+ if (ret == LDB_SUCCESS) {
+ ret = dsdb_request_add_controls(search_req,
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SHOW_EXTENDED_DN);
+ }
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ ret);
+ }
+
+ ac->op_controls = talloc_steal(ac, ares->controls);
+ ac->op_response = talloc_steal(ac, ares->response);
+
+ return ldb_next_request(ac->module, search_req);
+
+ } else {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+}
+
+/* Reconstruct the original request, but pointing at our local callback to finish things off */
+static int la_down_req(struct la_context *ac)
+{
+ struct ldb_request *down_req;
+ struct ldb_context *ldb;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ switch (ac->req->operation) {
+ case LDB_ADD:
+ ret = ldb_build_add_req(&down_req, ldb, ac,
+ ac->req->op.add.message,
+ ac->req->controls,
+ ac, la_add_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(down_req);
+ break;
+ case LDB_MODIFY:
+ ret = ldb_build_mod_req(&down_req, ldb, ac,
+ ac->req->op.mod.message,
+ ac->req->controls,
+ ac, la_mod_del_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(down_req);
+ break;
+ default:
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ac->module, down_req);
+}
+
+/*
+ use the GUID part of an extended DN to find the target DN, in case
+ it has moved
+ */
+static int la_find_dn_target(struct ldb_module *module, struct la_context *ac,
+ struct GUID *guid, struct ldb_dn **dn)
+{
+ return dsdb_module_dn_by_guid(ac->module, ac, guid, dn, ac->req);
+}
+
+/* apply one la_context op change */
+static int la_do_op_request(struct ldb_module *module, struct la_context *ac, struct la_op_store *op)
+{
+ struct ldb_message_element *ret_el;
+ struct ldb_message *new_msg;
+ struct ldb_context *ldb;
+ int ret;
+
+ if (ac->mod_dn == NULL) {
+ /* we didn't find the DN that we searched for */
+ return LDB_SUCCESS;
+ }
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ /* Create the modify request */
+ new_msg = ldb_msg_new(ac);
+ if (!new_msg) {
+ return ldb_oom(ldb);
+ }
+
+ ret = la_find_dn_target(module, ac, &op->guid, &new_msg->dn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (op->op == LA_OP_ADD) {
+ ret = ldb_msg_add_empty(new_msg, op->name,
+ LDB_FLAG_MOD_ADD, &ret_el);
+ } else {
+ ret = ldb_msg_add_empty(new_msg, op->name,
+ 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) {
+ return ldb_oom(ldb);
+ }
+ ret_el->num_values = 1;
+ ret_el->values[0] = data_blob_string_const(ldb_dn_get_extended_linearized(new_msg, ac->mod_dn, 1));
+
+ /* a backlink should never be single valued. Unfortunately the
+ exchange schema has a attribute
+ msExchBridgeheadedLocalConnectorsDNBL which is single
+ valued and a backlink. We need to cope with that by
+ ignoring the single value flag */
+ ret_el->flags |= LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK;
+
+#if 0
+ ldb_debug(ldb, LDB_DEBUG_WARNING,
+ "link on %s %s: %s %s\n",
+ ldb_dn_get_linearized(new_msg->dn), ret_el->name,
+ ret_el->values[0].data, ac->ops->op == LA_OP_ADD ? "added" : "deleted");
+#endif
+
+ if (DEBUGLVL(4)) {
+ DEBUG(4,("Applying linked attribute change:\n%s\n",
+ ldb_ldif_message_redacted_string(ldb, op,
+ LDB_CHANGETYPE_MODIFY,
+ new_msg)));
+ }
+
+ ret = dsdb_module_modify(module, new_msg, DSDB_FLAG_NEXT_MODULE, ac->req);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_WARNING, __location__ ": failed to apply linked attribute change '%s'\n%s\n",
+ ldb_errstring(ldb),
+ ldb_ldif_message_redacted_string(ldb, op,
+ LDB_CHANGETYPE_MODIFY,
+ new_msg));
+ }
+
+ return ret;
+}
+
+/* apply one set of la_context changes */
+static int la_do_mod_request(struct ldb_module *module, struct la_context *ac)
+{
+ struct la_op_store *op;
+
+ for (op = ac->ops; op; op=op->next) {
+ int ret = la_do_op_request(module, ac, op);
+ if (ret != LDB_SUCCESS) {
+ if (ret != LDB_ERR_NO_SUCH_OBJECT) {
+ return ret;
+ }
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ we hook into the transaction operations to allow us to
+ perform the linked attribute updates at the end of the whole
+ transaction. This allows a forward linked attribute to be created
+ before the target is created, as long as the target is created
+ in the same transaction
+ */
+static int linked_attributes_start_transaction(struct ldb_module *module)
+{
+ /* create our private structure for this transaction */
+ struct la_private *la_private =
+ talloc_get_type(ldb_module_get_private(module),
+ struct la_private);
+
+ if (la_private == NULL) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+ talloc_free(la_private->transaction);
+ la_private->transaction = talloc(module, struct la_private_transaction);
+ if (la_private->transaction == NULL) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+ la_private->transaction->la_list = NULL;
+ return ldb_next_start_trans(module);
+}
+
+/*
+ on prepare commit we loop over our queued la_context structures
+ and apply each of them
+ */
+static int linked_attributes_prepare_commit(struct ldb_module *module)
+{
+ struct la_context *ac;
+ struct la_private *la_private =
+ talloc_get_type(ldb_module_get_private(module),
+ struct la_private);
+ if (la_private == NULL || la_private->transaction == NULL) {
+ DBG_ERR("prepare_commit without begin_transaction\n");
+ /* prepare commit without begin_transaction - let someone else
+ * return the error, just don't segfault */
+ return ldb_next_prepare_commit(module);
+ }
+ /* walk the list backwards, to do the first entry first, as we
+ * added the entries with DLIST_ADD() which puts them at the
+ * start of the list */
+
+ /* Start at the end of the list - so we can start
+ * there, but ensure we don't create a loop by NULLing
+ * it out in the first element */
+ ac = DLIST_TAIL(la_private->transaction->la_list);
+
+ for (; ac; ac=DLIST_PREV(ac)) {
+ int ret;
+ ac->req = NULL;
+ ret = la_do_mod_request(module, ac);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed mod request ret=%d\n", ret));
+ TALLOC_FREE(la_private->transaction);
+ return ret;
+ }
+ }
+
+ TALLOC_FREE(la_private->transaction);
+
+ return ldb_next_prepare_commit(module);
+}
+
+static int linked_attributes_del_transaction(struct ldb_module *module)
+{
+ struct la_private *la_private =
+ talloc_get_type(ldb_module_get_private(module),
+ struct la_private);
+ TALLOC_FREE(la_private->transaction);
+ return ldb_next_del_trans(module);
+}
+
+static int linked_attributes_ldb_init(struct ldb_module *module)
+{
+ int ret;
+ struct la_private *la_private = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_VERIFY_NAME_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_ERROR,
+ "verify_name: Unable to register control with rootdse!\n");
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ la_private = talloc_zero(module, struct la_private);
+ if (la_private == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = dsdb_check_samba_compatible_feature(module,
+ SAMBA_SORTED_LINKS_FEATURE,
+ &la_private->sorted_links);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(la_private);
+ return ret;
+ }
+
+ ldb_module_set_private(module, la_private);
+ return ldb_next_init(module);
+}
+
+
+static const struct ldb_module_ops ldb_linked_attributes_module_ops = {
+ .name = "linked_attributes",
+ .add = linked_attributes_add,
+ .modify = linked_attributes_modify,
+ .rename = linked_attributes_rename,
+ .init_context = linked_attributes_ldb_init,
+ .start_transaction = linked_attributes_start_transaction,
+ .prepare_commit = linked_attributes_prepare_commit,
+ .del_transaction = linked_attributes_del_transaction,
+};
+
+int ldb_linked_attributes_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_linked_attributes_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/netlogon.c b/source4/dsdb/samdb/ldb_modules/netlogon.c
new file mode 100644
index 0000000..4864ae4
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/netlogon.c
@@ -0,0 +1,510 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ CLDAP server - netlogon handling
+
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 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/>.
+*/
+
+#include "includes.h"
+#include <ldb.h>
+#include <ldb_errors.h>
+#include "lib/events/events.h"
+#include "samba/service_task.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "libcli/security/security.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "auth/auth.h"
+#include "ldb_wrap.h"
+#include "system/network.h"
+#include "lib/socket/netif.h"
+#include "param/param.h"
+#include "../lib/tsocket/tsocket.h"
+#include "libds/common/flag_mapping.h"
+#include "lib/util/util_net.h"
+
+#undef strcasecmp
+
+/*
+ fill in the cldap netlogon union for a given version
+*/
+NTSTATUS fill_netlogon_samlogon_response(struct ldb_context *sam_ctx,
+ TALLOC_CTX *mem_ctx,
+ const char *domain,
+ const char *netbios_domain,
+ struct dom_sid *domain_sid,
+ const char *domain_guid,
+ const char *user,
+ uint32_t acct_control,
+ const char *src_address,
+ uint32_t version,
+ struct loadparm_context *lp_ctx,
+ struct netlogon_samlogon_response *netlogon,
+ bool fill_on_blank_request)
+{
+ const char *dom_attrs[] = {"objectGUID", NULL};
+ const char *none_attrs[] = {NULL};
+ struct ldb_result *dom_res = NULL;
+ int ret;
+ const char **services = lpcfg_server_services(lp_ctx);
+ uint32_t server_type;
+ const char *pdc_name;
+ struct GUID domain_uuid;
+ const char *dns_domain;
+ const char *forest_domain;
+ const char *pdc_dns_name;
+ const char *flatname;
+ const char *server_site;
+ const char *client_site;
+ const char *pdc_ip;
+ struct ldb_dn *domain_dn = NULL;
+ struct interface *ifaces;
+ bool user_known = false, am_rodc = false;
+ uint32_t uac = 0;
+ int dc_level;
+ NTSTATUS status;
+
+ /* the domain parameter could have an optional trailing "." */
+ if (domain && domain[strlen(domain)-1] == '.') {
+ domain = talloc_strndup(mem_ctx, domain, strlen(domain)-1);
+ NT_STATUS_HAVE_NO_MEMORY(domain);
+ }
+
+ /* Lookup using long or short domainname */
+ if (domain && (strcasecmp_m(domain, lpcfg_dnsdomain(lp_ctx)) == 0)) {
+ domain_dn = ldb_get_default_basedn(sam_ctx);
+ }
+ if (netbios_domain && (strcasecmp_m(netbios_domain, lpcfg_sam_name(lp_ctx)) == 0)) {
+ domain_dn = ldb_get_default_basedn(sam_ctx);
+ }
+ if (domain_dn) {
+ const char *domain_identifier = domain != NULL ? domain
+ : netbios_domain;
+ ret = ldb_search(sam_ctx, mem_ctx, &dom_res,
+ domain_dn, LDB_SCOPE_BASE, dom_attrs,
+ "objectClass=domain");
+ if (ret != LDB_SUCCESS) {
+ DEBUG(2,("Error finding domain '%s'/'%s' in sam: %s\n",
+ domain_identifier,
+ ldb_dn_get_linearized(domain_dn),
+ ldb_errstring(sam_ctx)));
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+ if (dom_res->count != 1) {
+ DEBUG(2,("Error finding domain '%s'/'%s' in sam\n",
+ domain_identifier,
+ ldb_dn_get_linearized(domain_dn)));
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+ }
+
+ /* Lookup using GUID or SID */
+ if ((dom_res == NULL) && (domain_guid || domain_sid)) {
+ if (domain_guid) {
+ struct GUID binary_guid;
+ struct ldb_val guid_val;
+
+ /* By this means, we ensure we don't have funny stuff in the GUID */
+
+ status = GUID_from_string(domain_guid, &binary_guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* And this gets the result into the binary format we want anyway */
+ status = GUID_to_ndr_blob(&binary_guid, mem_ctx, &guid_val);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ ret = ldb_search(sam_ctx, mem_ctx, &dom_res,
+ NULL, LDB_SCOPE_SUBTREE,
+ dom_attrs,
+ "(&(objectCategory=DomainDNS)(objectGUID=%s))",
+ ldb_binary_encode(mem_ctx, guid_val));
+ } else { /* domain_sid case */
+ ret = ldb_search(sam_ctx, mem_ctx, &dom_res,
+ NULL, LDB_SCOPE_SUBTREE,
+ dom_attrs,
+ "(&(objectCategory=DomainDNS)(objectSid=%s))",
+ dom_sid_string(mem_ctx, domain_sid));
+ }
+
+ if (ret != LDB_SUCCESS) {
+ DEBUG(2,("Unable to find a correct reference to GUID '%s' or SID '%s' in sam: %s\n",
+ domain_guid, dom_sid_string(mem_ctx, domain_sid),
+ ldb_errstring(sam_ctx)));
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ } else if (dom_res->count == 1) {
+ /* Ok, now just check it is our domain */
+ if (ldb_dn_compare(ldb_get_default_basedn(sam_ctx),
+ dom_res->msgs[0]->dn) != 0) {
+ DEBUG(2,("The GUID '%s' or SID '%s' doesn't identify our domain\n",
+ domain_guid,
+ dom_sid_string(mem_ctx, domain_sid)));
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+ } else {
+ DEBUG(2,("Unable to find a correct reference to GUID '%s' or SID '%s' in sam\n",
+ domain_guid, dom_sid_string(mem_ctx, domain_sid)));
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+ }
+
+ if (dom_res == NULL && fill_on_blank_request) {
+ /* blank inputs gives our domain - tested against
+ w2k8r2. Without this ADUC on Win7 won't start */
+ domain_dn = ldb_get_default_basedn(sam_ctx);
+ ret = ldb_search(sam_ctx, mem_ctx, &dom_res,
+ domain_dn, LDB_SCOPE_BASE, dom_attrs,
+ "objectClass=domain");
+ if (ret != LDB_SUCCESS) {
+ DEBUG(2,("Error finding domain '%s'/'%s' in sam: %s\n",
+ lpcfg_dnsdomain(lp_ctx),
+ ldb_dn_get_linearized(domain_dn),
+ ldb_errstring(sam_ctx)));
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+ }
+
+ if (dom_res == NULL) {
+ DEBUG(2,(__location__ ": Unable to get domain information with no inputs\n"));
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ /* work around different inputs for not-specified users */
+ if (!user) {
+ user = "";
+ }
+
+ /* Enquire about any valid username with just a CLDAP packet -
+ * if kerberos didn't also do this, the security folks would
+ * scream... */
+ if (user[0]) {
+ /* Only allow some bits to be enquired: [MS-ATDS] 7.3.3.2 */
+ if (acct_control == (uint32_t)-1) {
+ acct_control = 0;
+ }
+ /*
+ * ACB_AUTOLOCK/UF_LOCKOUT seems to be a special
+ * hack for SEC_CHAN_DNS_DOMAIN.
+ *
+ * It's used together with user = "example.com."
+ */
+ if (acct_control != ACB_AUTOLOCK) {
+ acct_control &= (ACB_TEMPDUP | ACB_NORMAL | ACB_DOMTRUST | ACB_WSTRUST | ACB_SVRTRUST);
+ }
+ uac = ds_acb2uf(acct_control);
+ }
+
+ if (uac == UF_LOCKOUT) {
+ struct ldb_message *tdo_msg = NULL;
+
+ /*
+ * ACB_AUTOLOCK/UF_LOCKOUT seems to be a special
+ * hack for SEC_CHAN_DNS_DOMAIN.
+ *
+ * It's used together with user = "example.com."
+ */
+ status = dsdb_trust_search_tdo_by_type(sam_ctx,
+ SEC_CHAN_DNS_DOMAIN,
+ user, none_attrs,
+ mem_ctx, &tdo_msg);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ user_known = false;
+ } else if (NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tdo_msg);
+ user_known = true;
+ } else {
+ DEBUG(2,("Unable to find reference to TDO '%s' - %s\n",
+ user, nt_errstr(status)));
+ return status;
+ }
+ } else if (user[0]) {
+ struct ldb_result *user_res = NULL;
+
+ /* We must exclude disabled accounts, but otherwise do the bitwise match the client asked for */
+ ret = ldb_search(sam_ctx, mem_ctx, &user_res,
+ dom_res->msgs[0]->dn, LDB_SCOPE_SUBTREE,
+ none_attrs,
+ "(&(objectClass=user)(samAccountName=%s)"
+ "(!(userAccountControl:" LDB_OID_COMPARATOR_AND ":=%u))"
+ "(userAccountControl:" LDB_OID_COMPARATOR_OR ":=%u))",
+ ldb_binary_encode_string(mem_ctx, user),
+ UF_ACCOUNTDISABLE, uac);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(2,("Unable to find reference to user '%s' with ACB 0x%8x under %s: %s\n",
+ user, acct_control, ldb_dn_get_linearized(dom_res->msgs[0]->dn),
+ ldb_errstring(sam_ctx)));
+ return NT_STATUS_NO_SUCH_USER;
+ } else if (user_res->count == 1) {
+ user_known = true;
+ } else {
+ user_known = false;
+ }
+ TALLOC_FREE(user_res);
+ } else {
+ user_known = true;
+ }
+
+ server_type = DS_SERVER_DS;
+
+ if (samdb_is_pdc(sam_ctx)) {
+ server_type |= DS_SERVER_PDC;
+ }
+
+ if (samdb_is_gc(sam_ctx)) {
+ server_type |= DS_SERVER_GC;
+ }
+
+ if (str_list_check(services, "ldap")) {
+ server_type |= DS_SERVER_LDAP;
+ }
+
+ if (str_list_check(services, "kdc")) {
+ server_type |= DS_SERVER_KDC;
+ }
+
+ if (str_list_check(services, "ntp_signd")) {
+ server_type |= DS_SERVER_TIMESERV | DS_SERVER_GOOD_TIMESERV;
+ }
+
+ if (samdb_rodc(sam_ctx, &am_rodc) == LDB_SUCCESS && !am_rodc) {
+ server_type |= DS_SERVER_WRITABLE;
+ }
+
+ dc_level = dsdb_dc_functional_level(sam_ctx);
+ if (dc_level >= DS_DOMAIN_FUNCTION_2008) {
+ if (server_type & DS_SERVER_WRITABLE) {
+ server_type |= DS_SERVER_FULL_SECRET_DOMAIN_6;
+ } else {
+ server_type |= DS_SERVER_SELECT_SECRET_DOMAIN_6;
+ }
+ }
+
+ if (dc_level >= DS_DOMAIN_FUNCTION_2012) {
+ server_type |= DS_SERVER_DS_8;
+ }
+
+ if (dc_level >= DS_DOMAIN_FUNCTION_2012_R2) {
+ server_type |= DS_SERVER_DS_9;
+ }
+
+ if (dc_level >= DS_DOMAIN_FUNCTION_2016) {
+ server_type |= DS_SERVER_DS_10;
+ }
+
+ if (version & (NETLOGON_NT_VERSION_5EX|NETLOGON_NT_VERSION_5EX_WITH_IP)) {
+ pdc_name = lpcfg_netbios_name(lp_ctx);
+ } else {
+ pdc_name = talloc_asprintf(mem_ctx, "\\\\%s",
+ lpcfg_netbios_name(lp_ctx));
+ NT_STATUS_HAVE_NO_MEMORY(pdc_name);
+ }
+ domain_uuid = samdb_result_guid(dom_res->msgs[0], "objectGUID");
+ dns_domain = lpcfg_dnsdomain(lp_ctx);
+ forest_domain = samdb_forest_name(sam_ctx, mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(forest_domain);
+ pdc_dns_name = talloc_asprintf(mem_ctx, "%s.%s",
+ strlower_talloc(mem_ctx,
+ lpcfg_netbios_name(lp_ctx)),
+ dns_domain);
+ NT_STATUS_HAVE_NO_MEMORY(pdc_dns_name);
+ flatname = lpcfg_workgroup(lp_ctx);
+
+ server_site = samdb_server_site_name(sam_ctx, mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(server_site);
+ client_site = samdb_client_site_name(sam_ctx, mem_ctx,
+ src_address, NULL,
+ true);
+ NT_STATUS_HAVE_NO_MEMORY(client_site);
+ if (strcasecmp(server_site, client_site) == 0) {
+ server_type |= DS_SERVER_CLOSEST;
+ }
+
+ load_interface_list(mem_ctx, lp_ctx, &ifaces);
+ if (src_address) {
+ pdc_ip = iface_list_best_ip(ifaces, src_address);
+ } else {
+ pdc_ip = iface_list_first_v4(ifaces);
+ }
+ if (pdc_ip == NULL || !is_ipaddress_v4(pdc_ip)) {
+ /* this matches windows behaviour */
+ pdc_ip = "127.0.0.1";
+ }
+
+ ZERO_STRUCTP(netlogon);
+
+ /* check if either of these bits is present */
+ if (version & (NETLOGON_NT_VERSION_5EX|NETLOGON_NT_VERSION_5EX_WITH_IP)) {
+ uint32_t extra_flags = 0;
+ netlogon->ntver = NETLOGON_NT_VERSION_5EX;
+
+ /* could check if the user exists */
+ if (user_known) {
+ netlogon->data.nt5_ex.command = LOGON_SAM_LOGON_RESPONSE_EX;
+ } else {
+ netlogon->data.nt5_ex.command = LOGON_SAM_LOGON_USER_UNKNOWN_EX;
+ }
+ netlogon->data.nt5_ex.pdc_name = pdc_name;
+ netlogon->data.nt5_ex.user_name = user;
+ netlogon->data.nt5_ex.domain_name = flatname;
+ netlogon->data.nt5_ex.domain_uuid = domain_uuid;
+ netlogon->data.nt5_ex.forest = forest_domain;
+ netlogon->data.nt5_ex.dns_domain = dns_domain;
+ netlogon->data.nt5_ex.pdc_dns_name = pdc_dns_name;
+ netlogon->data.nt5_ex.server_site = server_site;
+ netlogon->data.nt5_ex.client_site = client_site;
+ if (version & NETLOGON_NT_VERSION_5EX_WITH_IP) {
+ /* note that this is always a IPV4 address */
+ extra_flags = NETLOGON_NT_VERSION_5EX_WITH_IP;
+ netlogon->data.nt5_ex.sockaddr.sockaddr_family = 2;
+ netlogon->data.nt5_ex.sockaddr.pdc_ip = pdc_ip;
+ netlogon->data.nt5_ex.sockaddr.remaining = data_blob_talloc_zero(mem_ctx, 8);
+ }
+ netlogon->data.nt5_ex.server_type = server_type;
+ netlogon->data.nt5_ex.nt_version = NETLOGON_NT_VERSION_1|NETLOGON_NT_VERSION_5EX|extra_flags;
+ netlogon->data.nt5_ex.lmnt_token = 0xFFFF;
+ netlogon->data.nt5_ex.lm20_token = 0xFFFF;
+
+ } else if (version & NETLOGON_NT_VERSION_5) {
+ netlogon->ntver = NETLOGON_NT_VERSION_5;
+
+ /* could check if the user exists */
+ if (user_known) {
+ netlogon->data.nt5.command = LOGON_SAM_LOGON_RESPONSE;
+ } else {
+ netlogon->data.nt5.command = LOGON_SAM_LOGON_USER_UNKNOWN;
+ }
+ netlogon->data.nt5.pdc_name = pdc_name;
+ netlogon->data.nt5.user_name = user;
+ netlogon->data.nt5.domain_name = flatname;
+ netlogon->data.nt5.domain_uuid = domain_uuid;
+ netlogon->data.nt5.forest = forest_domain;
+ netlogon->data.nt5.dns_domain = dns_domain;
+ netlogon->data.nt5.pdc_dns_name = pdc_dns_name;
+ netlogon->data.nt5.pdc_ip = pdc_ip;
+ netlogon->data.nt5.server_type = server_type;
+ netlogon->data.nt5.nt_version = NETLOGON_NT_VERSION_1|NETLOGON_NT_VERSION_5;
+ netlogon->data.nt5.lmnt_token = 0xFFFF;
+ netlogon->data.nt5.lm20_token = 0xFFFF;
+
+ } else /* (version & NETLOGON_NT_VERSION_1) and all other cases */ {
+ netlogon->ntver = NETLOGON_NT_VERSION_1;
+ /* could check if the user exists */
+ if (user_known) {
+ netlogon->data.nt4.command = LOGON_SAM_LOGON_RESPONSE;
+ } else {
+ netlogon->data.nt4.command = LOGON_SAM_LOGON_USER_UNKNOWN;
+ }
+ netlogon->data.nt4.pdc_name = pdc_name;
+ netlogon->data.nt4.user_name = user;
+ netlogon->data.nt4.domain_name = flatname;
+ netlogon->data.nt4.nt_version = NETLOGON_NT_VERSION_1;
+ netlogon->data.nt4.lmnt_token = 0xFFFF;
+ netlogon->data.nt4.lm20_token = 0xFFFF;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS parse_netlogon_request(struct ldb_parse_tree *tree,
+ struct loadparm_context *lp_ctx,
+ TALLOC_CTX *tmp_ctx,
+ const char **domain,
+ const char **host,
+ const char **user,
+ const char **domain_guid,
+ struct dom_sid **domain_sid,
+ int *acct_control,
+ int *version)
+{
+ unsigned int i;
+
+ *domain = NULL;
+ *host = NULL;
+ *user = NULL;
+ *domain_guid = NULL;
+ *domain_sid = NULL;
+ *acct_control = -1;
+ *version = NETLOGON_NT_VERSION_5;
+
+ if (tree->operation != LDB_OP_AND) goto failed;
+
+ /* extract the query elements */
+ for (i=0;i<tree->u.list.num_elements;i++) {
+ struct ldb_parse_tree *t = tree->u.list.elements[i];
+ if (t->operation != LDB_OP_EQUALITY) goto failed;
+ if (strcasecmp(t->u.equality.attr, "DnsDomain") == 0) {
+ *domain = talloc_strndup(tmp_ctx,
+ (const char *)t->u.equality.value.data,
+ t->u.equality.value.length);
+ }
+ if (strcasecmp(t->u.equality.attr, "Host") == 0) {
+ *host = talloc_strndup(tmp_ctx,
+ (const char *)t->u.equality.value.data,
+ t->u.equality.value.length);
+ }
+ if (strcasecmp(t->u.equality.attr, "DomainGuid") == 0) {
+ NTSTATUS enc_status;
+ struct GUID guid;
+ enc_status = ldap_decode_ndr_GUID(tmp_ctx,
+ t->u.equality.value, &guid);
+ if (NT_STATUS_IS_OK(enc_status)) {
+ *domain_guid = GUID_string(tmp_ctx, &guid);
+ }
+ }
+ if (strcasecmp(t->u.equality.attr, "DomainSid") == 0) {
+ enum ndr_err_code ndr_err;
+
+ *domain_sid = talloc(tmp_ctx, struct dom_sid);
+ if (*domain_sid == NULL) {
+ goto failed;
+ }
+ ndr_err = ndr_pull_struct_blob(&t->u.equality.value,
+ *domain_sid, *domain_sid,
+ (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(*domain_sid);
+ goto failed;
+ }
+ }
+ if (strcasecmp(t->u.equality.attr, "User") == 0) {
+ *user = talloc_strndup(tmp_ctx,
+ (const char *)t->u.equality.value.data,
+ t->u.equality.value.length);
+ }
+ if (strcasecmp(t->u.equality.attr, "NtVer") == 0 &&
+ t->u.equality.value.length == 4) {
+ *version = IVAL(t->u.equality.value.data, 0);
+ }
+ if (strcasecmp(t->u.equality.attr, "AAC") == 0 &&
+ t->u.equality.value.length == 4) {
+ *acct_control = IVAL(t->u.equality.value.data, 0);
+ }
+ }
+
+ if ((*domain == NULL) && (*domain_guid == NULL) && (*domain_sid == NULL)) {
+ *domain = lpcfg_dnsdomain(lp_ctx);
+ }
+
+ return NT_STATUS_OK;
+
+failed:
+ return NT_STATUS_UNSUCCESSFUL;
+}
diff --git a/source4/dsdb/samdb/ldb_modules/new_partition.c b/source4/dsdb/samdb/ldb_modules/new_partition.c
new file mode 100644
index 0000000..eaf7d43
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/new_partition.c
@@ -0,0 +1,213 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2004-2008
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 2005
+ 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 new partition module
+ *
+ * Description: Handle the add of new partitions
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb.h"
+#include "ldb_module.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "dsdb/samdb/samdb.h"
+#include "../libds/common/flags.h"
+#include "dsdb/common/util.h"
+
+struct np_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+ struct ldb_request *search_req;
+ struct ldb_request *part_add;
+};
+
+static int np_part_mod_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct np_context *ac;
+
+ ac = talloc_get_type(req->context, struct np_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* We just want to update the @PARTITIONS record if the value does not exist */
+ if (ares->error != LDB_SUCCESS && ares->error != LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ ldb_asprintf_errstring(ldb, "Invalid LDB reply type %d", ares->type);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ldb_reset_err_string(ldb);
+
+ /* Do the original add */
+ return ldb_next_request(ac->module, ac->req);
+}
+
+static int np_part_search_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct np_context *ac;
+ struct dsdb_create_partition_exop *ex_op;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct np_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* If this already exists, we really don't want to create a
+ * partition - it would allow a duplicate entry to be
+ * created */
+ if (ares->error != LDB_ERR_NO_SUCH_OBJECT) {
+ if (ares->error == LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, LDB_ERR_ENTRY_ALREADY_EXISTS);
+ } else {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ ldb_set_errstring(ldb, "Invalid reply type - we must not get a result here!");
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ldb_reset_err_string(ldb);
+
+ /* Now that we know it does not exist, we can try and create the partition */
+ ex_op = talloc(ac, struct dsdb_create_partition_exop);
+ if (ex_op == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ex_op->new_dn = ac->req->op.add.message->dn;
+
+ ret = ldb_build_extended_req(&ac->part_add,
+ ldb, ac, DSDB_EXTENDED_CREATE_PARTITION_OID, ex_op,
+ NULL, ac, np_part_mod_callback, req);
+
+ /* if the parent was asking for a partial replica, then we
+ * need the extended operation to also ask for a partial
+ * replica */
+ if (ldb_request_get_control(req, DSDB_CONTROL_PARTIAL_REPLICA)) {
+ ret = dsdb_request_add_controls(ac->part_add, DSDB_MODIFY_PARTIAL_REPLICA);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+
+ LDB_REQ_SET_LOCATION(ac->part_add);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ac->module, ac->part_add);
+}
+
+/* add_record: add instancetype attribute */
+static int new_partition_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct np_context *ac;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "new_partition_add\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_msg_find_element(req->op.add.message, "instanceType")) {
+ /* This needs to be 'static' to ensure it does not move, and is not on the stack */
+ static const char *no_attrs[] = { NULL };
+ uint32_t instanceType = ldb_msg_find_attr_as_uint(req->op.add.message, "instanceType", 0);
+
+ if (!(instanceType & INSTANCE_TYPE_IS_NC_HEAD)) {
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_msg_find_attr_as_bool(req->op.add.message, "isDeleted", false)) {
+ DEBUG(0,(__location__ ": Skipping deleted partition %s\n",
+ ldb_dn_get_linearized(req->op.add.message->dn)));
+ return ldb_next_request(module, req);
+ }
+
+ /* Create an @PARTITIONS record for this partition -
+ * by asking the partitions module to do so via an
+ * extended operation, after first checking if the
+ * record already exists */
+ ac = talloc(req, struct np_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->module = module;
+ ac->req = req;
+
+ ret = ldb_build_search_req(&ac->search_req, ldb, ac, req->op.add.message->dn,
+ LDB_SCOPE_BASE, NULL, no_attrs, req->controls, ac,
+ np_part_search_callback,
+ req);
+ LDB_REQ_SET_LOCATION(ac->search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, ac->search_req);
+ }
+
+ /* go on with the call chain */
+ return ldb_next_request(module, req);
+}
+
+static const struct ldb_module_ops ldb_new_partition_module_ops = {
+ .name = "new_partition",
+ .add = new_partition_add,
+};
+
+int ldb_new_partition_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_new_partition_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/objectclass.c b/source4/dsdb/samdb/ldb_modules/objectclass.c
new file mode 100644
index 0000000..5b97c9d
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/objectclass.c
@@ -0,0 +1,1477 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2006-2008
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
+ Copyright (C) Matthias Dieter Wallnöfer 2010-2011
+
+ 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 and constraint checking module
+ *
+ * Description:
+ * - sort the objectClass attribute into the class
+ * hierarchy and perform constraint checks (correct RDN name,
+ * valid parent),
+ * - fix DNs into 'standard' case
+ * - Add objectCategory and some other attribute defaults
+ *
+ * Author: Andrew Bartlett
+ */
+
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/samdb.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"
+#include "../libds/common/flags.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+#undef strcasecmp
+
+struct oc_context {
+
+ struct ldb_module *module;
+ struct ldb_request *req;
+ const struct dsdb_schema *schema;
+
+ struct ldb_reply *search_res;
+ struct ldb_reply *search_res2;
+
+ int (*step_fn)(struct oc_context *);
+};
+
+static struct oc_context *oc_init_context(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct oc_context *ac;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = talloc_zero(req, struct oc_context);
+ if (ac == NULL) {
+ ldb_oom(ldb);
+ return NULL;
+ }
+
+ ac->module = module;
+ ac->req = req;
+ ac->schema = dsdb_get_schema(ldb, ac);
+
+ return ac;
+}
+
+static int objectclass_do_add(struct oc_context *ac);
+
+/*
+ * This checks if we have unrelated object classes in our entry's "objectClass"
+ * attribute. That means "unsatisfied" abstract classes (no concrete subclass)
+ * or two or more disjunct structural ones.
+ * If one of these conditions are true, blame.
+ */
+static int check_unrelated_objectclasses(struct ldb_module *module,
+ const struct dsdb_schema *schema,
+ const struct dsdb_class *struct_objectclass,
+ struct ldb_message_element *objectclass_element)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ unsigned int i;
+ bool found;
+
+ if (schema == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ for (i = 0; i < objectclass_element->num_values; i++) {
+ const struct dsdb_class *tmp_class = dsdb_class_by_lDAPDisplayName_ldb_val(schema,
+ &objectclass_element->values[i]);
+ const struct dsdb_class *tmp_class2 = struct_objectclass;
+
+ /* Pointer comparison can be used due to the same schema str. */
+ if (tmp_class == NULL ||
+ tmp_class == struct_objectclass ||
+ tmp_class->objectClassCategory > 2 ||
+ ldb_attr_cmp(tmp_class->lDAPDisplayName, "top") == 0) {
+ continue;
+ }
+
+ found = false;
+ while (!found &&
+ ldb_attr_cmp(tmp_class2->lDAPDisplayName, "top") != 0) {
+ tmp_class2 = dsdb_class_by_lDAPDisplayName(schema,
+ tmp_class2->subClassOf);
+ if (tmp_class2 == tmp_class) {
+ found = true;
+ }
+ }
+ if (found) {
+ continue;
+ }
+
+ ldb_asprintf_errstring(ldb,
+ "objectclass: the objectclass '%s' seems to be unrelated to %s!",
+ tmp_class->lDAPDisplayName,
+ struct_objectclass->lDAPDisplayName);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int get_search_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct oc_context *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct oc_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS &&
+ ares->error != LDB_ERR_NO_SUCH_OBJECT) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ ldb_reset_err_string(ldb);
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ if (ac->search_res != NULL) {
+ ldb_set_errstring(ldb, "Too many results");
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ac->search_res = talloc_steal(ac, ares);
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ talloc_free(ares);
+ break;
+
+ case LDB_REPLY_DONE:
+ talloc_free(ares);
+ ret = ac->step_fn(ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ break;
+ }
+
+ 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(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *newdn, struct ldb_dn *parent_dn,
+ struct ldb_dn **fixed_dn)
+{
+ char *upper_rdn_attr;
+ const struct ldb_val *rdn_val;
+
+ /* 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);
+ if (*fixed_dn == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /* 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 == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /* Create a new child */
+ if (ldb_dn_add_child_fmt(*fixed_dn, "X=X") == false) {
+ return ldb_operr(ldb);
+ }
+
+ rdn_val = ldb_dn_get_rdn_val(newdn);
+ if (rdn_val == NULL) {
+ return ldb_operr(ldb);
+ }
+
+#if 0
+ /* the rules for rDN length constraints are more complex than
+ this. Until we understand them we need to leave this
+ constraint out. Otherwise we break replication, as windows
+ does sometimes send us rDNs longer than 64 */
+ if (!rdn_val || rdn_val->length > 64) {
+ DEBUG(2,(__location__ ": WARNING: rDN longer than 64 limit for '%s'\n", ldb_dn_get_linearized(newdn)));
+ }
+#endif
+
+
+ /* And replace it with CN=foo (we need the attribute in upper case) */
+ return ldb_dn_set_component(*fixed_dn, 0, upper_rdn_attr, *rdn_val);
+}
+
+
+static int objectclass_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct ldb_request *search_req;
+ struct oc_context *ac;
+ struct ldb_dn *parent_dn;
+ const struct ldb_val *val;
+ int ret;
+ static const char * const parent_attrs[] = { "objectClass", NULL };
+
+ ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(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);
+ }
+
+ /* An add operation on the basedn without "NC-add" operation isn't
+ * allowed. */
+ if (ldb_dn_compare(ldb_get_default_basedn(ldb), req->op.add.message->dn) == 0) {
+ unsigned int instanceType;
+
+ instanceType = ldb_msg_find_attr_as_uint(req->op.add.message,
+ "instanceType", 0);
+ if (!(instanceType & INSTANCE_TYPE_IS_NC_HEAD)) {
+ char *referral_uri;
+ /* When we are trying to readd the root basedn then
+ * this is denied, but with an interesting mechanism:
+ * there is generated a referral with the last
+ * component value as hostname. */
+ val = ldb_dn_get_component_val(req->op.add.message->dn,
+ ldb_dn_get_comp_num(req->op.add.message->dn) - 1);
+ if (val == NULL) {
+ return ldb_operr(ldb);
+ }
+ referral_uri = talloc_asprintf(req, "ldap://%s/%s", val->data,
+ ldb_dn_get_linearized(req->op.add.message->dn));
+ if (referral_uri == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ return ldb_module_send_referral(req, referral_uri);
+ }
+ }
+
+ ac = oc_init_context(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* If there isn't a parent, just go on to the add processing */
+ if (ldb_dn_get_comp_num(ac->req->op.add.message->dn) == 1) {
+ return objectclass_do_add(ac);
+ }
+
+ /* get copy of parent DN */
+ parent_dn = ldb_dn_get_parent(ac, ac->req->op.add.message->dn);
+ if (parent_dn == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ ret = ldb_build_search_req(&search_req, ldb,
+ ac, parent_dn, LDB_SCOPE_BASE,
+ "(objectClass=*)", parent_attrs,
+ NULL,
+ ac, get_search_callback,
+ req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = dsdb_request_add_controls(search_req,
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->step_fn = objectclass_do_add;
+
+ return ldb_next_request(ac->module, search_req);
+}
+
+
+/*
+ check if this is a special RODC nTDSDSA add
+ */
+static bool check_rodc_ntdsdsa_add(struct oc_context *ac,
+ const struct dsdb_class *objectclass)
+{
+ struct ldb_control *rodc_control;
+
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, "nTDSDSA") != 0) {
+ return false;
+ }
+ rodc_control = ldb_request_get_control(ac->req, LDB_CONTROL_RODC_DCPROMO_OID);
+ if (!rodc_control) {
+ return false;
+ }
+
+ rodc_control->critical = false;
+ return true;
+}
+
+static int objectclass_do_add(struct oc_context *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct ldb_request *add_req;
+ struct ldb_message_element *objectclass_element, *el;
+ struct ldb_message *msg;
+ const char *rdn_name = NULL;
+ char *value;
+ const struct dsdb_class *objectclass;
+ struct ldb_dn *objectcategory;
+ int32_t systemFlags = 0;
+ unsigned int i, j;
+ bool found;
+ int ret;
+
+ msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message);
+ if (msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ /* Check if we have a valid parent - this check is needed since
+ * we don't get a LDB_ERR_NO_SUCH_OBJECT error. */
+ if (ac->search_res == NULL) {
+ unsigned int instanceType;
+
+ /* An add operation on partition DNs without "NC-add" operation
+ * isn't allowed. */
+ instanceType = ldb_msg_find_attr_as_uint(msg, "instanceType",
+ 0);
+ if (!(instanceType & INSTANCE_TYPE_IS_NC_HEAD)) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, parent does not exist!",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+
+ /* Don't keep any error messages - we've to add a partition */
+ ldb_set_errstring(ldb, NULL);
+ } else {
+ /* Fix up the DN to be in the standard form, taking
+ * particular care to match the parent DN */
+ ret = fix_dn(ldb, msg,
+ ac->req->op.add.message->dn,
+ ac->search_res->message->dn,
+ &msg->dn);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "objectclass: Could not munge DN %s into normal form",
+ ldb_dn_get_linearized(ac->req->op.add.message->dn));
+ return ret;
+ }
+ }
+
+ if (ac->schema != NULL) {
+ unsigned int linkID = 0;
+ /*
+ * Notice: by the normalization function call in "ldb_request()"
+ * case "LDB_ADD" we have always only *one* "objectClass"
+ * attribute at this stage!
+ */
+
+ objectclass_element = ldb_msg_find_element(msg, "objectClass");
+ if (!objectclass_element) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, no objectclass specified!",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ if (objectclass_element->num_values == 0) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, at least one (structural) objectclass has to be specified!",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ /* Now do the sorting */
+ ret = dsdb_sort_objectClass_attr(ldb, ac->schema,
+ objectclass_element, msg,
+ objectclass_element);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ * Get the new top-most structural object class and check for
+ * unrelated structural classes
+ */
+ objectclass = dsdb_get_last_structural_class(ac->schema,
+ objectclass_element);
+ if (objectclass == NULL) {
+ ldb_asprintf_errstring(ldb,
+ "Failed to find a structural class for %s",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ ret = check_unrelated_objectclasses(ac->module, ac->schema,
+ objectclass,
+ objectclass_element);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ rdn_name = ldb_dn_get_rdn_name(msg->dn);
+ if (rdn_name == NULL) {
+ return ldb_operr(ldb);
+ }
+ found = false;
+ for (i = 0; (!found) && (i < objectclass_element->num_values);
+ i++) {
+ const struct dsdb_class *tmp_class =
+ dsdb_class_by_lDAPDisplayName_ldb_val(ac->schema,
+ &objectclass_element->values[i]);
+
+ if (tmp_class == NULL) continue;
+
+ if (ldb_attr_cmp(rdn_name, tmp_class->rDNAttID) == 0)
+ found = true;
+ }
+ if (!found) {
+ ldb_asprintf_errstring(ldb,
+ "objectclass: Invalid RDN '%s' for objectclass '%s'!",
+ rdn_name, objectclass->lDAPDisplayName);
+ return LDB_ERR_NAMING_VIOLATION;
+ }
+
+ if (objectclass->systemOnly &&
+ !ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) &&
+ !check_rodc_ntdsdsa_add(ac, objectclass)) {
+ ldb_asprintf_errstring(ldb,
+ "objectclass: object class '%s' is system-only, rejecting creation of '%s'!",
+ objectclass->lDAPDisplayName,
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ if (ac->search_res && ac->search_res->message) {
+ struct ldb_message_element *oc_el
+ = ldb_msg_find_element(ac->search_res->message, "objectClass");
+
+ bool allowed_class = false;
+ for (i=0; allowed_class == false && oc_el && i < oc_el->num_values; i++) {
+ const struct dsdb_class *sclass;
+
+ sclass = dsdb_class_by_lDAPDisplayName_ldb_val(ac->schema,
+ &oc_el->values[i]);
+ if (!sclass) {
+ /* We don't know this class? what is going on? */
+ continue;
+ }
+ for (j=0; sclass->systemPossibleInferiors && sclass->systemPossibleInferiors[j]; j++) {
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, sclass->systemPossibleInferiors[j]) == 0) {
+ allowed_class = true;
+ break;
+ }
+ }
+ }
+
+ if (!allowed_class) {
+ ldb_asprintf_errstring(ldb, "structural objectClass %s is not a valid child class for %s",
+ objectclass->lDAPDisplayName, ldb_dn_get_linearized(ac->search_res->message->dn));
+ return LDB_ERR_NAMING_VIOLATION;
+ }
+ }
+
+ objectcategory = ldb_msg_find_attr_as_dn(ldb, ac, msg,
+ "objectCategory");
+ if (objectcategory == NULL) {
+ struct dsdb_extended_dn_store_format *dn_format =
+ talloc_get_type(ldb_module_get_private(ac->module),
+ struct dsdb_extended_dn_store_format);
+ if (dn_format && dn_format->store_extended_dn_in_ldb == false) {
+ /* Strip off extended components */
+ struct ldb_dn *dn = ldb_dn_new(ac, ldb,
+ objectclass->defaultObjectCategory);
+ value = ldb_dn_alloc_linearized(msg, dn);
+ talloc_free(dn);
+ } else {
+ value = talloc_strdup(msg,
+ objectclass->defaultObjectCategory);
+ }
+ if (value == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ ret = ldb_msg_add_string(msg, "objectCategory", value);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ } else {
+ const struct dsdb_class *ocClass =
+ dsdb_class_by_cn_ldb_val(ac->schema,
+ ldb_dn_get_rdn_val(objectcategory));
+ if (ocClass != NULL) {
+ struct ldb_dn *dn = ldb_dn_new(ac, ldb,
+ ocClass->defaultObjectCategory);
+ if (ldb_dn_compare(objectcategory, dn) != 0) {
+ ocClass = NULL;
+ }
+ }
+ talloc_free(objectcategory);
+ if (ocClass == NULL) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, 'objectCategory' attribute invalid!",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ }
+
+ if (!ldb_msg_find_element(msg, "showInAdvancedViewOnly") && (objectclass->defaultHidingValue == true)) {
+ ldb_msg_add_string(msg, "showInAdvancedViewOnly",
+ "TRUE");
+ }
+
+ /* There are very special rules for systemFlags, see MS-ADTS
+ * MS-ADTS 3.1.1.5.2.4 */
+
+ el = ldb_msg_find_element(msg, "systemFlags");
+ if ((el != NULL) && (el->num_values > 1)) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot add %s, 'systemFlags' attribute multivalued!",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ systemFlags = ldb_msg_find_attr_as_int(msg, "systemFlags", 0);
+
+ ldb_msg_remove_attr(msg, "systemFlags");
+
+ /* Only the following flags may be set by a client */
+ if (ldb_request_get_control(ac->req,
+ LDB_CONTROL_RELAX_OID) == NULL) {
+ systemFlags &= ( SYSTEM_FLAG_CONFIG_ALLOW_RENAME
+ | SYSTEM_FLAG_CONFIG_ALLOW_MOVE
+ | SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE
+ | SYSTEM_FLAG_ATTR_IS_RDN );
+ }
+
+ /* But the last one ("ATTR_IS_RDN") is only allowed on
+ * "attributeSchema" objects. So truncate if it does not fit. */
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, "attributeSchema") != 0) {
+ systemFlags &= ~SYSTEM_FLAG_ATTR_IS_RDN;
+ }
+
+ if (ldb_attr_cmp(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(objectclass->lDAPDisplayName, "site") == 0
+ || ldb_attr_cmp(objectclass->lDAPDisplayName, "serversContainer") == 0
+ || ldb_attr_cmp(objectclass->lDAPDisplayName, "nTDSDSA") == 0) {
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, "site") == 0)
+ systemFlags |= (int32_t)(SYSTEM_FLAG_CONFIG_ALLOW_RENAME);
+ systemFlags |= (int32_t)(SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE);
+ } else if (ldb_attr_cmp(objectclass->lDAPDisplayName, "siteLink") == 0
+ || ldb_attr_cmp(objectclass->lDAPDisplayName, "subnet") == 0
+ || ldb_attr_cmp(objectclass->lDAPDisplayName, "siteLinkBridge") == 0
+ || ldb_attr_cmp(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) */
+
+ linkID = ldb_msg_find_attr_as_int(msg, "linkID", 0);
+ if (linkID > 0 && linkID % 2 == 1) {
+ systemFlags |= DS_FLAG_ATTR_NOT_REPLICATED;
+ }
+
+ if (el || systemFlags != 0) {
+ ret = samdb_msg_add_int(ldb, msg, msg, "systemFlags",
+ systemFlags);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* make sure that "isCriticalSystemObject" is not specified! */
+ el = ldb_msg_find_element(msg, "isCriticalSystemObject");
+ if ((el != NULL) &&
+ !ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) {
+ ldb_set_errstring(ldb,
+ "objectclass: 'isCriticalSystemObject' must not be specified!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+
+ ret = ldb_build_add_req(&add_req, ldb, ac,
+ msg,
+ ac->req->controls,
+ ac->req, dsdb_next_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(add_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* perform the add */
+ return ldb_next_request(ac->module, add_req);
+}
+
+static int oc_modify_callback(struct ldb_request *req,
+ struct ldb_reply *ares);
+static int objectclass_do_mod(struct oc_context *ac);
+
+static int objectclass_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_message_element *objectclass_element;
+ struct ldb_message *msg;
+ struct ldb_request *down_req;
+ struct oc_context *ac;
+ bool oc_changes = false;
+ int ret;
+
+ ldb_debug(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);
+ }
+
+ /* As with the "real" AD we don't accept empty messages */
+ if (req->op.mod.message->num_elements == 0) {
+ ldb_set_errstring(ldb, "objectclass: modify message must have "
+ "elements/attributes!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ ac = oc_init_context(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* Without schema, there isn't much to do here */
+ if (ac->schema == NULL) {
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+
+ msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
+ if (msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ /* For now change everything except the objectclasses */
+
+ objectclass_element = ldb_msg_find_element(msg, "objectClass");
+ if (objectclass_element != NULL) {
+ ldb_msg_remove_attr(msg, "objectClass");
+ oc_changes = true;
+ }
+
+ /* MS-ADTS 3.1.1.5.3.5 - on a forest level < 2003 we do allow updates
+ * only on application NCs - not on the default ones */
+ if (oc_changes &&
+ (dsdb_forest_functional_level(ldb) < DS_DOMAIN_FUNCTION_2003)) {
+ struct ldb_dn *nc_root;
+
+ ret = dsdb_find_nc_root(ldb, ac, req->op.mod.message->dn,
+ &nc_root);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if ((ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) == 0) ||
+ (ldb_dn_compare(nc_root, ldb_get_config_basedn(ldb)) == 0) ||
+ (ldb_dn_compare(nc_root, ldb_get_schema_basedn(ldb)) == 0)) {
+ ldb_set_errstring(ldb,
+ "objectclass: object class changes on objects under the standard name contexts not allowed!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ talloc_free(nc_root);
+ }
+
+ if (oc_changes) {
+ ret = ldb_build_mod_req(&down_req, ldb, ac,
+ msg,
+ req->controls, ac,
+ oc_modify_callback,
+ req);
+ } else {
+ ret = ldb_build_mod_req(&down_req, ldb, ac,
+ msg,
+ req->controls, req,
+ dsdb_next_callback,
+ req);
+ }
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, down_req);
+}
+
+static int oc_modify_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ static const char * const attrs[] = { "objectClass", NULL };
+ struct ldb_context *ldb;
+ struct ldb_request *search_req;
+ struct oc_context *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct oc_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ares->type == LDB_REPLY_REFERRAL) {
+ return ldb_module_send_referral(ac->req, ares->referral);
+ }
+
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ talloc_free(ares);
+
+ /* this looks up the real existing object for fetching some important
+ * information (objectclasses) */
+ ret = ldb_build_search_req(&search_req, ldb,
+ ac, ac->req->op.mod.message->dn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs, NULL,
+ ac, get_search_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ ret = dsdb_request_add_controls(search_req,
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ ac->step_fn = objectclass_do_mod;
+
+ ret = ldb_next_request(ac->module, search_req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int objectclass_do_mod(struct oc_context *ac)
+{
+ struct ldb_context *ldb;
+ struct ldb_request *mod_req;
+ struct ldb_message_element *oc_el_entry, *oc_el_change;
+ struct ldb_val *vals;
+ struct ldb_message *msg;
+ const struct dsdb_class *current_structural_objectclass;
+ const struct dsdb_class *objectclass;
+ unsigned int i, j, k;
+ bool found;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ /* we should always have a valid entry when we enter here */
+ if (ac->search_res == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ oc_el_entry = ldb_msg_find_element(ac->search_res->message,
+ "objectClass");
+ if (oc_el_entry == NULL) {
+ /* existing entry without a valid object class? */
+ return ldb_operr(ldb);
+ }
+
+ /*
+ * Get the current new top-most structural object class
+ *
+ * We must not allow this to change
+ */
+
+ current_structural_objectclass
+ = dsdb_get_last_structural_class(ac->schema,
+ oc_el_entry);
+ if (current_structural_objectclass == NULL) {
+ ldb_asprintf_errstring(ldb,
+ "objectclass: cannot find current structural objectclass on %s!",
+ ldb_dn_get_linearized(ac->search_res->message->dn));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* use a new message structure */
+ msg = ldb_msg_new(ac);
+ if (msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ msg->dn = ac->req->op.mod.message->dn;
+
+ /* We've to walk over all "objectClass" message elements */
+ for (k = 0; k < ac->req->op.mod.message->num_elements; k++) {
+ if (ldb_attr_cmp(ac->req->op.mod.message->elements[k].name,
+ "objectClass") != 0) {
+ continue;
+ }
+
+ oc_el_change = &ac->req->op.mod.message->elements[k];
+
+ switch (oc_el_change->flags & LDB_FLAG_MOD_MASK) {
+ case LDB_FLAG_MOD_ADD:
+ /* Merge the two message elements */
+ for (i = 0; i < oc_el_change->num_values; i++) {
+ for (j = 0; j < oc_el_entry->num_values; j++) {
+ if (ldb_attr_cmp((char *)oc_el_change->values[i].data,
+ (char *)oc_el_entry->values[j].data) == 0) {
+ ldb_asprintf_errstring(ldb,
+ "objectclass: cannot re-add an existing objectclass: '%.*s'!",
+ (int)oc_el_change->values[i].length,
+ (const char *)oc_el_change->values[i].data);
+ return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+ }
+ }
+ /* append the new object class value - code was
+ * copied from "ldb_msg_add_value" */
+ vals = talloc_realloc(oc_el_entry, oc_el_entry->values,
+ struct ldb_val,
+ oc_el_entry->num_values + 1);
+ if (vals == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ oc_el_entry->values = vals;
+ oc_el_entry->values[oc_el_entry->num_values] =
+ oc_el_change->values[i];
+ ++(oc_el_entry->num_values);
+ }
+
+ break;
+
+ case LDB_FLAG_MOD_REPLACE:
+ /*
+ * In this case the new "oc_el_entry" is simply
+ * "oc_el_change"
+ */
+ oc_el_entry = oc_el_change;
+
+ break;
+
+ case LDB_FLAG_MOD_DELETE:
+ /* Merge the two message elements */
+ for (i = 0; i < oc_el_change->num_values; i++) {
+ found = false;
+ for (j = 0; j < oc_el_entry->num_values; j++) {
+ if (ldb_attr_cmp((char *)oc_el_change->values[i].data,
+ (char *)oc_el_entry->values[j].data) == 0) {
+ found = true;
+ /* delete the object class value
+ * - code was copied from
+ * "ldb_msg_remove_element" */
+ if (j != oc_el_entry->num_values - 1) {
+ memmove(&oc_el_entry->values[j],
+ &oc_el_entry->values[j+1],
+ ((oc_el_entry->num_values-1) - j)*sizeof(struct ldb_val));
+ }
+ --(oc_el_entry->num_values);
+ break;
+ }
+ }
+ if (!found) {
+ /* we cannot delete a not existing
+ * object class */
+ ldb_asprintf_errstring(ldb,
+ "objectclass: cannot delete this objectclass: '%.*s'!",
+ (int)oc_el_change->values[i].length,
+ (const char *)oc_el_change->values[i].data);
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+ }
+
+ break;
+ }
+
+ /* Now do the sorting */
+ ret = dsdb_sort_objectClass_attr(ldb, ac->schema, oc_el_entry,
+ msg, oc_el_entry);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ * Get the new top-most structural object class and check for
+ * unrelated structural classes
+ */
+ objectclass = dsdb_get_last_structural_class(ac->schema,
+ oc_el_entry);
+ if (objectclass == NULL) {
+ ldb_set_errstring(ldb,
+ "objectclass: cannot delete all structural objectclasses!");
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /*
+ * Has (so far, we re-check for each and every
+ * "objectclass" in the message) the structural
+ * objectclass changed?
+ */
+
+ if (objectclass != current_structural_objectclass) {
+ const char *dn
+ = ldb_dn_get_linearized(ac->search_res->message->dn);
+ ldb_asprintf_errstring(ldb,
+ "objectclass: not permitted "
+ "to change the structural "
+ "objectClass on %s [%s] => [%s]!",
+ dn,
+ current_structural_objectclass->lDAPDisplayName,
+ objectclass->lDAPDisplayName);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /* Check for unrelated objectclasses */
+ ret = check_unrelated_objectclasses(ac->module, ac->schema,
+ objectclass,
+ oc_el_entry);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* Now add the new object class attribute to the change message */
+ ret = ldb_msg_add(msg, oc_el_entry, LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ ldb_module_oom(ac->module);
+ return ret;
+ }
+
+ /* Now we have the real and definitive change left to do */
+
+ ret = ldb_build_mod_req(&mod_req, ldb, ac,
+ msg,
+ ac->req->controls,
+ ac->req, dsdb_next_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(mod_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ac->module, mod_req);
+}
+
+static int objectclass_do_rename(struct oc_context *ac);
+
+static int objectclass_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ static const char * const attrs[] = { "objectClass", NULL };
+ struct ldb_context *ldb;
+ struct ldb_request *search_req;
+ struct oc_context *ac;
+ struct ldb_dn *parent_dn;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_rename\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.rename.olddn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /*
+ * Bypass the constraint checks when we do have the "DBCHECK" control
+ * set, so we can force objects under the deleted objects container.
+ */
+ if (ldb_request_get_control(req, DSDB_CONTROL_DBCHECK) != NULL) {
+ return ldb_next_request(module, req);
+ }
+
+ ac = oc_init_context(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ parent_dn = ldb_dn_get_parent(ac, req->op.rename.newdn);
+ if (parent_dn == NULL) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s, the parent DN does not exist!",
+ ldb_dn_get_linearized(req->op.rename.olddn));
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+
+ /* this looks up the parent object for fetching some important
+ * information (objectclasses, DN normalisation...) */
+ ret = ldb_build_search_req(&search_req, ldb,
+ ac, parent_dn, LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs, NULL,
+ ac, get_search_callback,
+ req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* we have to add the show recycled control, as otherwise DRS
+ deletes will be refused as we will think the target parent
+ does not exist */
+ ret = dsdb_request_add_controls(search_req,
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->step_fn = objectclass_do_rename;
+
+ return ldb_next_request(ac->module, search_req);
+}
+
+static int objectclass_do_rename2(struct oc_context *ac);
+
+static int objectclass_do_rename(struct oc_context *ac)
+{
+ static const char * const attrs[] = { "objectClass", NULL };
+ struct ldb_context *ldb;
+ struct ldb_request *search_req;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ /* Check if we have a valid parent - this check is needed since
+ * we don't get a LDB_ERR_NO_SUCH_OBJECT error. */
+ if (ac->search_res == NULL) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s, parent does not exist!",
+ ldb_dn_get_linearized(ac->req->op.rename.olddn));
+ return LDB_ERR_OTHER;
+ }
+
+ /* now assign "search_res2" to the parent entry to have "search_res"
+ * free for another lookup */
+ ac->search_res2 = ac->search_res;
+ ac->search_res = NULL;
+
+ /* this looks up the real existing object for fetching some important
+ * information (objectclasses) */
+ ret = ldb_build_search_req(&search_req, ldb,
+ ac, ac->req->op.rename.olddn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs, NULL,
+ ac, get_search_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = dsdb_request_add_controls(search_req,
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->step_fn = objectclass_do_rename2;
+
+ return ldb_next_request(ac->module, search_req);
+}
+
+static int objectclass_do_rename2(struct oc_context *ac)
+{
+ struct ldb_context *ldb;
+ struct ldb_request *rename_req;
+ struct ldb_dn *fixed_dn;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ /* Check if we have a valid entry - this check is needed since
+ * we don't get a LDB_ERR_NO_SUCH_OBJECT error. */
+ if (ac->search_res == NULL) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s, entry does not exist!",
+ ldb_dn_get_linearized(ac->req->op.rename.olddn));
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+
+ if (ac->schema != NULL) {
+ struct ldb_message_element *oc_el_entry, *oc_el_parent;
+ const struct dsdb_class *objectclass;
+ const char *rdn_name;
+ bool allowed_class = false;
+ unsigned int i, j;
+ bool found;
+
+ oc_el_entry = ldb_msg_find_element(ac->search_res->message,
+ "objectClass");
+ if (oc_el_entry == NULL) {
+ /* existing entry without a valid object class? */
+ return ldb_operr(ldb);
+ }
+ objectclass = dsdb_get_last_structural_class(ac->schema,
+ oc_el_entry);
+ if (objectclass == NULL) {
+ /* existing entry without a valid object class? */
+ return ldb_operr(ldb);
+ }
+
+ rdn_name = ldb_dn_get_rdn_name(ac->req->op.rename.newdn);
+ if (rdn_name == NULL) {
+ return ldb_operr(ldb);
+ }
+ found = false;
+ for (i = 0; (!found) && (i < oc_el_entry->num_values); i++) {
+ const struct dsdb_class *tmp_class =
+ dsdb_class_by_lDAPDisplayName_ldb_val(ac->schema,
+ &oc_el_entry->values[i]);
+
+ if (tmp_class == NULL) continue;
+
+ if (ldb_attr_cmp(rdn_name, tmp_class->rDNAttID) == 0)
+ found = true;
+ }
+ if (!found) {
+ ldb_asprintf_errstring(ldb,
+ "objectclass: Invalid RDN '%s' for objectclass '%s'!",
+ rdn_name, objectclass->lDAPDisplayName);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ oc_el_parent = ldb_msg_find_element(ac->search_res2->message,
+ "objectClass");
+ if (oc_el_parent == NULL) {
+ /* existing entry without a valid object class? */
+ return ldb_operr(ldb);
+ }
+
+ for (i=0; allowed_class == false && i < oc_el_parent->num_values; i++) {
+ const struct dsdb_class *sclass;
+
+ sclass = dsdb_class_by_lDAPDisplayName_ldb_val(ac->schema,
+ &oc_el_parent->values[i]);
+ if (!sclass) {
+ /* We don't know this class? what is going on? */
+ continue;
+ }
+ for (j=0; sclass->systemPossibleInferiors && sclass->systemPossibleInferiors[j]; j++) {
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, sclass->systemPossibleInferiors[j]) == 0) {
+ allowed_class = true;
+ break;
+ }
+ }
+ }
+
+ if (!allowed_class) {
+ ldb_asprintf_errstring(ldb,
+ "objectclass: structural objectClass %s is not a valid child class for %s",
+ objectclass->lDAPDisplayName, ldb_dn_get_linearized(ac->search_res2->message->dn));
+ return LDB_ERR_NAMING_VIOLATION;
+ }
+ }
+
+ /* Ensure we are not trying to rename it to be a child of itself */
+ if ((ldb_dn_compare_base(ac->req->op.rename.olddn,
+ ac->req->op.rename.newdn) == 0) &&
+ (ldb_dn_compare(ac->req->op.rename.olddn,
+ ac->req->op.rename.newdn) != 0)) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot rename %s to be a child of itself",
+ ldb_dn_get_linearized(ac->req->op.rename.olddn));
+ 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(ldb, ac,
+ ac->req->op.rename.newdn,
+ ac->search_res2->message->dn,
+ &fixed_dn);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "objectclass: Could not munge DN %s into normal form",
+ ldb_dn_get_linearized(ac->req->op.rename.newdn));
+ return ret;
+
+ }
+
+ ret = ldb_build_rename_req(&rename_req, ldb, ac,
+ ac->req->op.rename.olddn, fixed_dn,
+ ac->req->controls,
+ ac->req, dsdb_next_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(rename_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* perform the rename */
+ return ldb_next_request(ac->module, rename_req);
+}
+
+static int objectclass_do_delete(struct oc_context *ac);
+
+static int objectclass_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ static const char * const attrs[] = { "nCName", "objectClass",
+ "systemFlags",
+ "isDeleted",
+ "isCriticalSystemObject", NULL };
+ struct ldb_context *ldb;
+ struct ldb_request *search_req;
+ struct oc_context *ac;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_delete\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.del.dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* Bypass the constraint checks when we do have the "RELAX" control
+ * set. */
+ if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID) != NULL) {
+ return ldb_next_request(module, req);
+ }
+
+ ac = oc_init_context(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* this looks up the entry object for fetching some important
+ * information (object classes, system flags...) */
+ ret = ldb_build_search_req(&search_req, ldb,
+ ac, req->op.del.dn, LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs, NULL,
+ ac, get_search_callback,
+ req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = dsdb_request_add_controls(search_req,
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_RECYCLED);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->step_fn = objectclass_do_delete;
+
+ return ldb_next_request(ac->module, search_req);
+}
+
+static int objectclass_do_delete(struct oc_context *ac)
+{
+ struct ldb_context *ldb;
+ struct ldb_dn *dn;
+ int32_t systemFlags;
+ bool isCriticalSystemObject;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ /* Check if we have a valid entry - this check is needed since
+ * we don't get a LDB_ERR_NO_SUCH_OBJECT error. */
+ if (ac->search_res == NULL) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, entry does not exist!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+
+ /* DC's ntDSDSA object */
+ if (ldb_dn_compare(ac->req->op.del.dn, samdb_ntds_settings_dn(ldb, ac)) == 0) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's the DC's ntDSDSA object!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* DC's rIDSet object */
+ /* Perform this check only when it does exist - this is needed in order
+ * to don't let existing provisions break, and to delete . */
+ ret = samdb_rid_set_dn(ldb, ac, &dn);
+ if ((ret != LDB_SUCCESS) && (ret != LDB_ERR_NO_SUCH_ATTRIBUTE)
+ && (ret != LDB_ERR_NO_SUCH_OBJECT)) {
+ ldb_asprintf_errstring(ldb, "objectclass: Unable to determine if %s, is this DC's rIDSet object: %s ",
+ ldb_dn_get_linearized(ac->req->op.del.dn),
+ ldb_errstring(ldb));
+ return ret;
+ }
+ if (ret == LDB_SUCCESS) {
+ if (ldb_dn_compare(ac->req->op.del.dn, dn) == 0) {
+ talloc_free(dn);
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's the DC's rIDSet object!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ talloc_free(dn);
+ }
+
+ /* Only trusted request from system account are allowed to delete
+ * deleted objects.
+ */
+ if (ldb_msg_check_string_attribute(ac->search_res->message, "isDeleted", "TRUE") &&
+ (ldb_req_is_untrusted(ac->req) ||
+ !dsdb_module_am_system(ac->module))) {
+ ldb_asprintf_errstring(ldb, "Delete of '%s' failed",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* crossRef objects regarding config, schema and default domain NCs */
+ if (samdb_find_attribute(ldb, ac->search_res->message, "objectClass",
+ "crossRef") != NULL) {
+ dn = ldb_msg_find_attr_as_dn(ldb, ac, ac->search_res->message,
+ "nCName");
+ if ((ldb_dn_compare(dn, ldb_get_default_basedn(ldb)) == 0) ||
+ (ldb_dn_compare(dn, ldb_get_config_basedn(ldb)) == 0)) {
+ talloc_free(dn);
+
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's a crossRef object to the main or configuration partition!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_NOT_ALLOWED_ON_NON_LEAF;
+ }
+ if (ldb_dn_compare(dn, ldb_get_schema_basedn(ldb)) == 0) {
+ talloc_free(dn);
+
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it's a crossRef object to the schema partition!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ talloc_free(dn);
+ }
+
+ /* systemFlags */
+
+ systemFlags = ldb_msg_find_attr_as_int(ac->search_res->message,
+ "systemFlags", 0);
+ if ((systemFlags & SYSTEM_FLAG_DISALLOW_DELETE) != 0) {
+ ldb_asprintf_errstring(ldb, "objectclass: Cannot delete %s, it isn't permitted!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* isCriticalSystemObject - but this only applies on tree delete
+ * operations - MS-ADTS 3.1.1.5.5.7.2 */
+ if (ldb_request_get_control(ac->req, LDB_CONTROL_TREE_DELETE_OID) != NULL) {
+ isCriticalSystemObject = ldb_msg_find_attr_as_bool(ac->search_res->message,
+ "isCriticalSystemObject", false);
+ if (isCriticalSystemObject) {
+ /*
+ * Following the explaination from Microsoft
+ * https://lists.samba.org/archive/cifs-protocol/2011-August/002046.html
+ * "I finished the investigation on this behavior.
+ * As per MS-ADTS 3.1.5.5.7.2 , when a tree deletion is performed ,
+ * every object in the tree will be checked to see if it has isCriticalSystemObject
+ * set to TRUE, including the root node on which the delete operation is performed
+ * But there is an exception if the root object is a SAM specific objects(3.1.1.5.2.3 MS-ADTS)
+ * Its deletion is done through SAM manger and isCriticalSystemObject attribute is not checked
+ * The root node of the tree delete in your case is CN=ARES,OU=Domain Controllers,DC=w2k8r2,DC=home,DC=matws,DC=net
+ * which is a SAM object with user class. Therefore the tree deletion is performed without any error
+ */
+
+ if (samdb_find_attribute(ldb, ac->search_res->message, "objectClass", "group") == NULL &&
+ samdb_find_attribute(ldb, ac->search_res->message, "objectClass", "samDomain") == NULL &&
+ samdb_find_attribute(ldb, ac->search_res->message, "objectClass", "samServer") == NULL &&
+ samdb_find_attribute(ldb, ac->search_res->message, "objectClass", "user") == NULL) {
+ ldb_asprintf_errstring(ldb,
+ "objectclass: Cannot tree-delete %s, it's a critical system object!",
+ ldb_dn_get_linearized(ac->req->op.del.dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+ }
+
+ return ldb_next_request(ac->module, ac->req);
+}
+
+static int objectclass_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret;
+
+ /* Init everything else */
+ ret = ldb_next_init(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Look for the opaque to indicate we might have to cut down the DN of defaultObjectCategory */
+ ldb_module_set_private(module, ldb_get_opaque(ldb, DSDB_EXTENDED_DN_STORE_FORMAT_OPAQUE_NAME));
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_RODC_DCPROMO_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "objectclass_init: Unable to register control DCPROMO with rootdse\n");
+ return ldb_operr(ldb);
+ }
+
+ return ret;
+}
+
+static const struct ldb_module_ops ldb_objectclass_module_ops = {
+ .name = "objectclass",
+ .add = objectclass_add,
+ .modify = objectclass_modify,
+ .rename = objectclass_rename,
+ .del = objectclass_delete,
+ .init_context = objectclass_init
+};
+
+int ldb_objectclass_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_objectclass_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/objectclass_attrs.c b/source4/dsdb/samdb/ldb_modules/objectclass_attrs.c
new file mode 100644
index 0000000..2a77353
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/objectclass_attrs.c
@@ -0,0 +1,744 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2006-2008
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2009
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Matthias Dieter Wallnöfer 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: objectclass attribute checking module
+ *
+ * Description: this checks the attributes on a directory entry (if they're
+ * allowed, if the syntax is correct, if mandatory ones are missing,
+ * denies the deletion of mandatory ones...). The module contains portions
+ * of the "objectclass" and the "validate_update" LDB module.
+ *
+ * Author: Matthias Dieter Wallnöfer
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+#undef strcasecmp
+
+struct oc_context {
+
+ struct ldb_module *module;
+ struct ldb_request *req;
+ const struct dsdb_schema *schema;
+
+ struct ldb_message *msg;
+
+ struct ldb_reply *search_res;
+ struct ldb_reply *mod_ares;
+};
+
+static struct oc_context *oc_init_context(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct oc_context *ac;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = talloc_zero(req, struct oc_context);
+ if (ac == NULL) {
+ ldb_oom(ldb);
+ return NULL;
+ }
+
+ ac->module = module;
+ ac->req = req;
+ ac->schema = dsdb_get_schema(ldb, ac);
+
+ return ac;
+}
+
+static int oc_op_callback(struct ldb_request *req, struct ldb_reply *ares);
+
+/*
+ * Checks the correctness of the "dSHeuristics" attribute as described in both
+ * MS-ADTS 7.1.1.2.4.1.2 dSHeuristics and MS-ADTS 3.1.1.5.3.2 Constraints
+ */
+static int oc_validate_dsheuristics(struct ldb_message_element *el)
+{
+ if (el->num_values > 0) {
+ if ((el->values[0].length >= DS_HR_NINETIETH_CHAR) &&
+ (el->values[0].data[DS_HR_NINETIETH_CHAR-1] != '9')) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if ((el->values[0].length >= DS_HR_EIGHTIETH_CHAR) &&
+ (el->values[0].data[DS_HR_EIGHTIETH_CHAR-1] != '8')) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if ((el->values[0].length >= DS_HR_SEVENTIETH_CHAR) &&
+ (el->values[0].data[DS_HR_SEVENTIETH_CHAR-1] != '7')) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if ((el->values[0].length >= DS_HR_SIXTIETH_CHAR) &&
+ (el->values[0].data[DS_HR_SIXTIETH_CHAR-1] != '6')) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if ((el->values[0].length >= DS_HR_FIFTIETH_CHAR) &&
+ (el->values[0].data[DS_HR_FIFTIETH_CHAR-1] != '5')) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if ((el->values[0].length >= DS_HR_FOURTIETH_CHAR) &&
+ (el->values[0].data[DS_HR_FOURTIETH_CHAR-1] != '4')) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if ((el->values[0].length >= DS_HR_THIRTIETH_CHAR) &&
+ (el->values[0].data[DS_HR_THIRTIETH_CHAR-1] != '3')) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if ((el->values[0].length >= DS_HR_TWENTIETH_CHAR) &&
+ (el->values[0].data[DS_HR_TWENTIETH_CHAR-1] != '2')) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if ((el->values[0].length >= DS_HR_TENTH_CHAR) &&
+ (el->values[0].data[DS_HR_TENTH_CHAR-1] != '1')) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ auto normalise values on input
+ */
+static int oc_auto_normalise(struct ldb_context *ldb, const struct dsdb_attribute *attr,
+ struct ldb_message *msg, struct ldb_message_element *el)
+{
+ int i;
+ bool values_copied = false;
+
+ for (i=0; i<el->num_values; i++) {
+ struct ldb_val v;
+ int ret;
+ /*
+ * We use msg->elements (owned by this module due to
+ * ldb_msg_copy_shallow()) as a memory context and
+ * then steal from there to the right spot if we don't
+ * free it.
+ */
+ ret = attr->ldb_schema_attribute->syntax->canonicalise_fn(ldb,
+ msg->elements,
+ &el->values[i],
+ &v);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (data_blob_cmp(&v, &el->values[i]) == 0) {
+ /* no need to replace it */
+ talloc_free(v.data);
+ continue;
+ }
+
+ /* we need to copy the values array on the first change */
+ if (!values_copied) {
+ struct ldb_val *v2;
+ v2 = talloc_array(msg->elements, struct ldb_val, el->num_values);
+ if (v2 == NULL) {
+ return ldb_oom(ldb);
+ }
+ memcpy(v2, el->values, sizeof(struct ldb_val) * el->num_values);
+ el->values = v2;
+ values_copied = true;
+ }
+
+ el->values[i] = v;
+
+ /*
+ * By now el->values is a talloc pointer under
+ * msg->elements and may now be used
+ */
+ talloc_steal(el->values, v.data);
+ }
+ return LDB_SUCCESS;
+}
+
+static int attr_handler(struct oc_context *ac)
+{
+ struct ldb_context *ldb;
+ struct ldb_message *msg;
+ struct ldb_request *child_req;
+ const struct dsdb_attribute *attr;
+ unsigned int i;
+ int ret;
+ WERROR werr;
+ struct dsdb_syntax_ctx syntax_ctx;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (ac->req->operation == LDB_ADD) {
+ msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message);
+ } else {
+ msg = ldb_msg_copy_shallow(ac, ac->req->op.mod.message);
+ }
+ if (msg == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->msg = msg;
+
+ /* initialize syntax checking context */
+ dsdb_syntax_ctx_init(&syntax_ctx, ldb, ac->schema);
+
+ /* Check if attributes exist in the schema, if the values match,
+ * if they're not operational and fix the names to the match the schema
+ * case */
+ for (i = 0; i < msg->num_elements; i++) {
+ attr = dsdb_attribute_by_lDAPDisplayName(ac->schema,
+ msg->elements[i].name);
+ if (attr == NULL) {
+ if (ldb_request_get_control(ac->req, DSDB_CONTROL_DBCHECK) &&
+ ac->req->operation != LDB_ADD) {
+ /* we allow this for dbcheck to fix
+ broken attributes */
+ goto no_attribute;
+ }
+ ldb_asprintf_errstring(ldb, "objectclass_attrs: attribute '%s' on entry '%s' was not found in the schema!",
+ msg->elements[i].name,
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+
+ if ((attr->linkID & 1) == 1 &&
+ !ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) &&
+ !ldb_request_get_control(ac->req, DSDB_CONTROL_DBCHECK)) {
+ /* Odd is for the target. Illegal to modify */
+ ldb_asprintf_errstring(ldb,
+ "objectclass_attrs: attribute '%s' on entry '%s' must not be modified directly, it is a linked attribute",
+ msg->elements[i].name,
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /*
+ * Enforce systemOnly checks from [ADTS] 3.1.1.5.3.2
+ * Constraints in Modify Operation
+ */
+ if (ac->req->operation == LDB_MODIFY && attr->systemOnly) {
+ /*
+ * Allow dbcheck and relax to bypass. objectClass, name
+ * and distinguishedName are generally handled
+ * elsewhere.
+ *
+ * The remaining cases, undelete, msDS-AdditionalDnsHostName
+ * and wellKnownObjects are documented in the specification.
+ */
+ if (!ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) &&
+ !ldb_request_get_control(ac->req, DSDB_CONTROL_DBCHECK) &&
+ !ldb_request_get_control(ac->req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID) &&
+ ldb_attr_cmp(attr->lDAPDisplayName, "objectClass") != 0 &&
+ ldb_attr_cmp(attr->lDAPDisplayName, "name") != 0 &&
+ ldb_attr_cmp(attr->lDAPDisplayName, "distinguishedName") != 0 &&
+ ldb_attr_cmp(attr->lDAPDisplayName, "msDS-AdditionalDnsHostName") != 0 &&
+ ldb_attr_cmp(attr->lDAPDisplayName, "wellKnownObjects") != 0) {
+ /*
+ * Comparison against base schema DN is used as a substitute for
+ * fschemaUpgradeInProgress and other specific schema checks.
+ */
+ if (ldb_dn_compare_base(ldb_get_schema_basedn(ldb), msg->dn) != 0) {
+ struct ldb_control *as_system = ldb_request_get_control(ac->req,
+ LDB_CONTROL_AS_SYSTEM_OID);
+ if (!dsdb_module_am_system(ac->module) && !as_system) {
+ ldb_asprintf_errstring(ldb,
+ "objectclass_attrs: attribute '%s' on entry '%s' can only be modified as system",
+ msg->elements[i].name,
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+ }
+ }
+
+ if (!(msg->elements[i].flags & LDB_FLAG_INTERNAL_DISABLE_VALIDATION)) {
+ werr = attr->syntax->validate_ldb(&syntax_ctx, attr,
+ &msg->elements[i]);
+ if (!W_ERROR_IS_OK(werr) &&
+ !ldb_request_get_control(ac->req, DSDB_CONTROL_DBCHECK)) {
+ ldb_asprintf_errstring(ldb, "objectclass_attrs: attribute '%s' on entry '%s' contains at least one invalid value!",
+ msg->elements[i].name,
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+ }
+ }
+
+ if ((attr->systemFlags & DS_FLAG_ATTR_IS_CONSTRUCTED) != 0) {
+ ldb_asprintf_errstring(ldb, "objectclass_attrs: attribute '%s' on entry '%s' is constructed!",
+ msg->elements[i].name,
+ ldb_dn_get_linearized(msg->dn));
+ if (ac->req->operation == LDB_ADD) {
+ return LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE;
+ } else {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+
+ /* "dSHeuristics" syntax check */
+ if (ldb_attr_cmp(attr->lDAPDisplayName, "dSHeuristics") == 0) {
+ ret = oc_validate_dsheuristics(&(msg->elements[i]));
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* auto normalise some attribute values */
+ if (attr->syntax->auto_normalise) {
+ ret = oc_auto_normalise(ldb, attr, msg, &msg->elements[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* Substitute the attribute name to match in case */
+ msg->elements[i].name = attr->lDAPDisplayName;
+ }
+
+no_attribute:
+ if (ac->req->operation == LDB_ADD) {
+ ret = ldb_build_add_req(&child_req, ldb, ac,
+ msg, ac->req->controls,
+ ac, oc_op_callback, ac->req);
+ LDB_REQ_SET_LOCATION(child_req);
+ } else {
+ ret = ldb_build_mod_req(&child_req, ldb, ac,
+ msg, ac->req->controls,
+ ac, oc_op_callback, ac->req);
+ LDB_REQ_SET_LOCATION(child_req);
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ac->module, child_req);
+}
+
+/*
+ these are attributes which are left over from old ways of doing
+ things in ldb, and are harmless
+ */
+static const char *harmless_attrs[] = { "parentGUID", NULL };
+
+static int attr_handler2(struct oc_context *ac)
+{
+ struct ldb_context *ldb;
+ struct ldb_message_element *oc_element;
+ struct ldb_message *msg;
+ const char **must_contain, **may_contain, **found_must_contain;
+ /* There exists a hardcoded delete-protected attributes list in AD */
+ const char *del_prot_attributes[] = { "nTSecurityDescriptor",
+ "objectSid", "sAMAccountType", "sAMAccountName", "groupType",
+ "primaryGroupID", "userAccountControl", "accountExpires",
+ "badPasswordTime", "badPwdCount", "codePage", "countryCode",
+ "lastLogoff", "lastLogon", "logonCount", "pwdLastSet", NULL },
+ **l;
+ const struct dsdb_attribute *attr;
+ unsigned int i;
+ bool found;
+ bool isSchemaAttr = false;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (ac->search_res == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* We rely here on the preceding "objectclass" LDB module which did
+ * already fix up the objectclass list (inheritance, order...). */
+ oc_element = ldb_msg_find_element(ac->search_res->message,
+ "objectClass");
+ if (oc_element == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* LSA-specific object classes are not allowed to be created over LDAP,
+ * so we need to tell if this connection is internal (trusted) or not
+ * (untrusted).
+ *
+ * Hongwei Sun from Microsoft explains:
+ * The constraint in 3.1.1.5.2.2 MS-ADTS means that LSA objects cannot
+ * be added or modified through the LDAP interface, instead they can
+ * only be handled through LSA Policy API. This is also explained in
+ * 7.1.6.9.7 MS-ADTS as follows:
+ * "Despite being replicated normally between peer DCs in a domain,
+ * the process of creating or manipulating TDOs is specifically
+ * restricted to the LSA Policy APIs, as detailed in [MS-LSAD] section
+ * 3.1.1.5. Unlike other objects in the DS, TDOs may not be created or
+ * manipulated by client machines over the LDAPv3 transport."
+ */
+ for (i = 0; i < oc_element->num_values; i++) {
+ char * attname = (char *)oc_element->values[i].data;
+ if (ldb_req_is_untrusted(ac->req)) {
+ if (strcmp(attname, "secret") == 0 ||
+ strcmp(attname, "trustedDomain") == 0) {
+ ldb_asprintf_errstring(ldb, "objectclass_attrs: LSA objectclasses (entry '%s') cannot be created or changed over LDAP!",
+ ldb_dn_get_linearized(ac->search_res->message->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+ if (strcmp(attname, "attributeSchema") == 0) {
+ isSchemaAttr = true;
+ }
+ }
+
+ must_contain = dsdb_full_attribute_list(ac, ac->schema, oc_element,
+ DSDB_SCHEMA_ALL_MUST);
+ may_contain = dsdb_full_attribute_list(ac, ac->schema, oc_element,
+ DSDB_SCHEMA_ALL_MAY);
+ found_must_contain = const_str_list(str_list_copy(ac, must_contain));
+ if ((must_contain == NULL) || (may_contain == NULL)
+ || (found_must_contain == NULL)) {
+ return ldb_operr(ldb);
+ }
+
+ /* Check the delete-protected attributes list */
+ msg = ac->search_res->message;
+ for (l = del_prot_attributes; *l != NULL; l++) {
+ struct ldb_message_element *el;
+
+ el = ldb_msg_find_element(ac->msg, *l);
+ if (el == NULL) {
+ /*
+ * It was not specified in the add or modify,
+ * so it doesn't need to be in the stored record
+ */
+ continue;
+ }
+
+ found = str_list_check_ci(must_contain, *l);
+ if (!found) {
+ found = str_list_check_ci(may_contain, *l);
+ }
+ if (found && (ldb_msg_find_element(msg, *l) == NULL)) {
+ ldb_asprintf_errstring(ldb, "objectclass_attrs: delete protected attribute '%s' on entry '%s' missing!",
+ *l,
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+
+ /* Check if all specified attributes are valid in the given
+ * objectclasses and if they meet additional schema restrictions. */
+ for (i = 0; i < msg->num_elements; i++) {
+ attr = dsdb_attribute_by_lDAPDisplayName(ac->schema,
+ msg->elements[i].name);
+ if (attr == NULL) {
+ if (ldb_request_get_control(ac->req, DSDB_CONTROL_DBCHECK)) {
+ /* allow this to make it possible for dbcheck
+ to remove bad attributes */
+ continue;
+ }
+ return ldb_operr(ldb);
+ }
+
+ /* We can use "str_list_check" with "strcmp" here since the
+ * attribute information from the schema are always equal
+ * up-down-cased. */
+ found = str_list_check(must_contain, attr->lDAPDisplayName);
+ if (found) {
+ str_list_remove(found_must_contain, attr->lDAPDisplayName);
+ } else {
+ found = str_list_check(may_contain, attr->lDAPDisplayName);
+ }
+ if (!found) {
+ found = str_list_check(harmless_attrs, attr->lDAPDisplayName);
+ }
+ if (!found) {
+ /* we allow this for dbcheck to fix the rest of this broken entry */
+ if (!ldb_request_get_control(ac->req, DSDB_CONTROL_DBCHECK) ||
+ ac->req->operation == LDB_ADD) {
+ ldb_asprintf_errstring(ldb, "objectclass_attrs: attribute '%s' on entry '%s' does not exist in the specified objectclasses!",
+ msg->elements[i].name,
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ }
+ }
+
+ /*
+ * We skip this check under dbcheck to allow fixing of other
+ * attributes even if an attribute is missing. This matters
+ * for CN=RID Set as the required attribute rIDNextRid is not
+ * replicated.
+ */
+ if (found_must_contain[0] != NULL &&
+ ldb_msg_check_string_attribute(msg, "isDeleted", "TRUE") == 0) {
+
+ for (i = 0; found_must_contain[i] != NULL; i++) {
+ const struct dsdb_attribute *broken_attr = dsdb_attribute_by_lDAPDisplayName(ac->schema,
+ found_must_contain[i]);
+
+ bool replicated = (broken_attr->systemFlags &
+ (DS_FLAG_ATTR_NOT_REPLICATED | DS_FLAG_ATTR_IS_CONSTRUCTED)) == 0;
+
+ if (replicated) {
+ ldb_asprintf_errstring(ldb, "objectclass_attrs: at least one mandatory "
+ "attribute ('%s') on entry '%s' wasn't specified!",
+ found_must_contain[i],
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ }
+ }
+
+ if (isSchemaAttr) {
+ /*
+ * Before really adding an attribute in the database,
+ * let's check that we can translate it into a dsdb_attribute and
+ * that we can find a valid syntax object.
+ * If not it's better to reject this attribute than not be able
+ * to start samba next time due to schema being unloadable.
+ */
+ struct dsdb_attribute *att = talloc(ac, struct dsdb_attribute);
+ const struct dsdb_syntax *attrSyntax;
+ WERROR status;
+
+ status = dsdb_attribute_from_ldb(NULL, msg, att);
+ if (!W_ERROR_IS_OK(status)) {
+ ldb_set_errstring(ldb,
+ "objectclass: failed to translate the schemaAttribute to a dsdb_attribute");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ attrSyntax = dsdb_syntax_for_attribute(att);
+ if (!attrSyntax) {
+ ldb_set_errstring(ldb,
+ "objectclass: unknown attribute syntax");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+ return ldb_module_done(ac->req, ac->mod_ares->controls,
+ ac->mod_ares->response, LDB_SUCCESS);
+}
+
+static int get_search_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct oc_context *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct oc_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ ldb_reset_err_string(ldb);
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ if (ac->search_res != NULL) {
+ ldb_set_errstring(ldb, "Too many results");
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ac->search_res = talloc_steal(ac, ares);
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ talloc_free(ares);
+ break;
+
+ case LDB_REPLY_DONE:
+ talloc_free(ares);
+ ret = attr_handler2(ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ break;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int oc_op_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct oc_context *ac;
+ struct ldb_context *ldb;
+ struct ldb_request *search_req;
+ struct ldb_dn *base_dn;
+ int ret;
+ static const char *attrs[] = {"nTSecurityDescriptor", "*", NULL};
+
+ ac = talloc_get_type(req->context, struct oc_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ares->type == LDB_REPLY_REFERRAL) {
+ return ldb_module_send_referral(ac->req, ares->referral);
+ }
+
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls, ares->response,
+ ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ac->search_res = NULL;
+ ac->mod_ares = talloc_steal(ac, ares);
+
+ /* This looks up all attributes of our just added/modified entry */
+ base_dn = ac->req->operation == LDB_ADD ? ac->req->op.add.message->dn
+ : ac->req->op.mod.message->dn;
+ ret = ldb_build_search_req(&search_req, ldb, ac, base_dn,
+ LDB_SCOPE_BASE, "(objectClass=*)",
+ attrs, NULL, ac,
+ get_search_callback, ac->req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ ret = ldb_request_add_control(search_req, LDB_CONTROL_SHOW_RECYCLED_OID,
+ true, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ /*
+ * This ensures we see if there was a DN, that pointed at an
+ * object that is now deleted, that we still consider the
+ * schema check to have passed
+ */
+ ret = ldb_request_add_control(search_req, LDB_CONTROL_REVEAL_INTERNALS,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ ret = ldb_next_request(ac->module, search_req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ /* "ldb_module_done" isn't called here since we need to do additional
+ * checks. It is called at the end of "attr_handler2". */
+ return LDB_SUCCESS;
+}
+
+static int objectclass_attrs_add(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct oc_context *ac;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_attrs_add\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ ac = oc_init_context(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* without schema, there isn't much to do here */
+ if (ac->schema == NULL) {
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+
+ return attr_handler(ac);
+}
+
+static int objectclass_attrs_modify(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct ldb_control *sd_propagation_control;
+ int ret;
+
+ struct oc_context *ac;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "objectclass_attrs_modify\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ sd_propagation_control = ldb_request_get_control(req,
+ DSDB_CONTROL_SEC_DESC_PROPAGATION_OID);
+ if (sd_propagation_control != NULL) {
+ if (req->op.mod.message->num_elements != 1) {
+ return ldb_module_operr(module);
+ }
+ ret = strcmp(req->op.mod.message->elements[0].name,
+ "nTSecurityDescriptor");
+ if (ret != 0) {
+ return ldb_module_operr(module);
+ }
+
+ return ldb_next_request(module, req);
+ }
+
+ ac = oc_init_context(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* without schema, there isn't much to do here */
+ if (ac->schema == NULL) {
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+
+ return attr_handler(ac);
+}
+
+static const struct ldb_module_ops ldb_objectclass_attrs_module_ops = {
+ .name = "objectclass_attrs",
+ .add = objectclass_attrs_add,
+ .modify = objectclass_attrs_modify
+};
+
+int ldb_objectclass_attrs_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_objectclass_attrs_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/objectguid.c b/source4/dsdb/samdb/ldb_modules/objectguid.c
new file mode 100644
index 0000000..cfc8918
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/objectguid.c
@@ -0,0 +1,252 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Simo Sorce 2004-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 objectguid module
+ *
+ * Description: add a unique objectGUID onto every new record
+ *
+ * Author: Simo Sorce
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "param/param.h"
+
+/*
+ add a time element to a record
+*/
+static int add_time_element(struct ldb_message *msg, const char *attr, time_t t)
+{
+ char *s;
+ int ret;
+
+ if (ldb_msg_find_element(msg, attr) != NULL) {
+ return LDB_SUCCESS;
+ }
+
+ s = ldb_timestring(msg, t);
+ if (s == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* always set as replace. This works because on add ops, the flag
+ is ignored */
+ ret = ldb_msg_append_string(msg, attr, s, LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ add a uint64_t element to a record
+*/
+static int add_uint64_element(struct ldb_context *ldb, struct ldb_message *msg,
+ const char *attr, uint64_t v)
+{
+ int ret;
+
+ if (ldb_msg_find_element(msg, attr) != NULL) {
+ return LDB_SUCCESS;
+ }
+
+ /* always set as replace. This works because on add ops, the flag
+ is ignored */
+ ret = samdb_msg_append_uint64(ldb, msg, msg, attr, v, LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+struct og_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+};
+
+/* add_record: add objectGUID and timestamp attributes */
+static int objectguid_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ struct ldb_message_element *el;
+ struct GUID guid;
+ uint64_t seq_num;
+ int ret;
+ time_t t = time(NULL);
+ struct og_context *ac;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(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);
+ }
+
+ el = ldb_msg_find_element(req->op.add.message, "objectGUID");
+ if (el != NULL) {
+ ldb_set_errstring(ldb,
+ "objectguid: objectGUID must not be specified!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ ac = talloc(req, struct og_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->module = module;
+ ac->req = req;
+
+ /* we have to copy the message as the caller might have it as a const */
+ msg = ldb_msg_copy_shallow(ac, req->op.add.message);
+ if (msg == NULL) {
+ talloc_free(ac);
+ return ldb_operr(ldb);
+ }
+
+ /* a new GUID */
+ guid = GUID_random();
+
+ ret = dsdb_msg_add_guid(msg, &guid, "objectGUID");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (add_time_element(msg, "whenCreated", t) != LDB_SUCCESS ||
+ add_time_element(msg, "whenChanged", t) != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ /* Get a sequence number from the backend */
+ ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret == LDB_SUCCESS) {
+ if (add_uint64_element(ldb, msg, "uSNCreated",
+ seq_num) != LDB_SUCCESS ||
+ add_uint64_element(ldb, msg, "uSNChanged",
+ seq_num) != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+ }
+
+ ret = ldb_build_add_req(&down_req, ldb, ac,
+ msg,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* go on with the call chain */
+ return ldb_next_request(module, down_req);
+}
+
+/* modify_record: update timestamps */
+static int objectguid_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ struct ldb_message_element *el;
+ int ret;
+ time_t t = time(NULL);
+ uint64_t seq_num;
+ struct og_context *ac;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "objectguid_modify_record\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ el = ldb_msg_find_element(req->op.mod.message, "objectGUID");
+ if (el != NULL) {
+ ldb_set_errstring(ldb,
+ "objectguid: objectGUID must not be specified!");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ ac = talloc(req, struct og_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->module = module;
+ ac->req = req;
+
+ /* we have to copy the message as the caller might have it as a const */
+ msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
+ if (msg == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ if (add_time_element(msg, "whenChanged", t) != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ /* Get a sequence number from the backend */
+ ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret == LDB_SUCCESS) {
+ if (add_uint64_element(ldb, msg, "uSNChanged",
+ seq_num) != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+ }
+
+ ret = ldb_build_mod_req(&down_req, ldb, ac,
+ msg,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* go on with the call chain */
+ return ldb_next_request(module, down_req);
+}
+
+static const struct ldb_module_ops ldb_objectguid_module_ops = {
+ .name = "objectguid",
+ .add = objectguid_add,
+ .modify = objectguid_modify
+};
+
+int ldb_objectguid_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_objectguid_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/operational.c b/source4/dsdb/samdb/ldb_modules/operational.c
new file mode 100644
index 0000000..214079c
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/operational.c
@@ -0,0 +1,1829 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2010
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Simo Sorce 2006-2008
+ Copyright (C) Matthias Dieter Wallnöfer 2009
+
+ 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/>.
+*/
+
+/*
+ handle operational attributes
+ */
+
+/*
+ createTimeStamp: HIDDEN, searchable, ldaptime, alias for whenCreated
+ modifyTimeStamp: HIDDEN, searchable, ldaptime, alias for whenChanged
+
+ for the above two, we do the search as normal, and if
+ createTimeStamp or modifyTimeStamp is asked for, then do
+ additional searches for whenCreated and whenChanged and fill in
+ the resulting values
+
+ we also need to replace these with the whenCreated/whenChanged
+ equivalent in the search expression trees
+
+ whenCreated: not-HIDDEN, CONSTRUCTED, SEARCHABLE
+ whenChanged: not-HIDDEN, CONSTRUCTED, SEARCHABLE
+
+ on init we need to setup attribute handlers for these so
+ comparisons are done correctly. The resolution is 1 second.
+
+ on add we need to add both the above, for current time
+
+ on modify we need to change whenChanged
+
+ structuralObjectClass: HIDDEN, CONSTRUCTED, not-searchable. always same as objectclass?
+
+ for this one we do the search as normal, then if requested ask
+ for objectclass, change the attribute name, and add it
+
+ primaryGroupToken: HIDDEN, CONSTRUCTED, SEARCHABLE
+
+ contains the RID of a certain group object
+
+
+ attributeTypes: in schema only
+ objectClasses: in schema only
+ matchingRules: in schema only
+ matchingRuleUse: in schema only
+ creatorsName: not supported by w2k3?
+ modifiersName: not supported by w2k3?
+*/
+
+#include "includes.h"
+#include <ldb.h>
+#include <ldb_module.h>
+
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+#include "libcli/security/security.h"
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
+#endif
+
+#undef strcasecmp
+
+struct operational_data {
+ struct ldb_dn *aggregate_dn;
+};
+
+enum search_type {
+ TOKEN_GROUPS,
+ TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL,
+ TOKEN_GROUPS_NO_GC_ACCEPTABLE,
+
+ /*
+ * MS-DRSR 4.1.8.1.3 RevMembGetAccountGroups: Transitive membership in
+ * all account groups in a given domain, excluding built-in groups.
+ * (Used internally for msDS-ResultantPSO support)
+ */
+ ACCOUNT_GROUPS
+};
+
+static int get_pso_for_user(struct ldb_module *module,
+ struct ldb_message *user_msg,
+ struct ldb_request *parent,
+ struct ldb_message **pso_msg);
+
+/*
+ construct a canonical name from a message
+*/
+static int construct_canonical_name(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ char *canonicalName;
+ canonicalName = ldb_dn_canonical_string(msg, msg->dn);
+ if (canonicalName == NULL) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+ return ldb_msg_add_steal_string(msg, "canonicalName", canonicalName);
+}
+
+/*
+ construct a primary group token for groups from a message
+*/
+static int construct_primary_group_token(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct ldb_context *ldb;
+ uint32_t primary_group_token;
+
+ ldb = ldb_module_get_ctx(module);
+ if (ldb_match_msg_objectclass(msg, "group") == 1) {
+ primary_group_token
+ = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
+ if (primary_group_token == 0) {
+ return LDB_SUCCESS;
+ }
+
+ return samdb_msg_add_uint(ldb, msg, msg, "primaryGroupToken",
+ primary_group_token);
+ } else {
+ return LDB_SUCCESS;
+ }
+}
+
+/*
+ * Returns the group SIDs for the user in the given LDB message
+ */
+static int get_group_sids(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
+ struct ldb_message *msg, const char *attribute_string,
+ enum search_type type, struct dom_sid **groupSIDs,
+ unsigned int *num_groupSIDs)
+{
+ const char *filter = NULL;
+ NTSTATUS status;
+ struct dom_sid *primary_group_sid;
+ const char *primary_group_string;
+ const char *primary_group_dn;
+ DATA_BLOB primary_group_blob;
+ struct dom_sid *account_sid;
+ const char *account_sid_string;
+ const char *account_sid_dn;
+ DATA_BLOB account_sid_blob;
+ struct dom_sid *domain_sid;
+
+ /* If it's not a user, it won't have a primaryGroupID */
+ if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ /* Ensure it has an objectSID too */
+ account_sid = samdb_result_dom_sid(mem_ctx, msg, "objectSid");
+ if (account_sid == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ status = dom_sid_split_rid(mem_ctx, account_sid, &domain_sid, NULL);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ primary_group_sid = dom_sid_add_rid(mem_ctx,
+ domain_sid,
+ ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0));
+ if (!primary_group_sid) {
+ return ldb_oom(ldb);
+ }
+
+ /* only return security groups */
+ switch(type) {
+ case TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL:
+ filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u)(|(groupType:1.2.840.113556.1.4.803:=%u)(groupType:1.2.840.113556.1.4.803:=%u)))",
+ GROUP_TYPE_SECURITY_ENABLED, GROUP_TYPE_ACCOUNT_GROUP, GROUP_TYPE_UNIVERSAL_GROUP);
+ break;
+ case TOKEN_GROUPS_NO_GC_ACCEPTABLE:
+ case TOKEN_GROUPS:
+ filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))",
+ GROUP_TYPE_SECURITY_ENABLED);
+ break;
+
+ /* for RevMembGetAccountGroups, exclude built-in groups */
+ case ACCOUNT_GROUPS:
+ filter = talloc_asprintf(mem_ctx, "(&(objectClass=group)(!(groupType:1.2.840.113556.1.4.803:=%u))(groupType:1.2.840.113556.1.4.803:=%u))",
+ GROUP_TYPE_BUILTIN_LOCAL_GROUP, GROUP_TYPE_SECURITY_ENABLED);
+ break;
+ }
+
+ if (!filter) {
+ return ldb_oom(ldb);
+ }
+
+ primary_group_string = dom_sid_string(mem_ctx, primary_group_sid);
+ if (!primary_group_string) {
+ return ldb_oom(ldb);
+ }
+
+ primary_group_dn = talloc_asprintf(mem_ctx, "<SID=%s>", primary_group_string);
+ if (!primary_group_dn) {
+ return ldb_oom(ldb);
+ }
+
+ primary_group_blob = data_blob_string_const(primary_group_dn);
+
+ account_sid_string = dom_sid_string(mem_ctx, account_sid);
+ if (!account_sid_string) {
+ return ldb_oom(ldb);
+ }
+
+ account_sid_dn = talloc_asprintf(mem_ctx, "<SID=%s>", account_sid_string);
+ if (!account_sid_dn) {
+ return ldb_oom(ldb);
+ }
+
+ account_sid_blob = data_blob_string_const(account_sid_dn);
+
+ status = dsdb_expand_nested_groups(ldb, &account_sid_blob,
+ true, /* We don't want to add the object's SID itself,
+ it's not returend in this attribute */
+ filter,
+ mem_ctx, groupSIDs, num_groupSIDs);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
+ attribute_string, account_sid_string,
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Expands the primary group - this function takes in
+ * memberOf-like values, so we fake one up with the
+ * <SID=S-...> format of DN and then let it expand
+ * them, as long as they meet the filter - so only
+ * domain groups, not builtin groups
+ */
+ status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter,
+ mem_ctx, groupSIDs, num_groupSIDs);
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_asprintf_errstring(ldb, "Failed to construct %s: expanding groups of SID %s failed: %s",
+ attribute_string, account_sid_string,
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ construct the token groups for SAM objects from a message
+*/
+static int construct_generic_token_groups(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent,
+ const char *attribute_string,
+ enum search_type type)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx = talloc_new(msg);
+ unsigned int i;
+ int ret;
+ struct dom_sid *groupSIDs = NULL;
+ unsigned int num_groupSIDs = 0;
+
+ if (scope != LDB_SCOPE_BASE) {
+ ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* calculate the group SIDs for this object */
+ ret = get_group_sids(ldb, tmp_ctx, msg, attribute_string, type,
+ &groupSIDs, &num_groupSIDs);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* add these SIDs to the search result */
+ for (i=0; i < num_groupSIDs; i++) {
+ ret = samdb_msg_add_dom_sid(ldb, msg, msg, attribute_string, &groupSIDs[i]);
+ if (ret) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int construct_token_groups(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ /**
+ * TODO: Add in a limiting domain when we start to support
+ * trusted domains.
+ */
+ return construct_generic_token_groups(module, msg, scope, parent,
+ "tokenGroups",
+ TOKEN_GROUPS);
+}
+
+static int construct_token_groups_no_gc(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ /**
+ * TODO: Add in a limiting domain when we start to support
+ * trusted domains.
+ */
+ return construct_generic_token_groups(module, msg, scope, parent,
+ "tokenGroupsNoGCAcceptable",
+ TOKEN_GROUPS);
+}
+
+static int construct_global_universal_token_groups(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ return construct_generic_token_groups(module, msg, scope, parent,
+ "tokenGroupsGlobalAndUniversal",
+ TOKEN_GROUPS_GLOBAL_AND_UNIVERSAL);
+}
+/*
+ construct the parent GUID for an entry from a message
+*/
+static int construct_parent_guid(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct ldb_result *res, *parent_res;
+ const struct ldb_val *parent_guid;
+ const char *attrs[] = { "instanceType", NULL };
+ const char *attrs2[] = { "objectGUID", NULL };
+ uint32_t instanceType;
+ int ret;
+ struct ldb_dn *parent_dn;
+ struct ldb_val v;
+
+ /* determine if the object is NC by instance type */
+ ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED, parent);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ instanceType = ldb_msg_find_attr_as_uint(res->msgs[0],
+ "instanceType", 0);
+ talloc_free(res);
+ if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) {
+ DEBUG(4,(__location__ ": Object %s is NC\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_SUCCESS;
+ }
+ parent_dn = ldb_dn_get_parent(msg, msg->dn);
+
+ if (parent_dn == NULL) {
+ DEBUG(4,(__location__ ": Failed to find parent for dn %s\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_ERR_OTHER;
+ }
+ ret = dsdb_module_search_dn(module, msg, &parent_res, parent_dn, attrs2,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED, parent);
+ /* not NC, so the object should have a parent*/
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ret = ldb_error(ldb_module_get_ctx(module), LDB_ERR_OPERATIONS_ERROR,
+ talloc_asprintf(msg, "Parent dn %s for %s does not exist",
+ ldb_dn_get_linearized(parent_dn),
+ ldb_dn_get_linearized(msg->dn)));
+ talloc_free(parent_dn);
+ return ret;
+ } else if (ret != LDB_SUCCESS) {
+ talloc_free(parent_dn);
+ return ret;
+ }
+ talloc_free(parent_dn);
+
+ parent_guid = ldb_msg_find_ldb_val(parent_res->msgs[0], "objectGUID");
+ if (!parent_guid) {
+ talloc_free(parent_res);
+ return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+ }
+
+ v = data_blob_dup_talloc(parent_res, *parent_guid);
+ if (!v.data) {
+ talloc_free(parent_res);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+ ret = ldb_msg_add_steal_value(msg, "parentGUID", &v);
+ talloc_free(parent_res);
+ return ret;
+}
+
+static int construct_modifyTimeStamp(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ /* We may be being called before the init function has finished */
+ if (!data) {
+ return LDB_SUCCESS;
+ }
+
+ /* Try and set this value up, if possible. Don't worry if it
+ * fails, we may not have the DB set up yet.
+ */
+ if (!data->aggregate_dn) {
+ data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
+ }
+
+ if (data->aggregate_dn && ldb_dn_compare(data->aggregate_dn, msg->dn) == 0) {
+ /*
+ * If we have the DN for the object with common name = Aggregate and
+ * the request is for this DN then let's do the following:
+ * 1) search the object which changedUSN correspond to the one of the loaded
+ * schema.
+ * 2) Get the whenChanged attribute
+ * 3) Generate the modifyTimestamp out of the whenChanged attribute
+ */
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
+ char *value = ldb_timestring(msg, schema->ts_last_change);
+
+ if (value == NULL) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ return ldb_msg_add_string(msg, "modifyTimeStamp", value);
+ }
+ return ldb_msg_copy_attr(msg, "whenChanged", "modifyTimeStamp");
+}
+
+/*
+ construct a subSchemaSubEntry
+*/
+static int construct_subschema_subentry(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct operational_data *data = talloc_get_type(ldb_module_get_private(module), struct operational_data);
+ char *subSchemaSubEntry;
+
+ /* We may be being called before the init function has finished */
+ if (!data) {
+ return LDB_SUCCESS;
+ }
+
+ /* Try and set this value up, if possible. Don't worry if it
+ * fails, we may not have the DB set up yet, and it's not
+ * really vital anyway */
+ if (!data->aggregate_dn) {
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
+ }
+
+ if (data->aggregate_dn) {
+ subSchemaSubEntry = ldb_dn_alloc_linearized(msg, data->aggregate_dn);
+ return ldb_msg_add_steal_string(msg, "subSchemaSubEntry", subSchemaSubEntry);
+ }
+ return LDB_SUCCESS;
+}
+
+
+static int construct_msds_isrodc_with_dn(struct ldb_module *module,
+ struct ldb_message *msg,
+ struct ldb_message_element *object_category)
+{
+ struct ldb_context *ldb;
+ struct ldb_dn *dn;
+ const struct ldb_val *val;
+
+ ldb = ldb_module_get_ctx(module);
+ if (!ldb) {
+ DEBUG(4, (__location__ ": Failed to get ldb \n"));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ dn = ldb_dn_new(msg, ldb, (const char *)object_category->values[0].data);
+ if (!dn) {
+ DEBUG(4, (__location__ ": Failed to create dn from %s \n",
+ (const char *)object_category->values[0].data));
+ return ldb_operr(ldb);
+ }
+
+ val = ldb_dn_get_rdn_val(dn);
+ if (!val) {
+ DEBUG(4, (__location__ ": Failed to get rdn val from %s \n",
+ ldb_dn_get_linearized(dn)));
+ return ldb_operr(ldb);
+ }
+
+ if (strequal((const char *)val->data, "NTDS-DSA")) {
+ ldb_msg_add_string(msg, "msDS-isRODC", "FALSE");
+ } else {
+ ldb_msg_add_string(msg, "msDS-isRODC", "TRUE");
+ }
+ return LDB_SUCCESS;
+}
+
+static int construct_msds_isrodc_with_server_dn(struct ldb_module *module,
+ struct ldb_message *msg,
+ struct ldb_dn *dn,
+ struct ldb_request *parent)
+{
+ struct ldb_dn *server_dn;
+ const char *attr_obj_cat[] = { "objectCategory", NULL };
+ struct ldb_result *res;
+ struct ldb_message_element *object_category;
+ int ret;
+
+ server_dn = ldb_dn_copy(msg, dn);
+ if (!ldb_dn_add_child_fmt(server_dn, "CN=NTDS Settings")) {
+ DEBUG(4, (__location__ ": Failed to add child to %s \n",
+ ldb_dn_get_linearized(server_dn)));
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ ret = dsdb_module_search_dn(module, msg, &res, server_dn, attr_obj_cat,
+ DSDB_FLAG_NEXT_MODULE, parent);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
+ ldb_dn_get_linearized(server_dn)));
+ return LDB_SUCCESS;
+ } else if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ object_category = ldb_msg_find_element(res->msgs[0], "objectCategory");
+ if (!object_category) {
+ DEBUG(4,(__location__ ": Can't find objectCategory for %s \n",
+ ldb_dn_get_linearized(res->msgs[0]->dn)));
+ return LDB_SUCCESS;
+ }
+ return construct_msds_isrodc_with_dn(module, msg, object_category);
+}
+
+static int construct_msds_isrodc_with_computer_dn(struct ldb_module *module,
+ struct ldb_message *msg,
+ struct ldb_request *parent)
+{
+ int ret;
+ struct ldb_dn *server_dn;
+
+ ret = dsdb_module_reference_dn(module, msg, msg->dn, "serverReferenceBL",
+ &server_dn, parent);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
+ /* it's OK if we can't find serverReferenceBL attribute */
+ DEBUG(4,(__location__ ": Can't get serverReferenceBL for %s \n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_SUCCESS;
+ } else if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return construct_msds_isrodc_with_server_dn(module, msg, server_dn, parent);
+}
+
+/*
+ construct msDS-isRODC attr
+*/
+static int construct_msds_isrodc(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct ldb_message_element * object_class;
+ struct ldb_message_element * object_category;
+ unsigned int i;
+
+ object_class = ldb_msg_find_element(msg, "objectClass");
+ if (!object_class) {
+ DEBUG(4,(__location__ ": Can't get objectClass for %s \n",
+ ldb_dn_get_linearized(msg->dn)));
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ for (i=0; i<object_class->num_values; i++) {
+ if (strequal((const char*)object_class->values[i].data, "nTDSDSA")) {
+ /* If TO!objectCategory equals the DN of the classSchema object for the nTDSDSA
+ * object class, then TO!msDS-isRODC is false. Otherwise, TO!msDS-isRODC is true.
+ */
+ object_category = ldb_msg_find_element(msg, "objectCategory");
+ if (!object_category) {
+ DEBUG(4,(__location__ ": Can't get objectCategory for %s \n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_SUCCESS;
+ }
+ return construct_msds_isrodc_with_dn(module, msg, object_category);
+ }
+ if (strequal((const char*)object_class->values[i].data, "server")) {
+ /* Let TN be the nTDSDSA object whose DN is "CN=NTDS Settings," prepended to
+ * the DN of TO. Apply the previous rule for the "TO is an nTDSDSA object" case,
+ * substituting TN for TO.
+ */
+ return construct_msds_isrodc_with_server_dn(module, msg, msg->dn, parent);
+ }
+ if (strequal((const char*)object_class->values[i].data, "computer")) {
+ /* Let TS be the server object named by TO!serverReferenceBL. Apply the previous
+ * rule for the "TO is a server object" case, substituting TS for TO.
+ */
+ return construct_msds_isrodc_with_computer_dn(module, msg, parent);
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ construct msDS-keyVersionNumber attr
+
+ TODO: Make this based on the 'win2k' DS huristics bit...
+
+*/
+static int construct_msds_keyversionnumber(struct ldb_module *module,
+ struct ldb_message *msg,
+ enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ uint32_t i;
+ enum ndr_err_code ndr_err;
+ const struct ldb_val *omd_value;
+ struct replPropertyMetaDataBlob *omd;
+ int ret;
+
+ omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
+ if (!omd_value) {
+ /* We can't make up a key version number without meta data */
+ return LDB_SUCCESS;
+ }
+
+ omd = talloc(msg, struct replPropertyMetaDataBlob);
+ if (!omd) {
+ ldb_module_oom(module);
+ return LDB_SUCCESS;
+ }
+
+ ndr_err = ndr_pull_struct_blob(omd_value, omd, omd,
+ (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ if (omd->version != 1) {
+ DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s when trying to add msDS-KeyVersionNumber\n",
+ omd->version, ldb_dn_get_linearized(msg->dn)));
+ talloc_free(omd);
+ return LDB_SUCCESS;
+ }
+ for (i=0; i<omd->ctr.ctr1.count; i++) {
+ if (omd->ctr.ctr1.array[i].attid == DRSUAPI_ATTID_unicodePwd) {
+ ret = samdb_msg_add_uint(ldb_module_get_ctx(module),
+ msg, msg,
+ "msDS-KeyVersionNumber",
+ omd->ctr.ctr1.array[i].version);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(omd);
+ return ret;
+ }
+ break;
+ }
+ }
+ return LDB_SUCCESS;
+
+}
+
+#define _UF_TRUST_ACCOUNTS ( \
+ UF_WORKSTATION_TRUST_ACCOUNT | \
+ UF_SERVER_TRUST_ACCOUNT | \
+ UF_INTERDOMAIN_TRUST_ACCOUNT \
+)
+#define _UF_NO_EXPIRY_ACCOUNTS ( \
+ UF_SMARTCARD_REQUIRED | \
+ UF_DONT_EXPIRE_PASSWD | \
+ _UF_TRUST_ACCOUNTS \
+)
+
+
+/*
+ * Returns the Effective-MaximumPasswordAge for a user
+ */
+static int64_t get_user_max_pwd_age(struct ldb_module *module,
+ struct ldb_message *user_msg,
+ struct ldb_request *parent,
+ struct ldb_dn *nc_root)
+{
+ int ret;
+ struct ldb_message *pso = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ /* if a PSO applies to the user, use its maxPwdAge */
+ ret = get_pso_for_user(module, user_msg, parent, &pso);
+ if (ret != LDB_SUCCESS) {
+
+ /* log the error, but fallback to the domain default */
+ DBG_ERR("Error retrieving PSO for %s\n",
+ ldb_dn_get_linearized(user_msg->dn));
+ }
+
+ if (pso != NULL) {
+ return ldb_msg_find_attr_as_int64(pso,
+ "msDS-MaximumPasswordAge", 0);
+ }
+
+ /* otherwise return the default domain value */
+ return samdb_search_int64(ldb, user_msg, 0, nc_root, "maxPwdAge", NULL);
+}
+
+/*
+ calculate msDS-UserPasswordExpiryTimeComputed
+*/
+static NTTIME get_msds_user_password_expiry_time_computed(struct ldb_module *module,
+ struct ldb_message *msg,
+ struct ldb_request *parent,
+ struct ldb_dn *domain_dn)
+{
+ int64_t pwdLastSet, maxPwdAge;
+ uint32_t userAccountControl;
+ NTTIME ret;
+
+ userAccountControl = ldb_msg_find_attr_as_uint(msg,
+ "userAccountControl",
+ 0);
+ if (userAccountControl & _UF_NO_EXPIRY_ACCOUNTS) {
+ return 0x7FFFFFFFFFFFFFFFULL;
+ }
+
+ pwdLastSet = ldb_msg_find_attr_as_int64(msg, "pwdLastSet", 0);
+ if (pwdLastSet == 0) {
+ return 0;
+ }
+
+ if (pwdLastSet <= -1) {
+ /*
+ * This can't really happen...
+ */
+ return 0x7FFFFFFFFFFFFFFFULL;
+ }
+
+ if (pwdLastSet >= 0x7FFFFFFFFFFFFFFFLL) {
+ /*
+ * Somethings wrong with the clock...
+ */
+ return 0x7FFFFFFFFFFFFFFFULL;
+ }
+
+ /*
+ * Note that maxPwdAge is a stored as negative value.
+ *
+ * Possible values are in the range of:
+ *
+ * maxPwdAge: -864000000001
+ * to
+ * maxPwdAge: -9223372036854775808 (-0x8000000000000000ULL)
+ *
+ */
+ maxPwdAge = get_user_max_pwd_age(module, msg, parent, domain_dn);
+ if (maxPwdAge >= -864000000000) {
+ /*
+ * This is not really possible...
+ */
+ return 0x7FFFFFFFFFFFFFFFULL;
+ }
+
+ if (maxPwdAge == -0x8000000000000000LL) {
+ return 0x7FFFFFFFFFFFFFFFULL;
+ }
+
+ /*
+ * Note we already caught maxPwdAge == -0x8000000000000000ULL
+ * and pwdLastSet >= 0x7FFFFFFFFFFFFFFFULL above.
+ *
+ * Remember maxPwdAge is a negative number,
+ * so it results in the following.
+ *
+ * 0x7FFFFFFFFFFFFFFEULL + 0x7FFFFFFFFFFFFFFFULL
+ * =
+ * 0xFFFFFFFFFFFFFFFDULL
+ *
+ * or to put it another way, adding two numbers less than 1<<63 can't
+ * ever be more than 1<<64, therefore this result can't wrap.
+ */
+ ret = (NTTIME)pwdLastSet - (NTTIME)maxPwdAge;
+ if (ret >= 0x7FFFFFFFFFFFFFFFULL) {
+ return 0x7FFFFFFFFFFFFFFFULL;
+ }
+
+ return ret;
+}
+
+/*
+ * Returns the Effective-LockoutDuration for a user
+ */
+static int64_t get_user_lockout_duration(struct ldb_module *module,
+ struct ldb_message *user_msg,
+ struct ldb_request *parent,
+ struct ldb_dn *nc_root)
+{
+ int ret;
+ struct ldb_message *pso = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ /* if a PSO applies to the user, use its lockoutDuration */
+ ret = get_pso_for_user(module, user_msg, parent, &pso);
+ if (ret != LDB_SUCCESS) {
+
+ /* log the error, but fallback to the domain default */
+ DBG_ERR("Error retrieving PSO for %s\n",
+ ldb_dn_get_linearized(user_msg->dn));
+ }
+
+ if (pso != NULL) {
+ return ldb_msg_find_attr_as_int64(pso,
+ "msDS-LockoutDuration", 0);
+ }
+
+ /* otherwise return the default domain value */
+ return samdb_search_int64(ldb, user_msg, 0, nc_root, "lockoutDuration",
+ NULL);
+}
+
+/*
+ construct msDS-User-Account-Control-Computed attr
+*/
+static int construct_msds_user_account_control_computed(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ uint32_t userAccountControl;
+ uint32_t msDS_User_Account_Control_Computed = 0;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ NTTIME now;
+ struct ldb_dn *nc_root;
+ int ret;
+
+ ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
+ if (ret != 0) {
+ ldb_asprintf_errstring(ldb,
+ "Failed to find NC root of DN: %s: %s",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb_module_get_ctx(module)));
+ return ret;
+ }
+ if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
+ /* Only calculate this on our default NC */
+ return 0;
+ }
+ /* Test account expire time */
+ unix_to_nt_time(&now, time(NULL));
+
+ userAccountControl = ldb_msg_find_attr_as_uint(msg,
+ "userAccountControl",
+ 0);
+ if (!(userAccountControl & _UF_TRUST_ACCOUNTS)) {
+
+ int64_t lockoutTime = ldb_msg_find_attr_as_int64(msg, "lockoutTime", 0);
+ if (lockoutTime != 0) {
+ int64_t lockoutDuration;
+
+ lockoutDuration = get_user_lockout_duration(module, msg,
+ parent,
+ nc_root);
+
+ /* zero locks out until the administrator intervenes */
+ if (lockoutDuration >= 0) {
+ msDS_User_Account_Control_Computed |= UF_LOCKOUT;
+ } else if (lockoutTime - lockoutDuration >= now) {
+ msDS_User_Account_Control_Computed |= UF_LOCKOUT;
+ }
+ }
+ }
+
+ if (!(userAccountControl & _UF_NO_EXPIRY_ACCOUNTS)) {
+ NTTIME must_change_time
+ = get_msds_user_password_expiry_time_computed(module,
+ msg,
+ parent,
+ nc_root);
+ /* check for expired password */
+ if (must_change_time < now) {
+ msDS_User_Account_Control_Computed |= UF_PASSWORD_EXPIRED;
+ }
+ }
+
+ return samdb_msg_add_int64(ldb,
+ msg->elements, msg,
+ "msDS-User-Account-Control-Computed",
+ msDS_User_Account_Control_Computed);
+}
+
+/*
+ construct msDS-UserPasswordExpiryTimeComputed
+*/
+static int construct_msds_user_password_expiry_time_computed(struct ldb_module *module,
+ struct ldb_message *msg, enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_dn *nc_root;
+ int64_t password_expiry_time;
+ int ret;
+
+ ret = dsdb_find_nc_root(ldb, msg, msg->dn, &nc_root);
+ if (ret != 0) {
+ ldb_asprintf_errstring(ldb,
+ "Failed to find NC root of DN: %s: %s",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb));
+ return ret;
+ }
+
+ if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) != 0) {
+ /* Only calculate this on our default NC */
+ return 0;
+ }
+
+ password_expiry_time
+ = get_msds_user_password_expiry_time_computed(module, msg,
+ parent, nc_root);
+
+ return samdb_msg_add_int64(ldb,
+ msg->elements, msg,
+ "msDS-UserPasswordExpiryTimeComputed",
+ password_expiry_time);
+}
+
+/*
+ * Checks whether the msDS-ResultantPSO attribute is supported for a given
+ * user object. As per MS-ADTS, section 3.1.1.4.5.36 msDS-ResultantPSO.
+ */
+static bool pso_is_supported(struct ldb_context *ldb, struct ldb_message *msg)
+{
+ int functional_level;
+ uint32_t uac;
+ uint32_t user_rid;
+
+ functional_level = dsdb_functional_level(ldb);
+ if (functional_level < DS_DOMAIN_FUNCTION_2008) {
+ return false;
+ }
+
+ /* msDS-ResultantPSO is only supported for user objects */
+ if (!ldb_match_msg_objectclass(msg, "user")) {
+ return false;
+ }
+
+ /* ...and only if the ADS_UF_NORMAL_ACCOUNT bit is set */
+ uac = ldb_msg_find_attr_as_uint(msg, "userAccountControl", 0);
+ if (!(uac & UF_NORMAL_ACCOUNT)) {
+ return false;
+ }
+
+ /* skip it if it's the special KRBTGT default account */
+ user_rid = samdb_result_rid_from_sid(msg, msg, "objectSid", 0);
+ if (user_rid == DOMAIN_RID_KRBTGT) {
+ return false;
+ }
+
+ /* ...or if it's a special KRBTGT account for an RODC KDC */
+ if (ldb_msg_find_ldb_val(msg, "msDS-SecondaryKrbTgtNumber") != NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Returns the number of PSO objects that exist in the DB
+ */
+static int get_pso_count(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_request *parent, int *pso_count)
+{
+ static const char * const attrs[] = { NULL };
+ int ret;
+ struct ldb_dn *psc_dn = NULL;
+ struct ldb_result *res = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ bool psc_ok;
+
+ *pso_count = 0;
+ psc_dn = samdb_system_container_dn(ldb, mem_ctx);
+ if (psc_dn == NULL) {
+ return ldb_oom(ldb);
+ }
+ psc_ok = ldb_dn_add_child_fmt(psc_dn, "CN=Password Settings Container");
+ if (psc_ok == false) {
+ return ldb_oom(ldb);
+ }
+
+ /* get the number of PSO children */
+ ret = dsdb_module_search(module, mem_ctx, &res, psc_dn,
+ LDB_SCOPE_ONELEVEL, attrs,
+ DSDB_FLAG_NEXT_MODULE, parent,
+ "(objectClass=msDS-PasswordSettings)");
+
+ /*
+ * Just ignore PSOs if the container doesn't exist. This is a weird
+ * corner-case where the AD DB was created from a pre-2008 base schema,
+ * and then the FL was manually upgraded.
+ */
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ DBG_NOTICE("No Password Settings Container exists\n");
+ return LDB_SUCCESS;
+ }
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ *pso_count = res->count;
+ talloc_free(res);
+ talloc_free(psc_dn);
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * Compares two PSO objects returned by a search, to work out the better PSO.
+ * The PSO with the lowest precedence is better, otherwise (if the precedence
+ * is equal) the PSO with the lower GUID wins.
+ */
+static int pso_compare(struct ldb_message **m1, struct ldb_message **m2)
+{
+ uint32_t prec1;
+ uint32_t prec2;
+
+ prec1 = ldb_msg_find_attr_as_uint(*m1, "msDS-PasswordSettingsPrecedence",
+ 0xffffffff);
+ prec2 = ldb_msg_find_attr_as_uint(*m2, "msDS-PasswordSettingsPrecedence",
+ 0xffffffff);
+
+ /* if precedence is equal, use the lowest GUID */
+ if (prec1 == prec2) {
+ struct GUID guid1 = samdb_result_guid(*m1, "objectGUID");
+ struct GUID guid2 = samdb_result_guid(*m2, "objectGUID");
+
+ return ndr_guid_compare(&guid1, &guid2);
+ } else {
+ return prec1 - prec2;
+ }
+}
+
+/*
+ * Search for PSO objects that apply to the object SIDs specified
+ */
+static int pso_search_by_sids(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_request *parent,
+ struct dom_sid *sid_array, unsigned int num_sids,
+ struct ldb_result **result)
+{
+ int ret;
+ int i;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ char *sid_filter = NULL;
+ struct ldb_dn *psc_dn = NULL;
+ bool psc_ok;
+ const char *attrs[] = {
+ "msDS-PasswordSettingsPrecedence",
+ "objectGUID",
+ "msDS-LockoutDuration",
+ "msDS-MaximumPasswordAge",
+ NULL
+ };
+
+ /* build a query for PSO objects that apply to any of the SIDs given */
+ sid_filter = talloc_strdup(mem_ctx, "");
+
+ for (i = 0; sid_filter && i < num_sids; i++) {
+ struct dom_sid_buf sid_buf;
+
+ sid_filter = talloc_asprintf_append(
+ sid_filter,
+ "(msDS-PSOAppliesTo=<SID=%s>)",
+ dom_sid_str_buf(&sid_array[i], &sid_buf));
+ }
+
+ if (sid_filter == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /* only PSOs located in the Password Settings Container are valid */
+ psc_dn = samdb_system_container_dn(ldb, mem_ctx);
+ if (psc_dn == NULL) {
+ return ldb_oom(ldb);
+ }
+ psc_ok = ldb_dn_add_child_fmt(psc_dn, "CN=Password Settings Container");
+ if (psc_ok == false) {
+ return ldb_oom(ldb);
+ }
+
+ ret = dsdb_module_search(module, mem_ctx, result, psc_dn,
+ LDB_SCOPE_ONELEVEL, attrs,
+ DSDB_FLAG_NEXT_MODULE, parent,
+ "(&(objectClass=msDS-PasswordSettings)(|%s))",
+ sid_filter);
+ talloc_free(sid_filter);
+ return ret;
+}
+
+/*
+ * Returns the best PSO object that applies to the object SID(s) specified
+ */
+static int pso_find_best(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_request *parent, struct dom_sid *sid_array,
+ unsigned int num_sids, struct ldb_message **best_pso)
+{
+ struct ldb_result *res = NULL;
+ int ret;
+
+ *best_pso = NULL;
+
+ /* find any PSOs that apply to the SIDs specified */
+ ret = pso_search_by_sids(module, mem_ctx, parent, sid_array, num_sids,
+ &res);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error %d retrieving PSO for SID(s)\n", ret);
+ return ret;
+ }
+
+ /* sort the list so that the best PSO is first */
+ TYPESAFE_QSORT(res->msgs, res->count, pso_compare);
+
+ if (res->count > 0) {
+ *best_pso = res->msgs[0];
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * Determines the Password Settings Object (PSO) that applies to the given user
+ */
+static int get_pso_for_user(struct ldb_module *module,
+ struct ldb_message *user_msg,
+ struct ldb_request *parent,
+ struct ldb_message **pso_msg)
+{
+ bool pso_supported;
+ struct dom_sid *groupSIDs = NULL;
+ unsigned int num_groupSIDs = 0;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_message *best_pso = NULL;
+ struct ldb_dn *pso_dn = NULL;
+ int ret;
+ struct ldb_message_element *el = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+ int pso_count = 0;
+ struct ldb_result *res = NULL;
+ static const char *attrs[] = {
+ "msDS-LockoutDuration",
+ "msDS-MaximumPasswordAge",
+ NULL
+ };
+
+ *pso_msg = NULL;
+
+ /* first, check msDS-ResultantPSO is supported for this object */
+ pso_supported = pso_is_supported(ldb, user_msg);
+
+ if (!pso_supported) {
+ return LDB_SUCCESS;
+ }
+
+ tmp_ctx = talloc_new(user_msg);
+
+ /*
+ * Several different constructed attributes try to use the PSO info. If
+ * we've already constructed the msDS-ResultantPSO for this user, we can
+ * just re-use the result, rather than calculating it from scratch again
+ */
+ pso_dn = ldb_msg_find_attr_as_dn(ldb, tmp_ctx, user_msg,
+ "msDS-ResultantPSO");
+
+ if (pso_dn != NULL) {
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res, pso_dn,
+ attrs, DSDB_FLAG_NEXT_MODULE,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error %d retrieving PSO %s\n", ret,
+ ldb_dn_get_linearized(pso_dn));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (res->count == 1) {
+ *pso_msg = res->msgs[0];
+ return LDB_SUCCESS;
+ }
+ }
+
+ /*
+ * if any PSOs apply directly to the user, they are considered first
+ * before we check group membership PSOs
+ */
+ el = ldb_msg_find_element(user_msg, "msDS-PSOApplied");
+
+ if (el != NULL && el->num_values > 0) {
+ struct dom_sid *user_sid = NULL;
+
+ /* lookup the best PSO object, based on the user's SID */
+ user_sid = samdb_result_dom_sid(tmp_ctx, user_msg, "objectSid");
+
+ ret = pso_find_best(module, tmp_ctx, parent, user_sid, 1,
+ &best_pso);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (best_pso != NULL) {
+ *pso_msg = best_pso;
+ return LDB_SUCCESS;
+ }
+ }
+
+ /*
+ * If no valid PSO applies directly to the user, then try its groups.
+ * The group expansion is expensive, so check there are actually
+ * PSOs in the DB first (which is a quick search). Note in the above
+ * cases we could tell that a PSO applied to the user, based on info
+ * already retrieved by the user search.
+ */
+ ret = get_pso_count(module, tmp_ctx, parent, &pso_count);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error %d determining PSOs in system\n", ret);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (pso_count == 0) {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ /* Work out the SIDs of any account groups the user is a member of */
+ ret = get_group_sids(ldb, tmp_ctx, user_msg,
+ "msDS-ResultantPSO", ACCOUNT_GROUPS,
+ &groupSIDs, &num_groupSIDs);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error %d determining group SIDs for %s\n", ret,
+ ldb_dn_get_linearized(user_msg->dn));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* lookup the best PSO that applies to any of these groups */
+ ret = pso_find_best(module, tmp_ctx, parent, groupSIDs,
+ num_groupSIDs, &best_pso);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ *pso_msg = best_pso;
+ return LDB_SUCCESS;
+}
+
+/*
+ * Constructs the msDS-ResultantPSO attribute, which is the DN of the Password
+ * Settings Object (PSO) that applies to that user.
+ */
+static int construct_resultant_pso(struct ldb_module *module,
+ struct ldb_message *msg,
+ enum ldb_scope scope,
+ struct ldb_request *parent)
+{
+ struct ldb_message *pso = NULL;
+ int ret;
+
+ /* work out the PSO (if any) that applies to this user */
+ ret = get_pso_for_user(module, msg, parent, &pso);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Couldn't determine PSO for %s\n",
+ ldb_dn_get_linearized(msg->dn));
+ return ret;
+ }
+
+ if (pso != NULL) {
+ DBG_INFO("%s is resultant PSO for user %s\n",
+ ldb_dn_get_linearized(pso->dn),
+ ldb_dn_get_linearized(msg->dn));
+ return ldb_msg_add_string(msg, "msDS-ResultantPSO",
+ ldb_dn_get_linearized(pso->dn));
+ }
+
+ /* no PSO applies to this user */
+ return LDB_SUCCESS;
+}
+
+struct op_controls_flags {
+ bool sd;
+ bool bypassoperational;
+};
+
+static bool check_keep_control_for_attribute(struct op_controls_flags* controls_flags, const char* attr) {
+ if (controls_flags->bypassoperational && ldb_attr_cmp(attr, "msDS-KeyVersionNumber") == 0 ) {
+ return true;
+ }
+ return false;
+}
+
+/*
+ a list of attribute names that should be substituted in the parse
+ tree before the search is done
+*/
+static const struct {
+ const char *attr;
+ const char *replace;
+} parse_tree_sub[] = {
+ { "createTimeStamp", "whenCreated" },
+ { "modifyTimeStamp", "whenChanged" }
+};
+
+
+struct op_attributes_replace {
+ const char *attr;
+ const char *replace;
+ const char * const *extra_attrs;
+ int (*constructor)(struct ldb_module *, struct ldb_message *, enum ldb_scope, struct ldb_request *);
+};
+
+/* the 'extra_attrs' required for msDS-ResultantPSO */
+#define RESULTANT_PSO_COMPUTED_ATTRS \
+ "msDS-PSOApplied", \
+ "userAccountControl", \
+ "objectSid", \
+ "msDS-SecondaryKrbTgtNumber", \
+ "primaryGroupID"
+
+/*
+ * any other constructed attributes that want to work out the PSO also need to
+ * include objectClass (this gets included via 'replace' for msDS-ResultantPSO)
+ */
+#define PSO_ATTR_DEPENDENCIES \
+ RESULTANT_PSO_COMPUTED_ATTRS, \
+ "objectClass"
+
+static const char *objectSid_attr[] =
+{
+ "objectSid",
+ NULL
+};
+
+
+static const char *objectCategory_attr[] =
+{
+ "objectCategory",
+ NULL
+};
+
+
+static const char *user_account_control_computed_attrs[] =
+{
+ "lockoutTime",
+ "pwdLastSet",
+ PSO_ATTR_DEPENDENCIES,
+ NULL
+};
+
+
+static const char *user_password_expiry_time_computed_attrs[] =
+{
+ "pwdLastSet",
+ PSO_ATTR_DEPENDENCIES,
+ NULL
+};
+
+static const char *resultant_pso_computed_attrs[] =
+{
+ RESULTANT_PSO_COMPUTED_ATTRS,
+ NULL
+};
+
+/*
+ a list of attribute names that are hidden, but can be searched for
+ using another (non-hidden) name to produce the correct result
+*/
+static const struct op_attributes_replace search_sub[] = {
+ { "createTimeStamp", "whenCreated", NULL , NULL },
+ { "modifyTimeStamp", "whenChanged", NULL , construct_modifyTimeStamp},
+ { "structuralObjectClass", "objectClass", NULL , NULL },
+ { "canonicalName", NULL, NULL , construct_canonical_name },
+ { "primaryGroupToken", "objectClass", objectSid_attr, construct_primary_group_token },
+ { "tokenGroups", "primaryGroupID", objectSid_attr, construct_token_groups },
+ { "tokenGroupsNoGCAcceptable", "primaryGroupID", objectSid_attr, construct_token_groups_no_gc},
+ { "tokenGroupsGlobalAndUniversal", "primaryGroupID", objectSid_attr, construct_global_universal_token_groups },
+ { "parentGUID", "objectGUID", NULL, construct_parent_guid },
+ { "subSchemaSubEntry", NULL, NULL, construct_subschema_subentry },
+ { "msDS-isRODC", "objectClass", objectCategory_attr, construct_msds_isrodc },
+ { "msDS-KeyVersionNumber", "replPropertyMetaData", NULL, construct_msds_keyversionnumber },
+ { "msDS-User-Account-Control-Computed", "userAccountControl", user_account_control_computed_attrs,
+ construct_msds_user_account_control_computed },
+ { "msDS-UserPasswordExpiryTimeComputed", "userAccountControl", user_password_expiry_time_computed_attrs,
+ construct_msds_user_password_expiry_time_computed },
+ { "msDS-ResultantPSO", "objectClass", resultant_pso_computed_attrs,
+ construct_resultant_pso }
+};
+
+
+enum op_remove {
+ OPERATIONAL_REMOVE_ALWAYS, /* remove always */
+ OPERATIONAL_REMOVE_UNASKED,/* remove if not requested */
+ OPERATIONAL_SD_FLAGS, /* show if SD_FLAGS_OID set, or asked for */
+ OPERATIONAL_REMOVE_UNLESS_CONTROL /* remove always unless an adhoc control has been specified */
+};
+
+/*
+ a list of attributes that may need to be removed from the
+ underlying db return
+
+ Some of these are attributes that were once stored, but are now calculated
+*/
+struct op_attributes_operations {
+ const char *attr;
+ enum op_remove op;
+};
+
+static const struct op_attributes_operations operational_remove[] = {
+ { "nTSecurityDescriptor", OPERATIONAL_SD_FLAGS },
+ { "msDS-KeyVersionNumber", OPERATIONAL_REMOVE_UNLESS_CONTROL },
+ { "parentGUID", OPERATIONAL_REMOVE_ALWAYS },
+ { "replPropertyMetaData", OPERATIONAL_REMOVE_UNASKED },
+#define _SEP ,OPERATIONAL_REMOVE_UNASKED},{
+ { DSDB_SECRET_ATTRIBUTES_EX(_SEP), OPERATIONAL_REMOVE_UNASKED }
+};
+
+
+/*
+ post process a search result record. For any search_sub[] attributes that were
+ asked for, we need to call the appropriate copy routine to copy the result
+ into the message, then remove any attributes that we added to the search but
+ were not asked for by the user
+*/
+static int operational_search_post_process(struct ldb_module *module,
+ struct ldb_message *msg,
+ enum ldb_scope scope,
+ const char * const *attrs_from_user,
+ const char * const *attrs_searched_for,
+ struct op_controls_flags* controls_flags,
+ struct op_attributes_operations *list,
+ unsigned int list_size,
+ struct op_attributes_replace *list_replace,
+ unsigned int list_replace_size,
+ struct ldb_request *parent)
+{
+ struct ldb_context *ldb;
+ unsigned int i, a = 0;
+ bool constructed_attributes = false;
+
+ ldb = ldb_module_get_ctx(module);
+
+ /* removed any attrs that should not be shown to the user */
+ for (i=0; i < list_size; i++) {
+ ldb_msg_remove_attr(msg, list[i].attr);
+ }
+
+ for (a=0; a < list_replace_size; a++) {
+ if (check_keep_control_for_attribute(controls_flags,
+ list_replace[a].attr)) {
+ continue;
+ }
+
+ /* construct the new attribute, using either a supplied
+ constructor or a simple copy */
+ constructed_attributes = true;
+ if (list_replace[a].constructor != NULL) {
+ if (list_replace[a].constructor(module, msg, scope, parent) != LDB_SUCCESS) {
+ goto failed;
+ }
+ } else if (ldb_msg_copy_attr(msg,
+ list_replace[a].replace,
+ list_replace[a].attr) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ /* Deletion of the search helper attributes are needed if:
+ * - we generated constructed attributes and
+ * - we aren't requesting all attributes
+ */
+ if ((constructed_attributes) && (!ldb_attr_in_list(attrs_from_user, "*"))) {
+ for (i=0; i < list_replace_size; i++) {
+ /* remove the added search helper attributes, unless
+ * they were asked for by the user */
+ if (list_replace[i].replace != NULL &&
+ !ldb_attr_in_list(attrs_from_user, list_replace[i].replace)) {
+ ldb_msg_remove_attr(msg, list_replace[i].replace);
+ }
+ if (list_replace[i].extra_attrs != NULL) {
+ unsigned int j;
+ for (j=0; list_replace[i].extra_attrs[j]; j++) {
+ if (!ldb_attr_in_list(attrs_from_user, list_replace[i].extra_attrs[j])) {
+ ldb_msg_remove_attr(msg, list_replace[i].extra_attrs[j]);
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+
+failed:
+ ldb_debug_set(ldb, LDB_DEBUG_WARNING,
+ "operational_search_post_process failed for attribute '%s' - %s",
+ list_replace[a].attr, ldb_errstring(ldb));
+ return -1;
+}
+
+/*
+ hook search operations
+*/
+
+struct operational_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+ enum ldb_scope scope;
+ const char * const *attrs;
+ struct op_controls_flags* controls_flags;
+ struct op_attributes_operations *list_operations;
+ unsigned int list_operations_size;
+ struct op_attributes_replace *attrs_to_replace;
+ unsigned int attrs_to_replace_size;
+};
+
+static int operational_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct operational_context *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct operational_context);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ /* for each record returned post-process to add any derived
+ attributes that have been asked for */
+ ret = operational_search_post_process(ac->module,
+ ares->message,
+ ac->scope,
+ ac->attrs,
+ req->op.search.attrs,
+ ac->controls_flags,
+ ac->list_operations,
+ ac->list_operations_size,
+ ac->attrs_to_replace,
+ ac->attrs_to_replace_size,
+ req);
+ if (ret != 0) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ return ldb_module_send_entry(ac->req, ares->message, ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(ac->req, ares->referral);
+
+ case LDB_REPLY_DONE:
+
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, LDB_SUCCESS);
+ }
+
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+static struct op_attributes_operations* operation_get_op_list(TALLOC_CTX *ctx,
+ const char* const* attrs,
+ const char* const* searched_attrs,
+ struct op_controls_flags* controls_flags)
+{
+ int idx = 0;
+ int i;
+ struct op_attributes_operations *list = talloc_zero_array(ctx,
+ struct op_attributes_operations,
+ ARRAY_SIZE(operational_remove) + 1);
+
+ if (list == NULL) {
+ return NULL;
+ }
+
+ for (i=0; i<ARRAY_SIZE(operational_remove); i++) {
+ switch (operational_remove[i].op) {
+ case OPERATIONAL_REMOVE_UNASKED:
+ if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
+ continue;
+ }
+ if (ldb_attr_in_list(searched_attrs, operational_remove[i].attr)) {
+ continue;
+ }
+ list[idx].attr = operational_remove[i].attr;
+ list[idx].op = OPERATIONAL_REMOVE_UNASKED;
+ idx++;
+ break;
+
+ case OPERATIONAL_REMOVE_ALWAYS:
+ list[idx].attr = operational_remove[i].attr;
+ list[idx].op = OPERATIONAL_REMOVE_ALWAYS;
+ idx++;
+ break;
+
+ case OPERATIONAL_REMOVE_UNLESS_CONTROL:
+ if (!check_keep_control_for_attribute(controls_flags, operational_remove[i].attr)) {
+ list[idx].attr = operational_remove[i].attr;
+ list[idx].op = OPERATIONAL_REMOVE_UNLESS_CONTROL;
+ idx++;
+ }
+ break;
+
+ case OPERATIONAL_SD_FLAGS:
+ if (ldb_attr_in_list(attrs, operational_remove[i].attr)) {
+ continue;
+ }
+ if (controls_flags->sd) {
+ if (attrs == NULL) {
+ continue;
+ }
+ if (attrs[0] == NULL) {
+ continue;
+ }
+ if (ldb_attr_in_list(attrs, "*")) {
+ continue;
+ }
+ }
+ list[idx].attr = operational_remove[i].attr;
+ list[idx].op = OPERATIONAL_SD_FLAGS;
+ idx++;
+ break;
+ }
+ }
+
+ return list;
+}
+
+static int operational_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct operational_context *ac;
+ struct ldb_request *down_req;
+ const char **search_attrs = NULL;
+ unsigned int i, a;
+ int ret;
+
+ /* There are no operational attributes on special DNs */
+ if (ldb_dn_is_special(req->op.search.base)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = talloc(req, struct operational_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ac->module = module;
+ ac->req = req;
+ ac->scope = req->op.search.scope;
+ ac->attrs = req->op.search.attrs;
+
+ /* FIXME: We must copy the tree and keep the original
+ * unmodified. SSS */
+ /* replace any attributes in the parse tree that are
+ searchable, but are stored using a different name in the
+ backend */
+ for (i=0;i<ARRAY_SIZE(parse_tree_sub);i++) {
+ ldb_parse_tree_attr_replace(req->op.search.tree,
+ parse_tree_sub[i].attr,
+ parse_tree_sub[i].replace);
+ }
+
+ ac->controls_flags = talloc(ac, struct op_controls_flags);
+ /* remember if the SD_FLAGS_OID was set */
+ ac->controls_flags->sd = (ldb_request_get_control(req, LDB_CONTROL_SD_FLAGS_OID) != NULL);
+ /* remember if the LDB_CONTROL_BYPASS_OPERATIONAL_OID */
+ ac->controls_flags->bypassoperational =
+ (ldb_request_get_control(req, LDB_CONTROL_BYPASS_OPERATIONAL_OID) != NULL);
+
+ ac->attrs_to_replace = NULL;
+ ac->attrs_to_replace_size = 0;
+ /* in the list of attributes we are looking for, rename any
+ attributes to the alias for any hidden attributes that can
+ be fetched directly using non-hidden names.
+ Note that order here can affect performance, e.g. we should process
+ msDS-ResultantPSO before msDS-User-Account-Control-Computed (as the
+ latter is also dependent on the PSO information) */
+ for (a=0;ac->attrs && ac->attrs[a];a++) {
+ if (check_keep_control_for_attribute(ac->controls_flags, ac->attrs[a])) {
+ continue;
+ }
+ for (i=0;i<ARRAY_SIZE(search_sub);i++) {
+
+ if (ldb_attr_cmp(ac->attrs[a], search_sub[i].attr) != 0 ) {
+ continue;
+ }
+
+ ac->attrs_to_replace = talloc_realloc(ac,
+ ac->attrs_to_replace,
+ struct op_attributes_replace,
+ ac->attrs_to_replace_size + 1);
+
+ ac->attrs_to_replace[ac->attrs_to_replace_size] = search_sub[i];
+ ac->attrs_to_replace_size++;
+ if (!search_sub[i].replace) {
+ continue;
+ }
+
+ if (search_sub[i].extra_attrs && search_sub[i].extra_attrs[0]) {
+ unsigned int j;
+ const char **search_attrs2;
+ /* Only adds to the end of the list */
+ for (j = 0; search_sub[i].extra_attrs[j]; j++) {
+ search_attrs2 = ldb_attr_list_copy_add(req, search_attrs
+ ? search_attrs
+ : ac->attrs,
+ search_sub[i].extra_attrs[j]);
+ if (search_attrs2 == NULL) {
+ return ldb_operr(ldb);
+ }
+ /* may be NULL, talloc_free() doesn't mind */
+ talloc_free(search_attrs);
+ search_attrs = search_attrs2;
+ }
+ }
+
+ if (!search_attrs) {
+ search_attrs = ldb_attr_list_copy(req, ac->attrs);
+ if (search_attrs == NULL) {
+ return ldb_operr(ldb);
+ }
+ }
+ /* Despite the ldb_attr_list_copy_add, this is safe as that fn only adds to the end */
+ search_attrs[a] = search_sub[i].replace;
+ }
+ }
+ ac->list_operations = operation_get_op_list(ac, ac->attrs,
+ search_attrs == NULL?req->op.search.attrs:search_attrs,
+ ac->controls_flags);
+ ac->list_operations_size = 0;
+ i = 0;
+
+ while (ac->list_operations && ac->list_operations[i].attr != NULL) {
+ i++;
+ }
+ ac->list_operations_size = i;
+ ret = ldb_build_search_req_ex(&down_req, ldb, ac,
+ req->op.search.base,
+ req->op.search.scope,
+ req->op.search.tree,
+ /* use new set of attrs if any */
+ search_attrs == NULL?req->op.search.attrs:search_attrs,
+ req->controls,
+ ac, operational_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ /* perform the search */
+ return ldb_next_request(module, down_req);
+}
+
+static int operational_init(struct ldb_module *ctx)
+{
+ struct operational_data *data;
+ int ret;
+
+ ret = ldb_next_init(ctx);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ data = talloc_zero(ctx, struct operational_data);
+ if (!data) {
+ return ldb_module_oom(ctx);
+ }
+
+ ldb_module_set_private(ctx, data);
+
+ return LDB_SUCCESS;
+}
+
+static const struct ldb_module_ops ldb_operational_module_ops = {
+ .name = "operational",
+ .search = operational_search,
+ .init_context = operational_init
+};
+
+int ldb_operational_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_operational_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/paged_results.c b/source4/dsdb/samdb/ldb_modules/paged_results.c
new file mode 100644
index 0000000..2063e84
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/paged_results.c
@@ -0,0 +1,855 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2005-2008
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+
+ ** 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: paged_result
+ *
+ * Component: ldb paged results control module
+ *
+ * Description: this module caches a complete search and sends back
+ * results in chunks as asked by the client
+ *
+ * Author: Garming Sam and Aaron Haslett
+ *
+ * Note: Based on the original paged_results.c by Simo Sorce and
+ * vlv_pagination.c by Douglas Bagnall and Garming Sam.
+ */
+
+#include "includes.h"
+#include "auth/auth.h"
+#include <ldb.h>
+#include "dsdb/samdb/samdb.h"
+#include "libcli/security/security.h"
+#include "libcli/ldap/ldap_errors.h"
+#include "replace.h"
+#include "system/filesys.h"
+#include "system/time.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/samdb.h"
+
+#include "dsdb/common/util.h"
+#include "lib/util/dlinklist.h"
+
+/* Referrals are temporarily stored in a linked list */
+struct referral_store {
+ char *ref;
+ struct referral_store *next;
+};
+
+struct private_data;
+
+struct results_store {
+ struct results_store *prev, *next;
+
+ struct private_data *priv;
+
+ char *cookie;
+ time_t timestamp;
+
+ struct referral_store *first_ref;
+ struct referral_store *last_ref;
+
+ struct ldb_control **controls;
+
+ /* from VLV */
+ struct GUID *results;
+ size_t num_entries;
+ size_t result_array_size;
+
+ struct ldb_control **down_controls;
+ const char * const *attrs;
+
+ unsigned last_i;
+ struct ldb_parse_tree *expr;
+ char *expr_str;
+};
+
+struct private_data {
+ uint32_t next_free_id;
+ size_t num_stores;
+ struct results_store *store;
+};
+
+static int store_destructor(struct results_store *del)
+{
+ struct private_data *priv = del->priv;
+ DLIST_REMOVE(priv->store, del);
+
+ priv->num_stores -= 1;
+
+ return 0;
+}
+
+static struct results_store *new_store(struct private_data *priv)
+{
+ struct results_store *newr;
+ uint32_t new_id = priv->next_free_id++;
+
+ /* TODO: we should have a limit on the number of
+ * outstanding paged searches
+ */
+
+ newr = talloc_zero(priv, struct results_store);
+ if (!newr) return NULL;
+
+ newr->priv = priv;
+
+ newr->cookie = talloc_asprintf(newr, "%d", new_id);
+ if (!newr->cookie) {
+ talloc_free(newr);
+ return NULL;
+ }
+
+ newr->timestamp = time(NULL);
+
+ DLIST_ADD(priv->store, newr);
+
+ priv->num_stores += 1;
+
+ talloc_set_destructor(newr, store_destructor);
+
+ if (priv->num_stores > 10) {
+ struct results_store *last;
+ /*
+ * 10 is the default for MaxResultSetsPerConn --
+ * possibly need to parameterize it.
+ */
+ last = DLIST_TAIL(priv->store);
+ TALLOC_FREE(last);
+ }
+
+ return newr;
+}
+
+struct paged_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+
+ struct results_store *store;
+ int size;
+ struct ldb_control **controls;
+};
+
+static int send_referrals(struct results_store *store,
+ struct ldb_request *req)
+{
+ int ret;
+ struct referral_store *node;
+ while (store->first_ref != NULL) {
+ node = store->first_ref;
+ ret = ldb_module_send_referral(req, node->ref);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ store->first_ref = node->next;
+ talloc_free(node);
+ }
+ return LDB_SUCCESS;
+}
+
+/* Start an ldb request for a single object by GUID */
+static int paged_search_by_dn_guid(struct ldb_module *module,
+ struct paged_context *ac,
+ struct ldb_result **result,
+ const struct GUID *guid,
+ const char * const *attrs,
+ struct ldb_parse_tree *expr)
+{
+ struct ldb_dn *dn;
+ struct ldb_request *req;
+ struct ldb_result *res;
+ int ret;
+ struct GUID_txt_buf guid_str;
+
+ /* Use controls passed in on the downreq */
+ struct ldb_control **controls = ac->store->down_controls;
+
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ dn = ldb_dn_new_fmt(ac, ldb, "<GUID=%s>",
+ GUID_buf_string(guid, &guid_str));
+ if (dn == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ res = talloc_zero(ac, struct ldb_result);
+ if (res == NULL) {
+ TALLOC_FREE(dn);
+ return ldb_oom(ldb);
+ }
+
+ ret = ldb_build_search_req_ex(&req, ldb, ac,
+ dn,
+ LDB_SCOPE_BASE,
+ expr,
+ attrs,
+ controls,
+ res,
+ ldb_search_default_callback,
+ ac->req);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(dn);
+ TALLOC_FREE(res);
+ return ret;
+ }
+
+ /*
+ * Ensure the dn lasts only as long as the request,
+ * as we will have a lot of these (one per object
+ * being returned)
+ */
+
+ talloc_steal(req, dn);
+
+ ret = ldb_request(ldb, req);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ talloc_free(req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(res);
+ return ret;
+ }
+
+ *result = res;
+ return ret;
+}
+
+static int paged_results(struct paged_context *ac, struct ldb_reply *ares)
+{
+ struct ldb_extended *response = (ares != NULL ? ares->response : NULL);
+ struct ldb_paged_control *paged;
+ unsigned int i, num_ctrls;
+ int ret;
+
+ if (ac->store == NULL) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ return ldb_module_done(
+ ac->req, ac->controls, response, ret);
+ }
+
+ while (ac->store->last_i < ac->store->num_entries && ac->size > 0) {
+ struct GUID *guid = &ac->store->results[ac->store->last_i++];
+ struct ldb_result *result = NULL;
+
+ ac->size--;
+
+ /*
+ * Note: In the case that an object has been moved to a
+ * different place in the LDAP tree, we might expect the object
+ * to disappear from paged results. If we were going to
+ * implement that behaviour, we would do it here by passing
+ * down the original container DN to the search.
+ * However, testing shows that, on Windows, the moved object
+ * remains in the paged results. So, we are matching Windows
+ * behaviour here by leaving out the scope.
+ */
+ ret = paged_search_by_dn_guid(ac->module, ac, &result, guid,
+ ac->req->op.search.attrs,
+ ac->store->expr);
+ if (ret == LDAP_NO_SUCH_OBJECT ||
+ (ret == LDB_SUCCESS && result->count == 0)) {
+ /* The thing isn't there TODO, which we quietly
+ ignore and go on to send an extra one
+ instead. */
+ continue;
+ } else if (ret != LDB_SUCCESS) {
+ return ldb_module_done(
+ ac->req, ac->controls, response, ret);
+ }
+
+ ret = ldb_module_send_entry(ac->req, result->msgs[0],
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ /*
+ * ldb_module_send_entry will have called
+ * ldb_module_done if an error occurred.
+ */
+ return ret;
+ }
+ }
+
+ if (ac->store->first_ref) {
+ /* There is no right place to put references in the sorted
+ results, so we send them as soon as possible.
+ */
+ ret = send_referrals(ac->store, ac->req);
+ if (ret != LDB_SUCCESS) {
+ /*
+ * send_referrals will have called ldb_module_done
+ * if an error occurred.
+ */
+ return ret;
+ }
+ }
+
+ /* return result done */
+ num_ctrls = 1;
+ i = 0;
+
+ if (ac->store->controls != NULL) {
+ while (ac->store->controls[i]) i++; /* counting */
+
+ num_ctrls += i;
+ }
+
+ ac->controls = talloc_array(ac, struct ldb_control *, num_ctrls +1);
+ if (ac->controls == NULL) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ return ldb_module_done(
+ ac->req, ac->controls, response, ret);
+ }
+ ac->controls[num_ctrls] = NULL;
+
+ for (i = 0; i < (num_ctrls -1); i++) {
+ ac->controls[i] = talloc_reference(ac->controls,
+ ac->store->controls[i]);
+ }
+
+ ac->controls[i] = talloc(ac->controls, struct ldb_control);
+ if (ac->controls[i] == NULL) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ return ldb_module_done(
+ ac->req, ac->controls, response, ret);
+ }
+
+ ac->controls[i]->oid = talloc_strdup(ac->controls[i],
+ LDB_CONTROL_PAGED_RESULTS_OID);
+ if (ac->controls[i]->oid == NULL) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ return ldb_module_done(
+ ac->req, ac->controls, response, ret);
+ }
+
+ ac->controls[i]->critical = 0;
+
+ paged = talloc(ac->controls[i], struct ldb_paged_control);
+ if (paged == NULL) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ return ldb_module_done(
+ ac->req, ac->controls, response, ret);
+ }
+
+ ac->controls[i]->data = paged;
+
+ if (ac->size > 0) {
+ paged->size = 0;
+ paged->cookie = NULL;
+ paged->cookie_len = 0;
+ } else {
+ paged->size = ac->store->num_entries;
+ paged->cookie = talloc_strdup(paged, ac->store->cookie);
+ paged->cookie_len = strlen(paged->cookie) + 1;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int save_referral(struct results_store *store, char *ref)
+{
+ struct referral_store *node = talloc(store,
+ struct referral_store);
+ if (node == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ node->next = NULL;
+ node->ref = talloc_steal(node, ref);
+
+ if (store->first_ref == NULL) {
+ store->first_ref = node;
+ } else {
+ store->last_ref->next = node;
+ }
+ store->last_ref = node;
+ return LDB_SUCCESS;
+}
+
+static int paged_search_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct paged_context *ac;
+ struct results_store *store;
+ int ret;
+ const struct ldb_val *guid_blob;
+ struct GUID guid;
+ NTSTATUS status;
+
+ ac = talloc_get_type(req->context, struct paged_context);
+ store = ac->store;
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ if (store->results == NULL) {
+ store->num_entries = 0;
+ store->result_array_size = 16;
+ store->results = talloc_array(store, struct GUID,
+ store->result_array_size);
+ if (store->results == NULL) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ } else if (store->num_entries == store->result_array_size) {
+ if (store->result_array_size > INT_MAX/2) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ store->result_array_size *= 2;
+ store->results = talloc_realloc(store, store->results,
+ struct GUID,
+ store->result_array_size);
+ if (store->results == NULL) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+
+ guid_blob = ldb_dn_get_extended_component(ares->message->dn,
+ "GUID");
+ if (guid_blob == NULL) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ status = GUID_from_ndr_blob(guid_blob, &guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* Redundant paranoid check */
+ if (store->num_entries > store->result_array_size) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ store->results[store->num_entries] = guid;
+ store->num_entries++;
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ ret = save_referral(store, ares->referral);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ break;
+
+ case LDB_REPLY_DONE:
+ if (store->num_entries != 0) {
+ store->results = talloc_realloc(store, store->results,
+ struct GUID,
+ store->num_entries);
+ if (store->results == NULL) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+ store->result_array_size = store->num_entries;
+
+ ac->store->controls = talloc_move(ac->store, &ares->controls);
+ ret = paged_results(ac, ares);
+ if (ret != LDB_SUCCESS) {
+ /* paged_results will have called ldb_module_done
+ * if an error occurred
+ */
+ return ret;
+ }
+ return ldb_module_done(ac->req, ac->controls,
+ ares->response, ret);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static struct ldb_control **
+paged_results_copy_down_controls(TALLOC_CTX *mem_ctx,
+ struct ldb_control **controls)
+{
+
+ struct ldb_control **new_controls;
+ unsigned int i, j, num_ctrls;
+ if (controls == NULL) {
+ return NULL;
+ }
+
+ for (num_ctrls = 0; controls[num_ctrls]; num_ctrls++);
+
+ new_controls = talloc_array(mem_ctx, struct ldb_control *, num_ctrls);
+ if (new_controls == NULL) {
+ return NULL;
+ }
+
+ for (j = 0, i = 0; i < (num_ctrls); i++) {
+ struct ldb_control *control = controls[i];
+ if (control->oid == NULL) {
+ continue;
+ }
+ if (strcmp(control->oid, LDB_CONTROL_PAGED_RESULTS_OID) == 0) {
+ continue;
+ }
+ /*
+ * ASQ changes everything, do not copy it down for the
+ * per-GUID search
+ */
+ if (strcmp(control->oid, LDB_CONTROL_ASQ_OID) == 0) {
+ continue;
+ }
+ new_controls[j] = talloc_steal(new_controls, control);
+
+ /*
+ * Sadly the caller is not obliged to make this a
+ * proper talloc tree, so we do so here.
+ */
+ if (control->data) {
+ talloc_steal(control, control->data);
+ }
+ j++;
+ }
+ new_controls[j] = NULL;
+ return new_controls;
+}
+
+static const char * const *paged_copy_attrs(TALLOC_CTX *mem_ctx,
+ const char * const *attrs) {
+ int i;
+ const char **new_attrs;
+ if (attrs == NULL) {
+ return NULL;
+ }
+ new_attrs = ldb_attr_list_copy(mem_ctx, attrs);
+
+ for (i=0; attrs[i] != NULL; i++) {
+ new_attrs[i] = talloc_strdup(mem_ctx, attrs[i]);
+ }
+ new_attrs[i] = NULL;
+ return new_attrs;
+}
+
+/*
+ * Check if two sets of controls are the same except for the paged results
+ * control in the request controls. This function is messy because request
+ * control lists can contain controls that were NULL'd by the rootdse. We
+ * must ignore those entries. This function is not portable.
+ */
+static bool paged_controls_same(struct ldb_request *req,
+ struct ldb_control **down_controls) {
+ int i;
+ unsigned int num_down_controls, num_non_null_req_controls;
+ struct ldb_control *ctrl;
+
+ num_down_controls = 0;
+ for (i=0; down_controls[i] != NULL; i++) {
+ num_down_controls++;
+
+ ctrl = ldb_request_get_control(req, down_controls[i]->oid);
+ if (ctrl == NULL) {
+ return false;
+ }
+ }
+
+ num_non_null_req_controls = 0;
+ for (i=0; req->controls[i] != NULL; i++) {
+ if (req->controls[i]->oid != NULL &&
+ strcmp(req->controls[i]->oid,
+ LDB_CONTROL_ASQ_OID) != 0) {
+ num_non_null_req_controls++;
+ }
+ }
+
+ /* At this point we have the number of non-null entries for both
+ * control lists and we know that:
+ * 1. down_controls does not contain the paged control or ASQ
+ * (because paged_results_copy_down_controls excludes it)
+ * 2. req->controls does contain the paged control
+ * (because this function is only called if this is true)
+ * 3. down_controls is a subset of non-null controls in req->controls
+ * (checked above)
+ * So to confirm that the two lists are identical except for the paged
+ * control and possibly ASQ, all we need to check is: */
+ if (num_non_null_req_controls == num_down_controls + 1) {
+ return true;
+ }
+ return false;
+}
+
+static bool paged_attrs_same(const char * const *attrs_1,
+ const char * const *attrs_2) {
+ int i;
+ if (attrs_1 == NULL || attrs_2 == NULL) {
+ if (attrs_1 == NULL && attrs_2 == NULL) {
+ return true;
+ }
+ return false;
+ }
+
+ for (i=0; attrs_1[i] != NULL; i++) {
+ if (!ldb_attr_in_list(attrs_2, attrs_1[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static int paged_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct ldb_control *control;
+ struct ldb_control *vlv_control;
+ struct private_data *private_data;
+ struct ldb_paged_control *paged_ctrl;
+ struct ldb_request *search_req;
+ struct paged_context *ac;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ /* check if there's a paged request control */
+ control = ldb_request_get_control(req, LDB_CONTROL_PAGED_RESULTS_OID);
+ if (control == NULL) {
+ /* not found go on */
+ return ldb_next_request(module, req);
+ }
+
+ paged_ctrl = talloc_get_type(control->data, struct ldb_paged_control);
+ if (!paged_ctrl) {
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ private_data = talloc_get_type(ldb_module_get_private(module),
+ struct private_data);
+
+ vlv_control = ldb_request_get_control(req, LDB_CONTROL_VLV_REQ_OID);
+ if (vlv_control != NULL) {
+ /*
+ * VLV and paged_results are not allowed at the same
+ * time
+ */
+ return LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION;
+ }
+
+ ac = talloc_zero(req, struct paged_context);
+ if (ac == NULL) {
+ ldb_set_errstring(ldb, "Out of Memory");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->module = module;
+ ac->req = req;
+ ac->size = paged_ctrl->size;
+ if (ac->size < 0) {
+ /*
+ * Apparently some clients send more than 2^31. This
+ * violates the ldap standard, but we need to cope.
+ * In the future, if maximum result sizes are implemented in
+ * Samba, we should also clamp the page size to the maximum
+ * result size.
+ */
+ ac->size = 0x7FFFFFFF;
+ }
+
+ /* check if it is a continuation search the store */
+ if (paged_ctrl->cookie_len == 0) {
+ struct ldb_control *ext_ctrl;
+ struct ldb_control **controls;
+ static const char * const attrs[1] = { NULL };
+
+ if (paged_ctrl->size == 0) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->store = new_store(private_data);
+ if (ac->store == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ controls = req->controls;
+ ext_ctrl = ldb_request_get_control(req,
+ LDB_CONTROL_EXTENDED_DN_OID);
+ if (ext_ctrl == NULL) {
+ /*
+ * Add extended_dn control to the request if there
+ * isn't already one. We'll get the GUID out of it in
+ * the callback. This is a workaround for the case
+ * where ntsecuritydescriptor forbids fetching GUIDs
+ * for the current user.
+ */
+ struct ldb_request *req_extended_dn;
+ struct ldb_extended_dn_control *ext_ctrl_data;
+ req_extended_dn = talloc_zero(req, struct ldb_request);
+ req_extended_dn->controls = req->controls;
+ ext_ctrl_data = talloc_zero(req,
+ struct ldb_extended_dn_control);
+ ext_ctrl_data->type = 1;
+
+ ret = ldb_request_add_control(req_extended_dn,
+ LDB_CONTROL_EXTENDED_DN_OID,
+ true,
+ ext_ctrl_data);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ controls = req_extended_dn->controls;
+ }
+
+ ret = ldb_build_search_req_ex(&search_req, ldb, ac,
+ req->op.search.base,
+ req->op.search.scope,
+ req->op.search.tree,
+ attrs,
+ controls,
+ ac,
+ paged_search_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->store->expr = talloc_steal(ac->store, req->op.search.tree);
+ ac->store->expr_str = ldb_filter_from_tree(ac->store,
+ req->op.search.tree);
+ ac->store->attrs = paged_copy_attrs(ac->store,
+ req->op.search.attrs);
+
+ /* 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 (!ldb_save_controls(control, search_req, NULL)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ac->store->down_controls =
+ paged_results_copy_down_controls(ac->store, req->controls);
+ if (ac->store->down_controls == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return ldb_next_request(module, search_req);
+
+ } else {
+ struct results_store *current = NULL;
+ char *expr_str;
+ bool bool_ret;
+
+ /* TODO: age out old outstanding requests */
+ for (current = private_data->store; current != NULL;
+ current = current->next) {
+ if (strcmp(current->cookie, paged_ctrl->cookie) == 0) {
+ current->timestamp = time(NULL);
+ break;
+ }
+ }
+ if (current == NULL) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Get the expression string and make sure it didn't change */
+ expr_str = ldb_filter_from_tree(ac, req->op.search.tree);
+ if (expr_str == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = strcmp(current->expr_str, expr_str);
+ if (ret != 0) {
+ return LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION;
+ }
+
+ bool_ret = paged_controls_same(req, current->down_controls);
+ if (bool_ret == false) {
+ return LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION;
+ }
+
+ bool_ret = paged_attrs_same(req->op.search.attrs,
+ current->attrs);
+ if (bool_ret == false) {
+ return LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION;
+ }
+
+ DLIST_PROMOTE(private_data->store, current);
+
+ ac->store = current;
+
+ /* check if it is an abandon */
+ if (ac->size == 0) {
+ return ldb_module_done(req, NULL, NULL,
+ LDB_SUCCESS);
+ }
+
+ ret = paged_results(ac, NULL);
+ if (ret != LDB_SUCCESS) {
+ /*
+ * paged_results() will have called ldb_module_done
+ * if an error occurred
+ */
+ return ret;
+ }
+ return ldb_module_done(req, ac->controls, NULL, LDB_SUCCESS);
+ }
+}
+
+static int paged_request_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb;
+ struct private_data *data;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ data = talloc(module, struct private_data);
+ if (data == NULL) {
+ return LDB_ERR_OTHER;
+ }
+
+ data->next_free_id = 1;
+ data->num_stores = 0;
+ data->store = NULL;
+ ldb_module_set_private(module, data);
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_PAGED_RESULTS_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_WARNING,
+ "paged_results:"
+ "Unable to register control with rootdse!");
+ }
+
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_paged_results_module_ops = {
+ .name = "dsdb_paged_results",
+ .search = paged_search,
+ .init_context = paged_request_init
+};
+
+int ldb_dsdb_paged_results_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_paged_results_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/partition.c b/source4/dsdb/samdb/ldb_modules/partition.c
new file mode 100644
index 0000000..9ed0990
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/partition.c
@@ -0,0 +1,1721 @@
+/*
+ Partitions ldb module
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006
+ 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 partitions module
+ *
+ * Description: Implement LDAP partitions
+ *
+ * Author: Andrew Bartlett
+ * Author: Stefan Metzmacher
+ */
+
+#include "dsdb/samdb/ldb_modules/partition.h"
+
+struct part_request {
+ struct ldb_module *module;
+ struct ldb_request *req;
+};
+
+struct partition_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+
+ struct part_request *part_req;
+ unsigned int num_requests;
+ unsigned int finished_requests;
+
+ const char **referrals;
+};
+
+static struct partition_context *partition_init_ctx(struct ldb_module *module, struct ldb_request *req)
+{
+ struct partition_context *ac;
+
+ ac = talloc_zero(req, struct partition_context);
+ if (ac == NULL) {
+ ldb_set_errstring(ldb_module_get_ctx(module), "Out of Memory");
+ return NULL;
+ }
+
+ ac->module = module;
+ ac->req = req;
+
+ return ac;
+}
+
+/*
+ * helper functions to call the next module in chain
+ */
+int partition_request(struct ldb_module *module, struct ldb_request *request)
+{
+ if ((module && ldb_module_flags(ldb_module_get_ctx(module)) & LDB_FLG_ENABLE_TRACING)) { \
+ const struct dsdb_control_current_partition *partition = NULL;
+ struct ldb_control *partition_ctrl = ldb_request_get_control(request, DSDB_CONTROL_CURRENT_PARTITION_OID);
+ if (partition_ctrl) {
+ partition = talloc_get_type(partition_ctrl->data,
+ struct dsdb_control_current_partition);
+ }
+
+ if (partition != NULL) {
+ ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_TRACE, "partition_request() -> %s",
+ ldb_dn_get_linearized(partition->dn));
+ } else {
+ ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_TRACE, "partition_request() -> (metadata partition)");
+ }
+ }
+
+ return ldb_next_request(module, request);
+}
+
+static struct dsdb_partition *find_partition(struct partition_private_data *data,
+ struct ldb_dn *dn,
+ struct ldb_request *req)
+{
+ unsigned int i;
+ struct ldb_control *partition_ctrl;
+
+ /* see if the request has the partition DN specified in a
+ * control. The repl_meta_data module can specify this to
+ * ensure that replication happens to the right partition
+ */
+ partition_ctrl = ldb_request_get_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID);
+ if (partition_ctrl) {
+ const struct dsdb_control_current_partition *partition;
+ partition = talloc_get_type(partition_ctrl->data,
+ struct dsdb_control_current_partition);
+ if (partition != NULL) {
+ dn = partition->dn;
+ }
+ }
+
+ if (dn == NULL) {
+ return NULL;
+ }
+
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialisation) */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ if (ldb_dn_compare_base(data->partitions[i]->ctrl->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_req_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct partition_context *ac;
+ struct ldb_module *module;
+ struct ldb_request *nreq;
+ int ret;
+ struct ldb_control *partition_ctrl;
+
+ ac = talloc_get_type(req->context, struct partition_context);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ partition_ctrl = ldb_request_get_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID);
+ if (partition_ctrl && (ac->num_requests == 1 || ares->type == LDB_REPLY_ENTRY)) {
+ /* If we didn't fan this request out to mulitple partitions,
+ * or this is an individual search result, we can
+ * deterministically tell the caller what partition this was
+ * written to (repl_meta_data likes to know) */
+ ret = ldb_reply_add_control(ares,
+ DSDB_CONTROL_CURRENT_PARTITION_OID,
+ false, partition_ctrl->data);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ ret);
+ }
+ }
+
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(ac->req, ares->referral);
+
+ case LDB_REPLY_ENTRY:
+ if (ac->req->operation != LDB_SEARCH) {
+ ldb_set_errstring(ldb_module_get_ctx(ac->module),
+ "partition_req_callback:"
+ " Unsupported reply type for this request");
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ return ldb_module_send_entry(ac->req, ares->message, ares->controls);
+
+ case LDB_REPLY_DONE:
+ if (ac->req->operation == LDB_EXTENDED) {
+ /* FIXME: check for ares->response, replmd does not fill it ! */
+ if (ares->response) {
+ if (strcmp(ares->response->oid, LDB_EXTENDED_START_TLS_OID) != 0) {
+ ldb_set_errstring(ldb_module_get_ctx(ac->module),
+ "partition_req_callback:"
+ " Unknown extended reply, "
+ "only supports START_TLS");
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+ }
+
+ ac->finished_requests++;
+ if (ac->finished_requests == ac->num_requests) {
+ /* Send back referrals if they do exist (search ops) */
+ if (ac->referrals != NULL) {
+ const char **ref;
+ for (ref = ac->referrals; *ref != NULL; ++ref) {
+ ret = ldb_module_send_referral(ac->req,
+ talloc_strdup(ac->req, *ref));
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ ret);
+ }
+ }
+ }
+
+ /* this was the last one, call callback */
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response,
+ ares->error);
+ }
+
+ /* not the last, now call the next one */
+ module = ac->part_req[ac->finished_requests].module;
+ nreq = ac->part_req[ac->finished_requests].req;
+
+ ret = partition_request(module, nreq);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ break;
+ }
+
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+static int partition_prep_request(struct partition_context *ac,
+ struct dsdb_partition *partition)
+{
+ int ret;
+ struct ldb_request *req;
+ struct ldb_control *partition_ctrl = NULL;
+ void *part_data = NULL;
+
+ ac->part_req = talloc_realloc(ac, ac->part_req,
+ struct part_request,
+ ac->num_requests + 1);
+ if (ac->part_req == NULL) {
+ return ldb_oom(ldb_module_get_ctx(ac->module));
+ }
+
+ switch (ac->req->operation) {
+ case LDB_SEARCH:
+ ret = ldb_build_search_req_ex(&req, ldb_module_get_ctx(ac->module),
+ ac->part_req,
+ ac->req->op.search.base,
+ ac->req->op.search.scope,
+ ac->req->op.search.tree,
+ ac->req->op.search.attrs,
+ ac->req->controls,
+ ac, partition_req_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(req);
+ break;
+ case LDB_ADD:
+ ret = ldb_build_add_req(&req, ldb_module_get_ctx(ac->module), ac->part_req,
+ ac->req->op.add.message,
+ ac->req->controls,
+ ac, partition_req_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(req);
+ break;
+ case LDB_MODIFY:
+ ret = ldb_build_mod_req(&req, ldb_module_get_ctx(ac->module), ac->part_req,
+ ac->req->op.mod.message,
+ ac->req->controls,
+ ac, partition_req_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(req);
+ break;
+ case LDB_DELETE:
+ ret = ldb_build_del_req(&req, ldb_module_get_ctx(ac->module), ac->part_req,
+ ac->req->op.del.dn,
+ ac->req->controls,
+ ac, partition_req_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(req);
+ break;
+ case LDB_RENAME:
+ ret = ldb_build_rename_req(&req, ldb_module_get_ctx(ac->module), ac->part_req,
+ ac->req->op.rename.olddn,
+ ac->req->op.rename.newdn,
+ ac->req->controls,
+ ac, partition_req_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(req);
+ break;
+ case LDB_EXTENDED:
+ ret = ldb_build_extended_req(&req, ldb_module_get_ctx(ac->module),
+ ac->part_req,
+ ac->req->op.extended.oid,
+ ac->req->op.extended.data,
+ ac->req->controls,
+ ac, partition_req_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(req);
+ break;
+ default:
+ ldb_set_errstring(ldb_module_get_ctx(ac->module),
+ "Unsupported request type!");
+ ret = LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ac->part_req[ac->num_requests].req = req;
+
+ if (ac->req->controls) {
+ /* Duplicate everything beside the current partition control */
+ partition_ctrl = ldb_request_get_control(ac->req,
+ DSDB_CONTROL_CURRENT_PARTITION_OID);
+ if (!ldb_save_controls(partition_ctrl, req, NULL)) {
+ return ldb_module_oom(ac->module);
+ }
+ }
+
+ part_data = partition->ctrl;
+
+ ac->part_req[ac->num_requests].module = partition->module;
+
+ if (partition_ctrl != NULL) {
+ if (partition_ctrl->data != NULL) {
+ part_data = partition_ctrl->data;
+ }
+
+ /*
+ * If the provided current partition control is without
+ * data then use the calculated one.
+ */
+ ret = ldb_request_add_control(req,
+ DSDB_CONTROL_CURRENT_PARTITION_OID,
+ false, part_data);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (req->operation == LDB_SEARCH) {
+ /*
+ * If the search is for 'more' than this partition,
+ * then change the basedn, so the check of the BASE DN
+ * still passes in the ldb_key_value layer
+ */
+ if (ldb_dn_compare_base(partition->ctrl->dn,
+ req->op.search.base) != 0) {
+ req->op.search.base = partition->ctrl->dn;
+ }
+ }
+
+ ac->num_requests++;
+
+ return LDB_SUCCESS;
+}
+
+static int partition_call_first(struct partition_context *ac)
+{
+ return partition_request(ac->part_req[0].module, ac->part_req[0].req);
+}
+
+/**
+ * Send a request down to all the partitions (but not the sam.ldb file)
+ */
+static int partition_send_all(struct ldb_module *module,
+ struct partition_context *ac,
+ struct ldb_request *req)
+{
+ unsigned int i;
+ struct partition_private_data *data = talloc_get_type(ldb_module_get_private(module),
+ struct partition_private_data);
+ int ret;
+
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ ret = partition_prep_request(ac, data->partitions[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* fire the first one */
+ return partition_call_first(ac);
+}
+
+struct partition_copy_context {
+ struct ldb_module *module;
+ struct partition_context *partition_context;
+ struct ldb_request *request;
+ struct ldb_dn *dn;
+};
+
+/*
+ * A special DN has been updated in the primary partition. Now propagate those
+ * changes to the remaining partitions.
+ *
+ * Note: that the operations are asynchronous and this function is called
+ * from partition_copy_all_callback_handler in response to an async
+ * callback.
+ */
+static int partition_copy_all_callback_action(
+ struct ldb_module *module,
+ struct partition_context *ac,
+ struct ldb_request *req,
+ struct ldb_dn *dn)
+
+{
+
+ unsigned int i;
+ struct partition_private_data *data =
+ talloc_get_type(
+ ldb_module_get_private(module),
+ struct partition_private_data);
+ int search_ret;
+ struct ldb_result *res;
+ /* now fetch the resulting object, and then copy it to all the
+ * other partitions. We need this approach to cope with the
+ * partitions getting out of sync. If for example the
+ * @ATTRIBUTES object exists on one partition but not the
+ * others then just doing each of the partitions in turn will
+ * lead to an error
+ */
+ search_ret = dsdb_module_search_dn(module, ac, &res, dn, NULL, DSDB_FLAG_NEXT_MODULE, req);
+ if (search_ret != LDB_SUCCESS) {
+ return search_ret;
+ }
+
+ /* now delete the object in the other partitions, if requried
+ */
+ if (search_ret == LDB_ERR_NO_SUCH_OBJECT) {
+ for (i=0; data->partitions && data->partitions[i]; i++) {
+ int pret;
+ pret = dsdb_module_del(data->partitions[i]->module,
+ dn,
+ DSDB_FLAG_NEXT_MODULE,
+ req);
+ if (pret != LDB_SUCCESS && pret != LDB_ERR_NO_SUCH_OBJECT) {
+ /* we should only get success or no
+ such object from the other partitions */
+ return pret;
+ }
+ }
+
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
+ }
+
+ /* now add/modify in the other partitions */
+ for (i=0; data->partitions && data->partitions[i]; i++) {
+ struct ldb_message *modify_msg = NULL;
+ int pret;
+ unsigned int el_idx;
+
+ pret = dsdb_module_add(data->partitions[i]->module,
+ res->msgs[0],
+ DSDB_FLAG_NEXT_MODULE,
+ req);
+ if (pret == LDB_SUCCESS) {
+ continue;
+ }
+
+ if (pret != LDB_ERR_ENTRY_ALREADY_EXISTS) {
+ return pret;
+ }
+
+ modify_msg = ldb_msg_copy(req, res->msgs[0]);
+ if (modify_msg == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ /*
+ * mark all the message elements as
+ * LDB_FLAG_MOD_REPLACE
+ */
+ for (el_idx=0;
+ el_idx < modify_msg->num_elements;
+ el_idx++) {
+ modify_msg->elements[el_idx].flags
+ = LDB_FLAG_MOD_REPLACE;
+ }
+
+ if (req->operation == LDB_MODIFY) {
+ const struct ldb_message *req_msg = req->op.mod.message;
+ /*
+ * mark elements to be removed, if there were
+ * deleted entirely above we need to delete
+ * them here too
+ */
+ for (el_idx=0; el_idx < req_msg->num_elements; el_idx++) {
+ if (LDB_FLAG_MOD_TYPE(req_msg->elements[el_idx].flags) == LDB_FLAG_MOD_DELETE
+ || ((LDB_FLAG_MOD_TYPE(req_msg->elements[el_idx].flags) == LDB_FLAG_MOD_REPLACE) &&
+ req_msg->elements[el_idx].num_values == 0)) {
+ if (ldb_msg_find_element(modify_msg,
+ req_msg->elements[el_idx].name) != NULL) {
+ continue;
+ }
+ pret = ldb_msg_add_empty(
+ modify_msg,
+ req_msg->elements[el_idx].name,
+ LDB_FLAG_MOD_REPLACE,
+ NULL);
+ if (pret != LDB_SUCCESS) {
+ return pret;
+ }
+ }
+ }
+ }
+
+ pret = dsdb_module_modify(data->partitions[i]->module,
+ modify_msg,
+ DSDB_FLAG_NEXT_MODULE,
+ req);
+
+ if (pret != LDB_SUCCESS) {
+ return pret;
+ }
+ }
+
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
+}
+
+
+/*
+ * @brief call back function for the ldb operations on special DN's.
+ *
+ * As the LDB operations are async, and we wish to use the result
+ * the operations, a callback needs to be registered to process the results
+ * of the LDB operations.
+ *
+ * @param req the ldb request
+ * @param res the result of the operation
+ *
+ * @return the LDB_STATUS
+ */
+static int partition_copy_all_callback_handler(
+ struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct partition_copy_context *ac = NULL;
+
+ ac = talloc_get_type(
+ req->context,
+ struct partition_copy_context);
+
+ if (!ares) {
+ return ldb_module_done(
+ ac->request,
+ NULL,
+ NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* pass on to the callback */
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ return ldb_module_send_entry(
+ ac->request,
+ ares->message,
+ ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(
+ ac->request,
+ ares->referral);
+
+ case LDB_REPLY_DONE: {
+ int error = ares->error;
+ if (error == LDB_SUCCESS) {
+ error = partition_copy_all_callback_action(
+ ac->module,
+ ac->partition_context,
+ ac->request,
+ ac->dn);
+ }
+ return ldb_module_done(
+ ac->request,
+ ares->controls,
+ ares->response,
+ error);
+ }
+
+ default:
+ /* Can't happen */
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+}
+
+/**
+ * send an operation to the top partition, then copy the resulting
+ * object to all other partitions.
+ */
+static int partition_copy_all(
+ struct ldb_module *module,
+ struct partition_context *partition_context,
+ struct ldb_request *req,
+ struct ldb_dn *dn)
+{
+ struct ldb_request *new_req = NULL;
+ struct ldb_context *ldb = NULL;
+ struct partition_copy_context *context = NULL;
+
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ context = talloc_zero(req, struct partition_copy_context);
+ if (context == NULL) {
+ return ldb_oom(ldb);
+ }
+ context->module = module;
+ context->request = req;
+ context->dn = dn;
+ context->partition_context = partition_context;
+
+ switch (req->operation) {
+ case LDB_ADD:
+ ret = ldb_build_add_req(
+ &new_req,
+ ldb,
+ req,
+ req->op.add.message,
+ req->controls,
+ context,
+ partition_copy_all_callback_handler,
+ req);
+ break;
+ case LDB_MODIFY:
+ ret = ldb_build_mod_req(
+ &new_req,
+ ldb,
+ req,
+ req->op.mod.message,
+ req->controls,
+ context,
+ partition_copy_all_callback_handler,
+ req);
+ break;
+ case LDB_DELETE:
+ ret = ldb_build_del_req(
+ &new_req,
+ ldb,
+ req,
+ req->op.del.dn,
+ req->controls,
+ context,
+ partition_copy_all_callback_handler,
+ req);
+ break;
+ case LDB_RENAME:
+ ret = ldb_build_rename_req(
+ &new_req,
+ ldb,
+ req,
+ req->op.rename.olddn,
+ req->op.rename.newdn,
+ req->controls,
+ context,
+ partition_copy_all_callback_handler,
+ req);
+ break;
+ default:
+ /*
+ * Shouldn't happen.
+ */
+ ldb_debug(
+ ldb,
+ LDB_DEBUG_ERROR,
+ "Unexpected operation type (%d)\n", req->operation);
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ break;
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(module, new_req);
+}
+/**
+ * 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)
+{
+ struct partition_context *ac;
+ unsigned int i;
+ int ret;
+ struct dsdb_partition *partition;
+ struct partition_private_data *data = talloc_get_type(ldb_module_get_private(module),
+ struct partition_private_data);
+
+ /* if we aren't initialised yet go further */
+ if (!data || !data->partitions) {
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_dn_is_special(dn)) {
+ /* 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) {
+
+ ac = partition_init_ctx(module, req);
+ if (!ac) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ return partition_copy_all(module, ac, req, dn);
+ }
+ }
+ }
+
+ /* Otherwise, we need to find the partition to fire it to */
+
+ /* Find partition */
+ partition = find_partition(data, dn, req);
+ 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);
+ }
+
+ ac = partition_init_ctx(module, req);
+ if (!ac) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ /* we need to add a control but we never touch the original request */
+ ret = partition_prep_request(ac, partition);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* fire the first one */
+ return partition_call_first(ac);
+}
+
+/* search */
+static int partition_search(struct ldb_module *module, struct ldb_request *req)
+{
+ /* Find backend */
+ struct partition_private_data *data = talloc_get_type(ldb_module_get_private(module),
+ struct partition_private_data);
+ struct partition_context *ac;
+ struct ldb_context *ldb;
+ struct loadparm_context *lp_ctx;
+
+ 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_control *no_gc_control = ldb_request_get_control(req, DSDB_CONTROL_NO_GLOBAL_CATALOG);
+
+ struct ldb_search_options_control *search_options = NULL;
+ struct dsdb_partition *p;
+ unsigned int i, j;
+ int ret;
+ bool domain_scope = false, phantom_root = false;
+
+ p = find_partition(data, NULL, req);
+ if (p != NULL) {
+ /* the caller specified what partition they want the
+ * search - just pass it on
+ */
+ return ldb_next_request(p->module, req);
+ }
+
+ /* Get back the search options from the search control, and mark it as
+ * non-critical (to make backends and also dcpromo happy).
+ */
+ if (search_control) {
+ search_options = talloc_get_type(search_control->data, struct ldb_search_options_control);
+ search_control->critical = 0;
+
+ }
+
+ /* if we aren't initialised yet go further */
+ if (!data || !data->partitions) {
+ return ldb_next_request(module, req);
+ }
+
+ /* Special DNs without specified partition should go further */
+ if (ldb_dn_is_special(req->op.search.base)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* Locate the options */
+ domain_scope = (search_options
+ && (search_options->search_options & LDB_SEARCH_OPTION_DOMAIN_SCOPE))
+ || domain_scope_control;
+ phantom_root = search_options
+ && (search_options->search_options & LDB_SEARCH_OPTION_PHANTOM_ROOT);
+
+ /* Remove handled options from the search control flag */
+ if (search_options) {
+ search_options->search_options = search_options->search_options
+ & ~LDB_SEARCH_OPTION_DOMAIN_SCOPE
+ & ~LDB_SEARCH_OPTION_PHANTOM_ROOT;
+ }
+
+ ac = partition_init_ctx(module, req);
+ if (!ac) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ ldb = ldb_module_get_ctx(ac->module);
+ lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+
+ /* Search from the base DN */
+ if (ldb_dn_is_null(req->op.search.base)) {
+ if (!phantom_root) {
+ return ldb_error(ldb, LDB_ERR_NO_SUCH_OBJECT, "empty base DN");
+ }
+ return partition_send_all(module, ac, req);
+ }
+
+ for (i=0; data->partitions[i]; i++) {
+ bool match = false, stop = false;
+
+ if (data->partitions[i]->partial_replica && no_gc_control != NULL) {
+ if (ldb_dn_compare_base(data->partitions[i]->ctrl->dn,
+ req->op.search.base) == 0) {
+ /* base DN is in a partial replica
+ with the NO_GLOBAL_CATALOG
+ control. This partition is invisible */
+ /* DEBUG(0,("DENYING NON-GC OP: %s\n", ldb_module_call_chain(req, req))); */
+ continue;
+ }
+ }
+
+ if (phantom_root) {
+ /* Phantom root: Find all partitions under the
+ * search base. We match if:
+ *
+ * 1) the DN we are looking for exactly matches a
+ * certain partition and always stop
+ * 2) the DN we are looking for is a parent of certain
+ * partitions and it isn't a scope base search
+ * 3) the DN we are looking for is a child of a certain
+ * partition and always stop
+ * - we don't need to go any further up in the
+ * hierarchy!
+ */
+ if (ldb_dn_compare(data->partitions[i]->ctrl->dn,
+ req->op.search.base) == 0) {
+ match = true;
+ stop = true;
+ }
+ if (!match &&
+ (ldb_dn_compare_base(req->op.search.base,
+ data->partitions[i]->ctrl->dn) == 0 &&
+ req->op.search.scope != LDB_SCOPE_BASE)) {
+ match = true;
+ }
+ if (!match &&
+ ldb_dn_compare_base(data->partitions[i]->ctrl->dn,
+ req->op.search.base) == 0) {
+ match = true;
+ stop = true; /* note that this relies on partition ordering */
+ }
+ } else {
+ /* Domain scope: Find all partitions under the search
+ * base.
+ *
+ * We generate referral candidates if we haven't
+ * specified the domain scope control, haven't a base
+ * search* scope and the DN we are looking for is a real
+ * predecessor of certain partitions. When a new
+ * referral candidate is nearer to the DN than an
+ * existing one delete the latter (we want to have only
+ * the closest ones). When we checked this for all
+ * candidates we have the final referrals.
+ *
+ * We match if the DN we are looking for is a child of
+ * a certain partition or the partition
+ * DN itself - we don't need to go any further
+ * up in the hierarchy!
+ */
+ if ((!domain_scope) &&
+ (req->op.search.scope != LDB_SCOPE_BASE) &&
+ (ldb_dn_compare_base(req->op.search.base,
+ data->partitions[i]->ctrl->dn) == 0) &&
+ (ldb_dn_compare(req->op.search.base,
+ data->partitions[i]->ctrl->dn) != 0)) {
+ const char *scheme = ldb_get_opaque(
+ ldb, LDAP_REFERRAL_SCHEME_OPAQUE);
+ char *ref = talloc_asprintf(
+ ac,
+ "%s://%s/%s%s",
+ scheme == NULL ? "ldap" : scheme,
+ lpcfg_dnsdomain(lp_ctx),
+ ldb_dn_get_linearized(
+ data->partitions[i]->ctrl->dn),
+ req->op.search.scope ==
+ LDB_SCOPE_ONELEVEL ? "??base" : "");
+
+ if (ref == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /* Initialise the referrals list */
+ if (ac->referrals == NULL) {
+ char **l = str_list_make_empty(ac);
+ ac->referrals = discard_const_p(const char *, l);
+ if (ac->referrals == NULL) {
+ return ldb_oom(ldb);
+ }
+ }
+
+ /* Check if the new referral candidate is
+ * closer to the base DN than already
+ * saved ones and delete the latters */
+ j = 0;
+ while (ac->referrals[j] != NULL) {
+ if (strstr(ac->referrals[j],
+ ldb_dn_get_linearized(data->partitions[i]->ctrl->dn)) != NULL) {
+ str_list_remove(ac->referrals,
+ ac->referrals[j]);
+ } else {
+ ++j;
+ }
+ }
+
+ /* Add our new candidate */
+ ac->referrals = str_list_add(ac->referrals, ref);
+
+ talloc_free(ref);
+
+ if (ac->referrals == NULL) {
+ return ldb_oom(ldb);
+ }
+ }
+ if (ldb_dn_compare_base(data->partitions[i]->ctrl->dn, req->op.search.base) == 0) {
+ match = true;
+ stop = true; /* note that this relies on partition ordering */
+ }
+ }
+
+ if (match) {
+ ret = partition_prep_request(ac, data->partitions[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (stop) break;
+ }
+
+ /* Perhaps we didn't match any partitions. Try the main partition */
+ if (ac->num_requests == 0) {
+ talloc_free(ac);
+ return ldb_next_request(module, req);
+ }
+
+ /* fire the first one */
+ return partition_call_first(ac);
+}
+
+/* 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)
+{
+ /* Find backend */
+ struct dsdb_partition *backend, *backend2;
+
+ struct partition_private_data *data = talloc_get_type(ldb_module_get_private(module),
+ struct partition_private_data);
+
+ /* Skip the lot if 'data' isn't here yet (initialisation) */
+ if (!data) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ backend = find_partition(data, req->op.rename.olddn, req);
+ backend2 = find_partition(data, req->op.rename.newdn, req);
+
+ if ((backend && !backend2) || (!backend && backend2)) {
+ return LDB_ERR_AFFECTS_MULTIPLE_DSAS;
+ }
+
+ if (backend != backend2) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "Cannot rename from %s in %s to %s in %s: %s",
+ ldb_dn_get_linearized(req->op.rename.olddn),
+ ldb_dn_get_linearized(backend->ctrl->dn),
+ ldb_dn_get_linearized(req->op.rename.newdn),
+ ldb_dn_get_linearized(backend2->ctrl->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 */
+int partition_start_trans(struct ldb_module *module)
+{
+ int i = 0;
+ int ret = 0;
+ struct partition_private_data *data = talloc_get_type(ldb_module_get_private(module),
+ struct partition_private_data);
+ /* Look at base DN */
+ /* Figure out which partition it is under */
+ /* Skip the lot if 'data' isn't here yet (initialization) */
+ if (ldb_module_flags(ldb_module_get_ctx(module)) & LDB_FLG_ENABLE_TRACING) {
+ ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_TRACE, "partition_start_trans() -> (metadata partition)");
+ }
+
+ /*
+ * We start a transaction on metadata.tdb first and end it last in
+ * end_trans. This makes locking semantics follow TDB rather than MDB,
+ * and effectively locks all partitions at once.
+ * Detail:
+ * Samba AD is special in that the partitions module (this file)
+ * combines multiple independently locked databases into one overall
+ * transaction. Changes across multiple partition DBs in a single
+ * transaction must ALL be either visible or invisible.
+ * The way this is achieved is by taking out a write lock on
+ * metadata.tdb at the start of prepare_commit, while unlocking it at
+ * the end of end_trans. This is matched by read_lock, ensuring it
+ * can't progress until that write lock is released.
+ *
+ * metadata.tdb needs to be a TDB file because MDB uses independent
+ * locks, which means a read lock and a write lock can be held at the
+ * same time, whereas in TDB, the two locks block each other. The TDB
+ * behaviour is required to implement the functionality described
+ * above.
+ *
+ * An important additional detail here is that if prepare_commit is
+ * called on a TDB without any changes being made, no write lock is
+ * taken. We address this by storing a sequence number in metadata.tdb
+ * which is updated every time a replicated attribute is modified.
+ * The possibility of a few unreplicated attributes being out of date
+ * turns out not to be a problem.
+ * For this reason, a lock on sam.ldb (which is a TDB) won't achieve
+ * the same end as locking metadata.tdb, unless we made a modification
+ * to the @ records found there before every prepare_commit.
+ */
+ ret = partition_metadata_start_trans(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_next_start_trans(module);
+ if (ret != LDB_SUCCESS) {
+ partition_metadata_del_trans(module);
+ return ret;
+ }
+
+ ret = partition_reload_if_required(module, data, NULL);
+ if (ret != LDB_SUCCESS) {
+ ldb_next_del_trans(module);
+ partition_metadata_del_trans(module);
+ return ret;
+ }
+
+ /*
+ * The following per partition locks are required mostly because TDB
+ * and MDB require locks before read and write ops are permitted.
+ */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ if ((module && ldb_module_flags(ldb_module_get_ctx(module)) & LDB_FLG_ENABLE_TRACING)) {
+ ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_TRACE, "partition_start_trans() -> %s",
+ ldb_dn_get_linearized(data->partitions[i]->ctrl->dn));
+ }
+ ret = ldb_next_start_trans(data->partitions[i]->module);
+ if (ret != LDB_SUCCESS) {
+ /* Back it out, if it fails on one */
+ for (i--; i >= 0; i--) {
+ ldb_next_del_trans(data->partitions[i]->module);
+ }
+ ldb_next_del_trans(module);
+ partition_metadata_del_trans(module);
+ return ret;
+ }
+ }
+
+ data->in_transaction++;
+
+ return LDB_SUCCESS;
+}
+
+/* prepare for a commit */
+int partition_prepare_commit(struct ldb_module *module)
+{
+ unsigned int i;
+ struct partition_private_data *data = talloc_get_type(ldb_module_get_private(module),
+ struct partition_private_data);
+ int ret;
+
+ /*
+ * Order of prepare_commit calls must match that in
+ * partition_start_trans. See comment in that function for detail.
+ */
+ ret = partition_metadata_prepare_commit(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_next_prepare_commit(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ if ((module && ldb_module_flags(ldb_module_get_ctx(module)) & LDB_FLG_ENABLE_TRACING)) {
+ ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_TRACE, "partition_prepare_commit() -> %s",
+ ldb_dn_get_linearized(data->partitions[i]->ctrl->dn));
+ }
+ ret = ldb_next_prepare_commit(data->partitions[i]->module);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module), "prepare_commit error on %s: %s",
+ ldb_dn_get_linearized(data->partitions[i]->ctrl->dn),
+ ldb_errstring(ldb_module_get_ctx(module)));
+ return ret;
+ }
+ }
+
+ if ((module && ldb_module_flags(ldb_module_get_ctx(module)) & LDB_FLG_ENABLE_TRACING)) {
+ ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_TRACE, "partition_prepare_commit() -> (metadata partition)");
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+/* end a transaction */
+int partition_end_trans(struct ldb_module *module)
+{
+ int ret, ret2;
+ int i;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct partition_private_data *data = talloc_get_type(ldb_module_get_private(module),
+ struct partition_private_data);
+ bool trace = module && ldb_module_flags(ldb) & LDB_FLG_ENABLE_TRACING;
+
+ ret = LDB_SUCCESS;
+
+ if (data->in_transaction == 0) {
+ DEBUG(0,("partition end transaction mismatch\n"));
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ } else {
+ data->in_transaction--;
+ }
+
+ /*
+ * Order of end_trans calls must be the reverse of that in
+ * partition_start_trans. See comment in that function for detail.
+ */
+ if (data && data->partitions) {
+ /* Just counting the partitions */
+ for (i=0; data->partitions[i]; i++) {}
+
+ /* now walk them backwards */
+ for (i--; i>=0; i--) {
+ struct dsdb_partition *p = data->partitions[i];
+ if (trace) {
+ ldb_debug(ldb,
+ LDB_DEBUG_TRACE,
+ "partition_end_trans() -> %s",
+ ldb_dn_get_linearized(p->ctrl->dn));
+ }
+ ret2 = ldb_next_end_trans(p->module);
+ if (ret2 != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "end_trans error on %s: %s",
+ ldb_dn_get_linearized(p->ctrl->dn),
+ ldb_errstring(ldb));
+ ret = ret2;
+ }
+ }
+ }
+
+ if (trace) {
+ ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_TRACE, "partition_end_trans() -> (metadata partition)");
+ }
+ ret2 = ldb_next_end_trans(module);
+ if (ret2 != LDB_SUCCESS) {
+ ret = ret2;
+ }
+
+ ret2 = partition_metadata_end_trans(module);
+ if (ret2 != LDB_SUCCESS) {
+ ret = ret2;
+ }
+
+ return ret;
+}
+
+/* delete a transaction */
+int partition_del_trans(struct ldb_module *module)
+{
+ int ret, final_ret = LDB_SUCCESS;
+ int i;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct partition_private_data *data = talloc_get_type(ldb_module_get_private(module),
+ struct partition_private_data);
+ bool trace = module && ldb_module_flags(ldb) & LDB_FLG_ENABLE_TRACING;
+
+ if (data == NULL) {
+ DEBUG(0,("partition delete transaction with no private data\n"));
+ return ldb_operr(ldb);
+ }
+
+ /*
+ * Order of del_trans calls must be the reverse of that in
+ * partition_start_trans. See comment in that function for detail.
+ */
+ if (data->partitions) {
+ /* Just counting the partitions */
+ for (i=0; data->partitions[i]; i++) {}
+
+ /* now walk them backwards */
+ for (i--; i>=0; i--) {
+ struct dsdb_partition *p = data->partitions[i];
+ if (trace) {
+ ldb_debug(ldb,
+ LDB_DEBUG_TRACE,
+ "partition_del_trans() -> %s",
+ ldb_dn_get_linearized(p->ctrl->dn));
+ }
+ ret = ldb_next_del_trans(p->module);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "del_trans error on %s: %s",
+ ldb_dn_get_linearized(p->ctrl->dn),
+ ldb_errstring(ldb));
+ final_ret = ret;
+ }
+ }
+ }
+
+ if (trace) {
+ ldb_debug(ldb_module_get_ctx(module), LDB_DEBUG_TRACE, "partition_del_trans() -> (metadata partition)");
+ }
+ ret = ldb_next_del_trans(module);
+ if (ret != LDB_SUCCESS) {
+ final_ret = ret;
+ }
+
+ ret = partition_metadata_del_trans(module);
+ if (ret != LDB_SUCCESS) {
+ final_ret = ret;
+ }
+
+ if (data->in_transaction == 0) {
+ DEBUG(0,("partition del transaction mismatch\n"));
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+ data->in_transaction--;
+
+ return final_ret;
+}
+
+int partition_primary_sequence_number(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ uint64_t *seq_number,
+ struct ldb_request *parent)
+{
+ int ret;
+ struct ldb_result *res;
+ struct ldb_seqnum_request *tseq;
+ struct ldb_seqnum_result *seqr;
+
+ tseq = talloc_zero(mem_ctx, struct ldb_seqnum_request);
+ if (tseq == NULL) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+ tseq->type = LDB_SEQ_HIGHEST_SEQ;
+
+ ret = dsdb_module_extended(module, tseq, &res,
+ LDB_EXTENDED_SEQUENCE_NUMBER,
+ tseq,
+ DSDB_FLAG_NEXT_MODULE,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tseq);
+ return ret;
+ }
+
+ seqr = talloc_get_type_abort(res->extended->data,
+ struct ldb_seqnum_result);
+ if (seqr->flags & LDB_SEQ_TIMESTAMP_SEQUENCE) {
+ talloc_free(res);
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "Primary backend in partition module returned a timestamp based seq");
+ }
+
+ *seq_number = seqr->seq_num;
+ talloc_free(tseq);
+ return LDB_SUCCESS;
+}
+
+
+/*
+ * Older version of sequence number as sum of sequence numbers for each partition
+ */
+int partition_sequence_number_from_partitions(struct ldb_module *module,
+ uint64_t *seqr)
+{
+ int ret;
+ unsigned int i;
+ uint64_t seq_number = 0;
+ struct partition_private_data *data = talloc_get_type(ldb_module_get_private(module),
+ struct partition_private_data);
+
+ ret = partition_primary_sequence_number(module, data, &seq_number, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Skip the lot if 'data' isn't here yet (initialisation) */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ struct ldb_seqnum_request *tseq;
+ struct ldb_seqnum_result *tseqr;
+ struct ldb_request *treq;
+ struct ldb_result *res = talloc_zero(data, struct ldb_result);
+ if (res == NULL) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+ tseq = talloc_zero(res, struct ldb_seqnum_request);
+ if (tseq == NULL) {
+ talloc_free(res);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+ tseq->type = LDB_SEQ_HIGHEST_SEQ;
+
+ ret = ldb_build_extended_req(&treq, ldb_module_get_ctx(module), res,
+ LDB_EXTENDED_SEQUENCE_NUMBER,
+ tseq,
+ NULL,
+ res,
+ ldb_extended_default_callback,
+ NULL);
+ LDB_REQ_SET_LOCATION(treq);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(res);
+ return ret;
+ }
+
+ ret = partition_request(data->partitions[i]->module, treq);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(res);
+ return ret;
+ }
+ ret = ldb_wait(treq->handle, LDB_WAIT_ALL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(res);
+ return ret;
+ }
+ tseqr = talloc_get_type(res->extended->data,
+ struct ldb_seqnum_result);
+ seq_number += tseqr->seq_num;
+ talloc_free(res);
+ }
+
+ *seqr = seq_number;
+ return LDB_SUCCESS;
+}
+
+
+/*
+ * Newer version of sequence number using metadata tdb
+ */
+static int partition_sequence_number(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_extended *ext;
+ struct ldb_seqnum_request *seq;
+ struct ldb_seqnum_result *seqr;
+ uint64_t seq_number;
+ int ret;
+
+ seq = talloc_get_type_abort(req->op.extended.data, struct ldb_seqnum_request);
+ switch (seq->type) {
+ case LDB_SEQ_NEXT:
+ ret = partition_metadata_sequence_number_increment(module, &seq_number);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ break;
+
+ case LDB_SEQ_HIGHEST_SEQ:
+ ret = partition_metadata_sequence_number(module, &seq_number);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ break;
+
+ case LDB_SEQ_HIGHEST_TIMESTAMP:
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "LDB_SEQ_HIGHEST_TIMESTAMP not supported");
+ }
+
+ ext = talloc_zero(req, struct ldb_extended);
+ if (!ext) {
+ return ldb_module_oom(module);
+ }
+ seqr = talloc_zero(ext, struct ldb_seqnum_result);
+ if (seqr == NULL) {
+ talloc_free(ext);
+ return ldb_module_oom(module);
+ }
+ ext->oid = LDB_EXTENDED_SEQUENCE_NUMBER;
+ ext->data = seqr;
+
+ seqr->seq_num = seq_number;
+ seqr->flags |= LDB_SEQ_GLOBAL_SEQUENCE;
+
+ /* send request done */
+ return ldb_module_done(req, NULL, ext, LDB_SUCCESS);
+}
+
+/* lock all the backends */
+int partition_read_lock(struct ldb_module *module)
+{
+ int i = 0;
+ int ret = 0;
+ int ret2 = 0;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct partition_private_data *data = \
+ talloc_get_type(ldb_module_get_private(module),
+ struct partition_private_data);
+
+ if (ldb_module_flags(ldb) & LDB_FLG_ENABLE_TRACING) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "partition_read_lock() -> (metadata partition)");
+ }
+
+ /*
+ * It is important to only do this for LOCK because:
+ * - we don't want to unlock what we did not lock
+ *
+ * - we don't want to make a new lock on the sam.ldb
+ * (triggered inside this routine due to the seq num check)
+ * during an unlock phase as that will violate the lock
+ * ordering
+ */
+
+ if (data == NULL) {
+ TALLOC_CTX *mem_ctx = talloc_new(module);
+
+ data = talloc_zero(mem_ctx, struct partition_private_data);
+ if (data == NULL) {
+ talloc_free(mem_ctx);
+ return ldb_operr(ldb);
+ }
+
+ /*
+ * When used from Samba4, this message is set by the
+ * samba4 module, as a fixed value not read from the
+ * DB. This avoids listing modules in the DB
+ */
+ data->forced_module_msg = talloc_get_type(
+ ldb_get_opaque(ldb,
+ DSDB_OPAQUE_PARTITION_MODULE_MSG_OPAQUE_NAME),
+ struct ldb_message);
+
+ ldb_module_set_private(module, talloc_steal(module,
+ data));
+ talloc_free(mem_ctx);
+ }
+
+ /*
+ * This will lock sam.ldb and will also call event loops,
+ * so we do it before we get the whole db lock.
+ */
+ ret = partition_reload_if_required(module, data, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ * Order of read_lock calls must match that in partition_start_trans.
+ * See comment in that function for detail.
+ */
+ ret = partition_metadata_read_lock(module);
+ if (ret != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ /*
+ * The top level DB (sam.ldb) lock is not enough to block another
+ * process in prepare_commit(), because if nothing was changed in the
+ * specific backend, then prepare_commit() is a no-op. Therefore the
+ * metadata.tdb lock is taken out above, as it is the best we can do
+ * right now.
+ */
+ ret = ldb_next_read_lock(module);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb,
+ LDB_DEBUG_FATAL,
+ "Failed to lock db: %s / %s for metadata partition",
+ ldb_errstring(ldb),
+ ldb_strerror(ret));
+
+ return ret;
+ }
+
+ /*
+ * The following per partition locks are required mostly because TDB
+ * and MDB require locks before reads are permitted.
+ */
+ for (i=0; data && data->partitions && data->partitions[i]; i++) {
+ if ((module && ldb_module_flags(ldb) & LDB_FLG_ENABLE_TRACING)) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "partition_read_lock() -> %s",
+ ldb_dn_get_linearized(
+ data->partitions[i]->ctrl->dn));
+ }
+ ret = ldb_next_read_lock(data->partitions[i]->module);
+ if (ret == LDB_SUCCESS) {
+ continue;
+ }
+
+ ldb_debug_set(ldb,
+ LDB_DEBUG_FATAL,
+ "Failed to lock db: %s / %s for %s",
+ ldb_errstring(ldb),
+ ldb_strerror(ret),
+ ldb_dn_get_linearized(
+ data->partitions[i]->ctrl->dn));
+
+ goto failed;
+ }
+
+ return LDB_SUCCESS;
+
+failed:
+ /* Back it out, if it fails on one */
+ for (i--; i >= 0; i--) {
+ ret2 = ldb_next_read_unlock(data->partitions[i]->module);
+ if (ret2 != LDB_SUCCESS) {
+ ldb_debug(ldb,
+ LDB_DEBUG_FATAL,
+ "Failed to unlock db: %s / %s",
+ ldb_errstring(ldb),
+ ldb_strerror(ret2));
+ }
+ }
+ ret2 = ldb_next_read_unlock(module);
+ if (ret2 != LDB_SUCCESS) {
+ ldb_debug(ldb,
+ LDB_DEBUG_FATAL,
+ "Failed to unlock db: %s / %s",
+ ldb_errstring(ldb),
+ ldb_strerror(ret2));
+ }
+ return ret;
+}
+
+/* unlock all the backends */
+int partition_read_unlock(struct ldb_module *module)
+{
+ int i;
+ int ret = LDB_SUCCESS;
+ int ret2;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct partition_private_data *data = \
+ talloc_get_type(ldb_module_get_private(module),
+ struct partition_private_data);
+ bool trace = module && ldb_module_flags(ldb) & LDB_FLG_ENABLE_TRACING;
+
+ /*
+ * Order of read_unlock calls must be the reverse of that in
+ * partition_start_trans. See comment in that function for detail.
+ */
+ if (data && data->partitions) {
+ /* Just counting the partitions */
+ for (i=0; data->partitions[i]; i++) {}
+
+ /* now walk them backwards */
+ for (i--; i>=0; i--) {
+ struct dsdb_partition *p = data->partitions[i];
+ if (trace) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "partition_read_unlock() -> %s",
+ ldb_dn_get_linearized(p->ctrl->dn));
+ }
+ ret2 = ldb_next_read_unlock(p->module);
+ if (ret2 != LDB_SUCCESS) {
+ ldb_debug_set(ldb,
+ LDB_DEBUG_FATAL,
+ "Failed to lock db: %s / %s for %s",
+ ldb_errstring(ldb),
+ ldb_strerror(ret),
+ ldb_dn_get_linearized(p->ctrl->dn));
+
+ /*
+ * Don't overwrite the original failure code
+ * if there was one
+ */
+ if (ret == LDB_SUCCESS) {
+ ret = ret2;
+ }
+ }
+ }
+ }
+
+ if (trace) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "partition_read_unlock() -> (metadata partition)");
+ }
+
+ ret2 = ldb_next_read_unlock(module);
+ if (ret2 != LDB_SUCCESS) {
+ ldb_debug_set(ldb,
+ LDB_DEBUG_FATAL,
+ "Failed to unlock db: %s / %s for metadata partition",
+ ldb_errstring(ldb),
+ ldb_strerror(ret2));
+
+ /*
+ * Don't overwrite the original failure code
+ * if there was one
+ */
+ if (ret == LDB_SUCCESS) {
+ ret = ret2;
+ }
+ }
+
+ ret = partition_metadata_read_unlock(module);
+
+ /*
+ * Don't overwrite the original failure code
+ * if there was one
+ */
+ if (ret == LDB_SUCCESS) {
+ ret = ret2;
+ }
+
+ return ret;
+}
+
+/* extended */
+static int partition_extended(struct ldb_module *module, struct ldb_request *req)
+{
+ struct partition_private_data *data = talloc_get_type(ldb_module_get_private(module),
+ struct partition_private_data);
+ struct partition_context *ac;
+ int ret;
+
+ /* if we aren't initialised yet go further */
+ if (!data) {
+ return ldb_next_request(module, req);
+ }
+
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID) == 0) {
+ /* Update the metadata.tdb to increment the schema version if needed*/
+ DEBUG(10, ("Incrementing the sequence_number after schema_update_now\n"));
+ ret = partition_metadata_inc_schema_sequence(module);
+ return ldb_module_done(req, NULL, NULL, ret);
+ }
+
+ if (strcmp(req->op.extended.oid, LDB_EXTENDED_SEQUENCE_NUMBER) == 0) {
+ return partition_sequence_number(module, req);
+ }
+
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_CREATE_PARTITION_OID) == 0) {
+ return partition_create(module, req);
+ }
+
+ /*
+ * as the extended operation has no dn
+ * we need to send it to all partitions
+ */
+
+ ac = partition_init_ctx(module, req);
+ if (!ac) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ return partition_send_all(module, ac, req);
+}
+
+static 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,
+ .start_transaction = partition_start_trans,
+ .prepare_commit = partition_prepare_commit,
+ .end_transaction = partition_end_trans,
+ .del_transaction = partition_del_trans,
+ .read_lock = partition_read_lock,
+ .read_unlock = partition_read_unlock
+};
+
+int ldb_partition_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_partition_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/partition.h b/source4/dsdb/samdb/ldb_modules/partition.h
new file mode 100644
index 0000000..e6b5187
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/partition.h
@@ -0,0 +1,64 @@
+/*
+ Partitions ldb module
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006
+ 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 <ldb.h>
+#include <ldb_errors.h>
+#include <ldb_module.h>
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "system/locale.h"
+#include "param/param.h"
+
+struct dsdb_partition {
+ struct ldb_module *module;
+ struct dsdb_control_current_partition *ctrl;
+ const char *backend_url;
+ DATA_BLOB orig_record;
+ bool partial_replica; /* a GC partition */
+};
+
+struct partition_module {
+ const char **modules;
+ struct ldb_dn *dn;
+};
+
+struct partition_metadata {
+ struct tdb_wrap *db;
+ int in_transaction;
+ int read_lock_count;
+};
+
+struct partition_private_data {
+ struct dsdb_partition **partitions;
+ struct ldb_dn **replicate;
+ struct partition_metadata *metadata;
+
+ struct partition_module **modules;
+
+ uint64_t metadata_seq;
+ uint32_t in_transaction;
+
+ struct ldb_message *forced_module_msg;
+
+ const char *backend_db_store;
+};
+
+#include "dsdb/samdb/ldb_modules/partition_proto.h"
diff --git a/source4/dsdb/samdb/ldb_modules/partition_init.c b/source4/dsdb/samdb/ldb_modules/partition_init.c
new file mode 100644
index 0000000..484b5bf
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/partition_init.c
@@ -0,0 +1,885 @@
+/*
+ Partitions ldb module
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006
+ 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 partitions module
+ *
+ * Description: Implement LDAP partitions
+ *
+ * Author: Andrew Bartlett
+ * Author: Stefan Metzmacher
+ */
+
+#include "dsdb/samdb/ldb_modules/partition.h"
+#include "lib/util/tsort.h"
+#include "lib/ldb-samba/ldb_wrap.h"
+#include "system/filesys.h"
+
+static int partition_sort_compare(const void *v1, const void *v2)
+{
+ const struct dsdb_partition *p1;
+ const struct dsdb_partition *p2;
+
+ p1 = *((struct dsdb_partition * const*)v1);
+ p2 = *((struct dsdb_partition * const*)v2);
+
+ return ldb_dn_compare(p1->ctrl->dn, p2->ctrl->dn);
+}
+
+/* Load the list of DNs that we must replicate to all partitions */
+static int partition_load_replicate_dns(struct ldb_context *ldb,
+ struct partition_private_data *data,
+ struct ldb_message *msg)
+{
+ struct ldb_message_element *replicate_attributes = ldb_msg_find_element(msg, "replicateEntries");
+
+ talloc_free(data->replicate);
+ if (!replicate_attributes) {
+ data->replicate = NULL;
+ } else {
+ unsigned int i;
+ data->replicate = talloc_array(data, struct ldb_dn *, replicate_attributes->num_values + 1);
+ if (!data->replicate) {
+ return ldb_oom(ldb);
+ }
+
+ for (i=0; i < replicate_attributes->num_values; i++) {
+ data->replicate[i] = ldb_dn_from_ldb_val(data->replicate, ldb, &replicate_attributes->values[i]);
+ if (!ldb_dn_validate(data->replicate[i])) {
+ ldb_asprintf_errstring(ldb,
+ "partition_init: "
+ "invalid DN in partition replicate record: %s",
+ replicate_attributes->values[i].data);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+ data->replicate[i] = NULL;
+ }
+ return LDB_SUCCESS;
+}
+
+/* Load the list of modules for the partitions */
+static int partition_load_modules(struct ldb_context *ldb,
+ struct partition_private_data *data, struct ldb_message *msg)
+{
+ unsigned int i;
+ struct ldb_message_element *modules_attributes = ldb_msg_find_element(msg, "modules");
+ talloc_free(data->modules);
+ if (!modules_attributes) {
+ return LDB_SUCCESS;
+ }
+
+ data->modules = talloc_array(data, struct partition_module *, modules_attributes->num_values + 1);
+ if (!data->modules) {
+ return ldb_oom(ldb);
+ }
+
+ for (i=0; i < modules_attributes->num_values; i++) {
+ char *p;
+ DATA_BLOB dn_blob;
+ data->modules[i] = talloc(data->modules, struct partition_module);
+ if (!data->modules[i]) {
+ return ldb_oom(ldb);
+ }
+
+ dn_blob = modules_attributes->values[i];
+
+ p = strchr((const char *)dn_blob.data, ':');
+ if (!p) {
+ ldb_asprintf_errstring(ldb,
+ "partition_load_modules: "
+ "invalid form for partition module record (missing ':'): %s", (const char *)dn_blob.data);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ /* Now trim off the filename */
+ dn_blob.length = ((uint8_t *)p - dn_blob.data);
+
+ p++;
+ data->modules[i]->modules = ldb_modules_list_from_string(ldb, data->modules[i],
+ p);
+
+ if (dn_blob.length == 1 && dn_blob.data[0] == '*') {
+ data->modules[i]->dn = NULL;
+ } else {
+ data->modules[i]->dn = ldb_dn_from_ldb_val(data->modules[i], ldb, &dn_blob);
+ if (!data->modules[i]->dn || !ldb_dn_validate(data->modules[i]->dn)) {
+ return ldb_operr(ldb);
+ }
+ }
+ }
+ data->modules[i] = NULL;
+ return LDB_SUCCESS;
+}
+
+static int partition_reload_metadata(struct ldb_module *module, struct partition_private_data *data,
+ TALLOC_CTX *mem_ctx, struct ldb_message **_msg,
+ struct ldb_request *parent)
+{
+ int ret;
+ struct ldb_message *msg, *module_msg;
+ struct ldb_result *res;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const char *attrs[] = { "partition", "replicateEntries", "modules",
+ "partialReplica", "backendStore", NULL };
+ /* perform search for @PARTITION, looking for module, replicateEntries and ldapBackend */
+ ret = dsdb_module_search_dn(module, mem_ctx, &res,
+ ldb_dn_new(mem_ctx, ldb, DSDB_PARTITION_DN),
+ attrs,
+ DSDB_FLAG_NEXT_MODULE, parent);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ msg = res->msgs[0];
+
+ ret = partition_load_replicate_dns(ldb, data, msg);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* When used from Samba4, this message is set by the samba4
+ * module, as a fixed value not read from the DB. This avoids
+ * listing modules in the DB */
+ if (data->forced_module_msg) {
+ module_msg = data->forced_module_msg;
+ } else {
+ module_msg = msg;
+ }
+
+ ret = partition_load_modules(ldb, data, module_msg);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (_msg) {
+ *_msg = msg;
+ } else {
+ talloc_free(msg);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static const char **find_modules_for_dn(struct partition_private_data *data, struct ldb_dn *dn)
+{
+ unsigned int i;
+ struct partition_module *default_mod = NULL;
+ for (i=0; data->modules && data->modules[i]; i++) {
+ if (!data->modules[i]->dn) {
+ default_mod = data->modules[i];
+ } else if (ldb_dn_compare(dn, data->modules[i]->dn) == 0) {
+ return data->modules[i]->modules;
+ }
+ }
+ if (default_mod) {
+ return default_mod->modules;
+ } else {
+ return NULL;
+ }
+}
+
+static int new_partition_from_dn(struct ldb_context *ldb, struct partition_private_data *data,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn, const char *filename,
+ const char *backend_db_store,
+ struct dsdb_partition **partition) {
+ struct dsdb_control_current_partition *ctrl;
+ struct ldb_module *backend_module;
+ char *backend_path;
+ struct ldb_module *module_chain;
+ const char **modules;
+ const char **options = NULL;
+ int ret;
+
+ (*partition) = talloc_zero(mem_ctx, struct dsdb_partition);
+ if (!*partition) {
+ return ldb_oom(ldb);
+ }
+
+ (*partition)->ctrl = ctrl = talloc((*partition), struct dsdb_control_current_partition);
+ if (!ctrl) {
+ talloc_free(*partition);
+ return ldb_oom(ldb);
+ }
+
+ /* the backend LDB is the DN (base64 encoded if not 'plain') followed by .ldb */
+ backend_path = ldb_relative_path(ldb,
+ *partition,
+ filename);
+ if (!backend_path) {
+ ldb_asprintf_errstring(ldb,
+ "partition_init: unable to determine an relative path for partition: %s",
+ filename);
+ talloc_free(*partition);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ (*partition)->backend_url = talloc_asprintf(*partition, "%s://%s",
+ backend_db_store,
+ backend_path);
+
+ if (!(ldb_module_flags(ldb) & LDB_FLG_RDONLY)) {
+ char *p;
+ char *backend_dir;
+
+ p = strrchr(backend_path, '/');
+ if (p) {
+ p[0] = '\0';
+ }
+ backend_dir = backend_path;
+
+ /* Failure is quite reasonable, it might alredy exist */
+ mkdir(backend_dir, 0700);
+ }
+
+ ctrl->version = DSDB_CONTROL_CURRENT_PARTITION_VERSION;
+ ctrl->dn = talloc_steal(ctrl, dn);
+
+ options = ldb_options_get(ldb);
+ ret = ldb_module_connect_backend(
+ ldb, (*partition)->backend_url, options, &backend_module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_steal((*partition), backend_module);
+
+ modules = find_modules_for_dn(data, dn);
+
+ if (!modules) {
+ DEBUG(0, ("Unable to load partition modules for new DN %s, perhaps you need to reprovision? See partition-upgrade.txt for instructions\n", ldb_dn_get_linearized(dn)));
+ talloc_free(*partition);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ ret = ldb_module_load_list(ldb, modules, backend_module, &module_chain);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "partition_init: "
+ "loading backend for %s failed: %s",
+ ldb_dn_get_linearized(dn), ldb_errstring(ldb));
+ talloc_free(*partition);
+ return ret;
+ }
+ ret = ldb_module_init_chain(ldb, module_chain);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "partition_init: "
+ "initialising backend for %s failed: %s",
+ ldb_dn_get_linearized(dn), ldb_errstring(ldb));
+ talloc_free(*partition);
+ return ret;
+ }
+
+ /* This weirdness allows us to use ldb_next_request() in partition.c */
+ (*partition)->module = ldb_module_new(*partition, ldb, "partition_next", NULL);
+ if (!(*partition)->module) {
+ talloc_free(*partition);
+ return ldb_oom(ldb);
+ }
+ ldb_module_set_next((*partition)->module, talloc_steal((*partition)->module, module_chain));
+
+ /* if we were in a transaction then we need to start a
+ transaction on this new partition, otherwise we'll get a
+ transaction mismatch when we end the transaction */
+ if (data->in_transaction) {
+ if (ldb_module_flags(ldb) & LDB_FLG_ENABLE_TRACING) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "partition_start_trans() -> %s (new partition)",
+ ldb_dn_get_linearized((*partition)->ctrl->dn));
+ }
+ ret = ldb_next_start_trans((*partition)->module);
+ }
+
+ return ret;
+}
+
+/* Tell the rootDSE about the new partition */
+static int partition_register(struct ldb_context *ldb, struct dsdb_control_current_partition *ctrl)
+{
+ struct ldb_request *req;
+ int ret;
+
+ req = talloc_zero(NULL, struct ldb_request);
+ if (req == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ req->operation = LDB_REQ_REGISTER_PARTITION;
+ req->op.reg_partition.dn = ctrl->dn;
+ req->callback = ldb_op_default_callback;
+
+ ldb_set_timeout(ldb, req, 0);
+
+ req->handle = ldb_handle_new(req, ldb);
+ if (req->handle == NULL) {
+ talloc_free(req);
+ return ldb_operr(ldb);
+ }
+
+ ret = ldb_request(ldb, req);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR, "partition: Unable to register partition with rootdse!\n");
+ talloc_free(req);
+ return LDB_ERR_OTHER;
+ }
+ talloc_free(req);
+
+ return LDB_SUCCESS;
+}
+
+/* Add a newly found partition to the global data */
+static int add_partition_to_data(struct ldb_context *ldb, struct partition_private_data *data,
+ struct dsdb_partition *partition)
+{
+ unsigned int i;
+ int ret;
+
+ /* Count the partitions */
+ for (i=0; data->partitions && data->partitions[i]; i++) { /* noop */};
+
+ /* Add partition to list of partitions */
+ data->partitions = talloc_realloc(data, data->partitions, struct dsdb_partition *, i + 2);
+ if (!data->partitions) {
+ return ldb_oom(ldb);
+ }
+ data->partitions[i] = talloc_steal(data->partitions, partition);
+ data->partitions[i+1] = NULL;
+
+ /* Sort again (should use binary insert) */
+ TYPESAFE_QSORT(data->partitions, i+1, partition_sort_compare);
+
+ ret = partition_register(ldb, partition->ctrl);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return LDB_SUCCESS;
+}
+
+int partition_reload_if_required(struct ldb_module *module,
+ struct partition_private_data *data,
+ struct ldb_request *parent)
+{
+ uint64_t seq;
+ int ret;
+ unsigned int i;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_message *msg;
+ struct ldb_message_element *partition_attributes;
+ struct ldb_message_element *partial_replicas;
+ TALLOC_CTX *mem_ctx;
+
+ if (!data) {
+ /* Not initialised yet */
+ return LDB_SUCCESS;
+ }
+
+ mem_ctx = talloc_new(data);
+ if (!mem_ctx) {
+ return ldb_oom(ldb);
+ }
+
+ ret = partition_primary_sequence_number(module, mem_ctx, &seq, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ if (seq == data->metadata_seq) {
+ talloc_free(mem_ctx);
+ return LDB_SUCCESS;
+ }
+
+ /* This loads metadata tdb. If it's missing, creates it */
+ ret = partition_metadata_init(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = partition_reload_metadata(module, data, mem_ctx, &msg, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ data->metadata_seq = seq;
+
+ partition_attributes = ldb_msg_find_element(msg, "partition");
+ partial_replicas = ldb_msg_find_element(msg, "partialReplica");
+ data->backend_db_store
+ = talloc_strdup(data, ldb_msg_find_attr_as_string(msg, "backendStore", "tdb"));
+
+ if (data->backend_db_store == NULL) {
+ talloc_free(mem_ctx);
+ return ldb_module_oom(module);
+ }
+
+ for (i=0; partition_attributes && i < partition_attributes->num_values; i++) {
+ unsigned int j;
+ bool new_partition = true;
+ const char *filename = NULL;
+ DATA_BLOB dn_blob;
+ struct ldb_dn *dn;
+ struct dsdb_partition *partition;
+ struct ldb_result *dn_res;
+ const char *no_attrs[] = { NULL };
+
+ for (j=0; data->partitions && data->partitions[j]; j++) {
+ if (data_blob_cmp(&data->partitions[j]->orig_record, &partition_attributes->values[i]) == 0) {
+ new_partition = false;
+ break;
+ }
+ }
+ if (new_partition == false) {
+ continue;
+ }
+
+ dn_blob = partition_attributes->values[i];
+
+ if (dn_blob.length > 4 &&
+ (strncmp((const char *)&dn_blob.data[dn_blob.length-4], ".ldb", 4) == 0)) {
+
+ /* Look for DN:filename.ldb */
+ char *p = strrchr((const char *)dn_blob.data, ':');
+ if (!p) {
+ ldb_asprintf_errstring(ldb,
+ "partition_init: invalid DN in attempting to parse partition record: %s", (const char *)dn_blob.data);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ filename = p+1;
+
+ /* Now trim off the filename */
+ dn_blob.length = ((uint8_t *)p - dn_blob.data);
+ }
+
+ dn = ldb_dn_from_ldb_val(mem_ctx, ldb, &dn_blob);
+ if (!dn) {
+ ldb_asprintf_errstring(ldb,
+ "partition_init: invalid DN in partition record: %s", (const char *)dn_blob.data);
+ talloc_free(mem_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ /* Now do a slow check with the DN compare */
+ for (j=0; data->partitions && data->partitions[j]; j++) {
+ if (ldb_dn_compare(dn, data->partitions[j]->ctrl->dn) == 0) {
+ new_partition = false;
+ break;
+ }
+ }
+ if (new_partition == false) {
+ continue;
+ }
+
+ if (!filename) {
+ char *base64_dn = NULL;
+ const char *p;
+ for (p = ldb_dn_get_linearized(dn); *p; p++) {
+ /* We have such a strict check because I don't want shell metacharacters in the file name, nor ../ */
+ if (!(isalnum(*p) || *p == ' ' || *p == '=' || *p == ',')) {
+ break;
+ }
+ }
+ if (*p) {
+ base64_dn = ldb_base64_encode(data, ldb_dn_get_linearized(dn), strlen(ldb_dn_get_linearized(dn)));
+ filename = talloc_asprintf(mem_ctx, "%s.ldb", base64_dn);
+ } else {
+ filename = talloc_asprintf(mem_ctx, "%s.ldb", ldb_dn_get_linearized(dn));
+ }
+ }
+
+ /* We call ldb_dn_get_linearized() because the DN in
+ * partition_attributes is already casefolded
+ * correctly. We don't want to mess that up as the
+ * schema isn't loaded yet */
+ ret = new_partition_from_dn(ldb, data, data->partitions, dn,
+ filename, data->backend_db_store,
+ &partition);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ talloc_steal(partition, partition_attributes->values[i].data);
+ partition->orig_record = partition_attributes->values[i];
+
+ /* Get the 'correct' case of the partition DNs from the database */
+ ret = dsdb_module_search_dn(partition->module, data, &dn_res,
+ dn, no_attrs,
+ DSDB_FLAG_NEXT_MODULE, parent);
+ if (ret == LDB_SUCCESS) {
+ talloc_free(partition->ctrl->dn);
+ partition->ctrl->dn = talloc_steal(partition->ctrl, dn_res->msgs[0]->dn);
+ talloc_free(dn_res);
+ } else if (ret != LDB_ERR_NO_SUCH_OBJECT) {
+ ldb_asprintf_errstring(ldb,
+ "Failed to search for partition base %s in new partition at %s: %s",
+ ldb_dn_get_linearized(dn),
+ partition->backend_url,
+ ldb_errstring(ldb));
+ talloc_free(mem_ctx);
+ return ret;
+ }
+
+ /* see if it is a partial replica */
+ for (j=0; partial_replicas && j<partial_replicas->num_values; j++) {
+ struct ldb_dn *pa_dn = ldb_dn_from_ldb_val(mem_ctx, ldb, &partial_replicas->values[j]);
+ if (pa_dn != NULL && ldb_dn_compare(pa_dn, partition->ctrl->dn) == 0) {
+ partition->partial_replica = true;
+ }
+ talloc_free(pa_dn);
+ }
+
+ ret = add_partition_to_data(ldb, data, partition);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+
+ talloc_free(mem_ctx);
+ return LDB_SUCCESS;
+}
+
+/* Copy the metadata (@OPTIONS etc) for the new partition into the partition */
+
+static int new_partition_set_replicated_metadata(struct ldb_context *ldb,
+ struct ldb_module *module, struct ldb_request *last_req,
+ struct partition_private_data *data,
+ struct dsdb_partition *partition)
+{
+ unsigned int i;
+ int ret;
+ /* for each replicate, copy from main partition. If we get an error, we report it up the chain */
+ for (i=0; data->replicate && data->replicate[i]; i++) {
+ struct ldb_result *replicate_res;
+ struct ldb_request *add_req;
+ ret = dsdb_module_search_dn(module, last_req, &replicate_res,
+ data->replicate[i],
+ NULL,
+ DSDB_FLAG_NEXT_MODULE, NULL);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ continue;
+ }
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "Failed to search for %s from " DSDB_PARTITION_DN
+ " replicateEntries for new partition at %s on %s: %s",
+ ldb_dn_get_linearized(data->replicate[i]),
+ partition->backend_url,
+ ldb_dn_get_linearized(partition->ctrl->dn),
+ ldb_errstring(ldb));
+ return ret;
+ }
+
+ /* Build add request */
+ ret = ldb_build_add_req(&add_req, ldb, replicate_res,
+ replicate_res->msgs[0], NULL, NULL,
+ ldb_op_default_callback, last_req);
+ LDB_REQ_SET_LOCATION(add_req);
+ last_req = add_req;
+ if (ret != LDB_SUCCESS) {
+ /* return directly, this is a very unlikely error */
+ return ret;
+ }
+ /* do request */
+ ret = ldb_next_request(partition->module, add_req);
+ /* wait */
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(add_req->handle, LDB_WAIT_ALL);
+ }
+
+ switch (ret) {
+ case LDB_SUCCESS:
+ break;
+
+ case LDB_ERR_ENTRY_ALREADY_EXISTS:
+ /* Handle this case specially - if the
+ * metadata already exists, replace it */
+ {
+ struct ldb_request *del_req;
+
+ /* Don't leave a confusing string in the ldb_errstring() */
+ ldb_reset_err_string(ldb);
+ /* Build del request */
+ ret = ldb_build_del_req(&del_req, ldb, replicate_res, replicate_res->msgs[0]->dn, NULL, NULL,
+ ldb_op_default_callback, last_req);
+ LDB_REQ_SET_LOCATION(del_req);
+ last_req = del_req;
+ if (ret != LDB_SUCCESS) {
+ /* return directly, this is a very unlikely error */
+ return ret;
+ }
+ /* do request */
+ ret = ldb_next_request(partition->module, del_req);
+
+ /* wait */
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(del_req->handle, LDB_WAIT_ALL);
+ }
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "Failed to delete (for re-add) %s from " DSDB_PARTITION_DN
+ " replicateEntries in new partition at %s on %s: %s",
+ ldb_dn_get_linearized(data->replicate[i]),
+ partition->backend_url,
+ ldb_dn_get_linearized(partition->ctrl->dn),
+ ldb_errstring(ldb));
+ return ret;
+ }
+
+ /* Build add request */
+ ret = ldb_build_add_req(&add_req, ldb, replicate_res, replicate_res->msgs[0], NULL, NULL,
+ ldb_op_default_callback, last_req);
+ LDB_REQ_SET_LOCATION(add_req);
+ last_req = add_req;
+ if (ret != LDB_SUCCESS) {
+ /* return directly, this is a very unlikely error */
+ return ret;
+ }
+
+ /* do the add again */
+ ret = ldb_next_request(partition->module, add_req);
+
+ /* wait */
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(add_req->handle, LDB_WAIT_ALL);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "Failed to add (after delete) %s from " DSDB_PARTITION_DN
+ " replicateEntries to new partition at %s on %s: %s",
+ ldb_dn_get_linearized(data->replicate[i]),
+ partition->backend_url,
+ ldb_dn_get_linearized(partition->ctrl->dn),
+ ldb_errstring(ldb));
+ return ret;
+ }
+ break;
+ }
+ default:
+ {
+ ldb_asprintf_errstring(ldb,
+ "Failed to add %s from " DSDB_PARTITION_DN
+ " replicateEntries to new partition at %s on %s: %s",
+ ldb_dn_get_linearized(data->replicate[i]),
+ partition->backend_url,
+ ldb_dn_get_linearized(partition->ctrl->dn),
+ ldb_errstring(ldb));
+ return ret;
+ }
+ }
+
+ /* And around again, for the next thing we must merge */
+ }
+ return LDB_SUCCESS;
+}
+
+/* Extended operation to create a new partition, called when
+ * 'new_partition' detects that one is being added based on it's
+ * instanceType */
+int partition_create(struct ldb_module *module, struct ldb_request *req)
+{
+ unsigned int i;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_request *mod_req, *last_req = req;
+ struct ldb_message *mod_msg;
+ struct partition_private_data *data;
+ struct dsdb_partition *partition = NULL;
+ const char *casefold_dn;
+ bool new_partition = false;
+
+ /* Check if this is already a partition */
+
+ struct dsdb_create_partition_exop *ex_op = talloc_get_type(req->op.extended.data, struct dsdb_create_partition_exop);
+ struct ldb_dn *dn = ex_op->new_dn;
+
+ data = talloc_get_type(ldb_module_get_private(module), struct partition_private_data);
+ if (!data) {
+ /* We are not going to create a partition before we are even set up */
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* see if we are still up-to-date */
+ ret = partition_reload_if_required(module, data, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ for (i=0; data->partitions && data->partitions[i]; i++) {
+ if (ldb_dn_compare(data->partitions[i]->ctrl->dn, dn) == 0) {
+ partition = data->partitions[i];
+ }
+ }
+
+ if (!partition) {
+ char *filename;
+ char *partition_record;
+ new_partition = true;
+ mod_msg = ldb_msg_new(req);
+ if (!mod_msg) {
+ return ldb_oom(ldb);
+ }
+
+ mod_msg->dn = ldb_dn_new(mod_msg, ldb, DSDB_PARTITION_DN);
+
+ casefold_dn = ldb_dn_get_casefold(dn);
+
+ {
+ char *escaped;
+ const char *p, *sam_name;
+ sam_name = strrchr((const char *)ldb_get_opaque(ldb, "ldb_url"), '/');
+ if (!sam_name) {
+ return ldb_operr(ldb);
+ }
+ sam_name++;
+
+ for (p = casefold_dn; *p; p++) {
+ /* We have such a strict check because
+ * I don't want shell metacharacters
+ * in the file name, nor ../, but I do
+ * want it to be easily typed if SAFE
+ * to do so */
+ if (!(isalnum(*p) || *p == ' ' || *p == '=' || *p == ',')) {
+ break;
+ }
+ }
+ if (*p) {
+ escaped = rfc1738_escape_part(mod_msg, casefold_dn);
+ if (!escaped) {
+ return ldb_oom(ldb);
+ }
+ filename = talloc_asprintf(mod_msg, "%s.d/%s.ldb", sam_name, escaped);
+ talloc_free(escaped);
+ } else {
+ filename = talloc_asprintf(mod_msg, "%s.d/%s.ldb", sam_name, casefold_dn);
+ }
+
+ if (!filename) {
+ return ldb_oom(ldb);
+ }
+ }
+ partition_record = talloc_asprintf(mod_msg, "%s:%s", casefold_dn, filename);
+
+ ret = ldb_msg_append_steal_string(mod_msg, DSDB_PARTITION_ATTR, partition_record,
+ LDB_FLAG_MOD_ADD);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (ldb_request_get_control(req, DSDB_CONTROL_PARTIAL_REPLICA)) {
+ /* this new partition is a partial replica */
+ ret = ldb_msg_append_fmt(mod_msg, LDB_FLAG_MOD_ADD,
+ "partialReplica", "%s", ldb_dn_get_linearized(dn));
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* Perform modify on @PARTITION record */
+ ret = ldb_build_mod_req(&mod_req, ldb, req, mod_msg, NULL, NULL,
+ ldb_op_default_callback, req);
+ LDB_REQ_SET_LOCATION(mod_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ last_req = mod_req;
+
+ ret = ldb_next_request(module, mod_req);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(mod_req->handle, LDB_WAIT_ALL);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Make a partition structure for this new partition, so we can copy in the template structure */
+ ret = new_partition_from_dn(ldb, data, req, ldb_dn_copy(req, dn),
+ filename, data->backend_db_store,
+ &partition);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_steal(partition, partition_record);
+ partition->orig_record = data_blob_string_const(partition_record);
+ }
+
+ ret = new_partition_set_replicated_metadata(ldb, module, last_req, data, partition);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (new_partition) {
+ ret = add_partition_to_data(ldb, data, partition);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* send request done */
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
+}
+
+
+int partition_init(struct ldb_module *module)
+{
+ int ret;
+ TALLOC_CTX *mem_ctx = talloc_new(module);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct partition_private_data *data;
+
+ if (!mem_ctx) {
+ return ldb_operr(ldb);
+ }
+
+ /* We actually got this during the read_lock call */
+ data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+
+ /* This loads the partitions */
+ ret = partition_reload_if_required(module, data, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ldb_module_set_private(module, talloc_steal(module, data));
+ talloc_free(mem_ctx);
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_DOMAIN_SCOPE_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "partition: Unable to register control with rootdse!\n");
+ return ldb_operr(ldb);
+ }
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_SEARCH_OPTIONS_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "partition: Unable to register control with rootdse!\n");
+ return ldb_operr(ldb);
+ }
+
+ return ldb_next_init(module);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/partition_metadata.c b/source4/dsdb/samdb/ldb_modules/partition_metadata.c
new file mode 100644
index 0000000..7763e53
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/partition_metadata.c
@@ -0,0 +1,605 @@
+/*
+ Partitions ldb module - management of metadata.tdb for sequence number
+
+ Copyright (C) Amitay Isaacs <amitay@samba.org> 2011
+
+ 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 "dsdb/samdb/ldb_modules/partition.h"
+#include "lib/ldb-samba/ldb_wrap.h"
+#include "system/filesys.h"
+#include "lib/util/smb_strtox.h"
+
+#define LDB_METADATA_SEQ_NUM "SEQ_NUM"
+
+
+/*
+ * Read a key with uint64 value
+ */
+static int partition_metadata_get_uint64(struct ldb_module *module,
+ const char *key, uint64_t *value,
+ uint64_t default_value)
+{
+ struct partition_private_data *data;
+ struct tdb_context *tdb;
+ TDB_DATA tdb_key, tdb_data;
+ char *value_str;
+ TALLOC_CTX *tmp_ctx;
+ int error = 0;
+
+ data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+
+ if (!data || !data->metadata || !data->metadata->db) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: metadata tdb not initialized");
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ tdb = data->metadata->db->tdb;
+
+ tdb_key.dptr = (uint8_t *)discard_const_p(char, key);
+ tdb_key.dsize = strlen(key);
+
+ tdb_data = tdb_fetch(tdb, tdb_key);
+ if (!tdb_data.dptr) {
+ if (tdb_error(tdb) == TDB_ERR_NOEXIST) {
+ *value = default_value;
+ return LDB_SUCCESS;
+ } else {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ tdb_errorstr(tdb));
+ }
+ }
+
+ value_str = talloc_strndup(tmp_ctx, (char *)tdb_data.dptr, tdb_data.dsize);
+ if (value_str == NULL) {
+ SAFE_FREE(tdb_data.dptr);
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+
+ *value = smb_strtoull(value_str, NULL, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: converision failed");
+ }
+
+ SAFE_FREE(tdb_data.dptr);
+ talloc_free(tmp_ctx);
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ * Write a key with uin64 value
+ */
+static int partition_metadata_set_uint64(struct ldb_module *module,
+ const char *key, uint64_t value,
+ bool insert)
+{
+ struct partition_private_data *data;
+ struct tdb_context *tdb;
+ TDB_DATA tdb_key, tdb_data;
+ int tdb_flag;
+ char *value_str;
+ TALLOC_CTX *tmp_ctx;
+
+ data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+
+ if (!data || !data->metadata || !data->metadata->db) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: metadata tdb not initialized");
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ tdb = data->metadata->db->tdb;
+
+ value_str = talloc_asprintf(tmp_ctx, "%llu", (unsigned long long)value);
+ if (value_str == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+
+ tdb_key.dptr = (uint8_t *)discard_const_p(char, key);
+ tdb_key.dsize = strlen(key);
+
+ tdb_data.dptr = (uint8_t *)value_str;
+ tdb_data.dsize = strlen(value_str);
+
+ if (insert) {
+ tdb_flag = TDB_INSERT;
+ } else {
+ tdb_flag = TDB_MODIFY;
+ }
+
+ if (tdb_store(tdb, tdb_key, tdb_data, tdb_flag) != 0) {
+ int ret;
+ char *error_string = talloc_asprintf(tmp_ctx, "%s: tdb_store of key %s failed: %s",
+ tdb_name(tdb), key, tdb_errorstr(tdb));
+ ret = ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ error_string);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ talloc_free(tmp_ctx);
+
+ return LDB_SUCCESS;
+}
+
+int partition_metadata_inc_schema_sequence(struct ldb_module *module)
+{
+ struct partition_private_data *data;
+ int ret;
+ uint64_t value = 0;
+
+ data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+ if (!data || !data->metadata) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: metadata not initialized");
+ }
+
+ if (data->metadata->in_transaction == 0) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: increment sequence number without transaction");
+ }
+ ret = partition_metadata_get_uint64(module, DSDB_METADATA_SCHEMA_SEQ_NUM, &value, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ value++;
+ ret = partition_metadata_set_uint64(module, DSDB_METADATA_SCHEMA_SEQ_NUM, value, false);
+ if (ret == LDB_ERR_OPERATIONS_ERROR) {
+ /* Modify failed, let's try the add */
+ ret = partition_metadata_set_uint64(module, DSDB_METADATA_SCHEMA_SEQ_NUM, value, true);
+ }
+ return ret;
+}
+
+
+
+/*
+ * Open sam.ldb.d/metadata.tdb.
+ */
+static int partition_metadata_open(struct ldb_module *module, bool create)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx;
+ struct partition_private_data *data;
+ struct loadparm_context *lp_ctx;
+ char *filename, *dirname;
+ int open_flags, tdb_flags, ldb_flags;
+ struct stat statbuf;
+
+ data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+ if (!data || !data->metadata) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: metadata not initialized");
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ filename = ldb_relative_path(ldb,
+ tmp_ctx,
+ "sam.ldb.d/metadata.tdb");
+
+ if (!filename) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ open_flags = O_RDWR;
+ if (create) {
+ open_flags |= O_CREAT;
+
+ /* While provisioning, sam.ldb.d directory may not exist,
+ * so create it. Ignore errors, if it already exists. */
+ dirname = ldb_relative_path(ldb,
+ tmp_ctx,
+ "sam.ldb.d");
+ if (!dirname) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ mkdir(dirname, 0700);
+ talloc_free(dirname);
+ } else {
+ if (stat(filename, &statbuf) != 0) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ lp_ctx = talloc_get_type_abort(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+
+ tdb_flags = lpcfg_tdb_flags(lp_ctx, TDB_DEFAULT|TDB_SEQNUM);
+
+ ldb_flags = ldb_module_flags(ldb);
+
+ if (ldb_flags & LDB_FLG_NOSYNC) {
+ tdb_flags |= TDB_NOSYNC;
+ }
+
+ data->metadata->db = tdb_wrap_open(
+ data->metadata, filename, 10,
+ tdb_flags, open_flags, 0660);
+ if (data->metadata->db == NULL) {
+ talloc_free(tmp_ctx);
+ if (create) {
+ ldb_asprintf_errstring(ldb, "partition_metadata: Unable to create %s: %s",
+ filename, strerror(errno));
+ } else {
+ ldb_asprintf_errstring(ldb, "partition_metadata: Unable to open %s: %s",
+ filename, strerror(errno));
+ }
+ if (errno == EACCES || errno == EPERM) {
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+
+/*
+ * Set the sequence number calculated from older logic (sum of primary sequence
+ * numbers for each partition) as LDB_METADATA_SEQ_NUM key.
+ */
+static int partition_metadata_set_sequence_number(struct ldb_module *module)
+{
+ int ret;
+ uint64_t seq_number;
+
+ ret = partition_sequence_number_from_partitions(module, &seq_number);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return partition_metadata_set_uint64(module, LDB_METADATA_SEQ_NUM, seq_number, true);
+}
+
+
+/*
+ * Initialize metadata. Load metadata.tdb.
+ * If missing, create it and fill in sequence number
+ */
+int partition_metadata_init(struct ldb_module *module)
+{
+ struct partition_private_data *data;
+ int ret;
+
+ data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+
+ if (data->metadata != NULL && data->metadata->db != NULL) {
+ return LDB_SUCCESS;
+ }
+
+ data->metadata = talloc_zero(data, struct partition_metadata);
+ if (data->metadata == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ ret = partition_metadata_open(module, false);
+ if (ret == LDB_SUCCESS) {
+ /* Great, we got the DB open */
+ return LDB_SUCCESS;
+ }
+
+ /* metadata.tdb does not exist, create it */
+ DEBUG(2, ("partition_metadata: Migrating partition metadata: "
+ "open of metadata.tdb gave: %s\n",
+ ldb_errstring(ldb_module_get_ctx(module))));
+ ret = partition_metadata_open(module, true);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "partition_metadata: "
+ "Migrating partition metadata: "
+ "create of metadata.tdb gave: %s\n",
+ ldb_errstring(ldb_module_get_ctx(module)));
+ TALLOC_FREE(data->metadata);
+ return ret;
+ }
+
+ return ret;
+}
+
+
+/*
+ * Read the sequence number, default to 0 if LDB_METADATA_SEQ_NUM key is missing
+ */
+int partition_metadata_sequence_number(struct ldb_module *module, uint64_t *value)
+{
+
+ /* We have to lock all the databases as otherwise we can
+ * return a sequence number that is higher than the DB values
+ * that we can see, as those transactions close after the
+ * metadata.tdb transaction closes */
+ int ret = partition_read_lock(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ * This means we will give a 0 until the first write
+ * transaction, which is actually pretty reasonable.
+ *
+ * All modern databases will have the metadata.tdb from
+ * the time of the first transaction in provision anyway.
+ */
+ ret = partition_metadata_get_uint64(module,
+ LDB_METADATA_SEQ_NUM,
+ value,
+ 0);
+ if (ret == LDB_SUCCESS) {
+ ret = partition_read_unlock(module);
+ } else {
+ /* Don't overwrite the error code */
+ partition_read_unlock(module);
+ }
+ return ret;
+
+}
+
+
+/*
+ * Increment the sequence number, returning the new sequence number
+ */
+int partition_metadata_sequence_number_increment(struct ldb_module *module, uint64_t *value)
+{
+ struct partition_private_data *data;
+ int ret;
+
+ data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+ if (!data || !data->metadata) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: metadata not initialized");
+ }
+
+ if (data->metadata->in_transaction == 0) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: increment sequence number without transaction");
+ }
+
+ ret = partition_metadata_get_uint64(module, LDB_METADATA_SEQ_NUM, value, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (*value == 0) {
+ /*
+ * We are in a transaction now, so we can get the
+ * sequence number from the partitions.
+ */
+ ret = partition_metadata_set_sequence_number(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = partition_metadata_get_uint64(module,
+ LDB_METADATA_SEQ_NUM,
+ value, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ (*value)++;
+ ret = partition_metadata_set_uint64(module, LDB_METADATA_SEQ_NUM, *value, false);
+ return ret;
+}
+/*
+ lock the database for read - use by partition_lock_read
+*/
+int partition_metadata_read_lock(struct ldb_module *module)
+{
+ struct partition_private_data *data
+ = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+ struct tdb_context *tdb = NULL;
+ int tdb_ret = 0;
+ int ret;
+
+ if (!data || !data->metadata || !data->metadata->db) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: metadata not initialized");
+ }
+ tdb = data->metadata->db->tdb;
+
+ if (tdb_transaction_active(tdb) == false &&
+ data->metadata->read_lock_count == 0) {
+ tdb_ret = tdb_lockall_read(tdb);
+ }
+ if (tdb_ret == 0) {
+ data->metadata->read_lock_count++;
+ return LDB_SUCCESS;
+ } else {
+ /* Sadly we can't call ltdb_err_map(tdb_error(tdb)); */
+ ret = LDB_ERR_BUSY;
+ }
+ ldb_debug_set(ldb_module_get_ctx(module),
+ LDB_DEBUG_FATAL,
+ "Failure during partition_metadata_read_lock(): %s",
+ tdb_errorstr(tdb));
+ return ret;
+}
+
+/*
+ unlock the database after a partition_metadata_lock_read()
+*/
+int partition_metadata_read_unlock(struct ldb_module *module)
+{
+ struct partition_private_data *data
+ = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+ struct tdb_context *tdb = NULL;
+
+ data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+ if (!data || !data->metadata || !data->metadata->db) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: metadata not initialized");
+ }
+ tdb = data->metadata->db->tdb;
+
+ if (!tdb_transaction_active(tdb) &&
+ data->metadata->read_lock_count == 1) {
+ tdb_unlockall_read(tdb);
+ data->metadata->read_lock_count--;
+ return 0;
+ }
+ data->metadata->read_lock_count--;
+ return 0;
+}
+
+
+/*
+ * Transaction start
+ */
+int partition_metadata_start_trans(struct ldb_module *module)
+{
+ struct partition_private_data *data;
+ struct tdb_context *tdb;
+
+ data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+ if (!data || !data->metadata || !data->metadata->db) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: metadata not initialized");
+ }
+ tdb = data->metadata->db->tdb;
+
+ if (tdb_transaction_start(tdb) != 0) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ tdb_errorstr(tdb));
+ }
+
+ data->metadata->in_transaction++;
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ * Transaction prepare commit
+ */
+int partition_metadata_prepare_commit(struct ldb_module *module)
+{
+ struct partition_private_data *data;
+ struct tdb_context *tdb;
+
+ data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+ if (!data || !data->metadata || !data->metadata->db) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: metadata not initialized");
+ }
+ tdb = data->metadata->db->tdb;
+
+ if (data->metadata->in_transaction == 0) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: not in transaction");
+ }
+
+ if (tdb_transaction_prepare_commit(tdb) != 0) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ tdb_errorstr(tdb));
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ * Transaction end
+ */
+int partition_metadata_end_trans(struct ldb_module *module)
+{
+ struct partition_private_data *data;
+ struct tdb_context *tdb;
+
+ data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+ if (!data || !data->metadata || !data->metadata->db) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: metadata not initialized");
+ }
+ tdb = data->metadata->db->tdb;
+
+ if (data->metadata->in_transaction == 0) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: not in transaction");
+ }
+
+ data->metadata->in_transaction--;
+
+ if (tdb_transaction_commit(tdb) != 0) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ tdb_errorstr(tdb));
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ * Transaction delete
+ */
+int partition_metadata_del_trans(struct ldb_module *module)
+{
+ struct partition_private_data *data;
+ struct tdb_context *tdb;
+
+ data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct partition_private_data);
+ if (!data || !data->metadata || !data->metadata->db) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: metadata not initialized");
+ }
+ tdb = data->metadata->db->tdb;
+
+ if (data->metadata->in_transaction == 0) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "partition_metadata: not in transaction");
+ }
+
+ data->metadata->in_transaction--;
+
+ tdb_transaction_cancel(tdb);
+
+ return LDB_SUCCESS;
+}
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 0000000..6a713b8
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/password_hash.c
@@ -0,0 +1,5134 @@
+/*
+ ldb database module
+
+ Copyright (C) Simo Sorce 2004-2008
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2006
+ Copyright (C) Andrew Tridgell 2004
+ Copyright (C) Stefan Metzmacher 2007-2010
+ Copyright (C) Matthias Dieter Wallnöfer 2009-2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb password_hash module
+ *
+ * Description: correctly handle AD password changes fields
+ *
+ * Author: Andrew Bartlett
+ * Author: Stefan Metzmacher
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "libcli/auth/libcli_auth.h"
+#include "libcli/security/dom_sid.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "dsdb/samdb/ldb_modules/password_modules.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "lib/crypto/md4.h"
+#include "param/param.h"
+#include "lib/krb5_wrap/krb5_samba.h"
+#include "auth/auth_sam.h"
+#include "auth/common_auth.h"
+#include "lib/messaging/messaging.h"
+#include "lib/param/loadparm.h"
+
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/crypto.h>
+
+#include "kdc/db-glue.h"
+
+#ifdef ENABLE_GPGME
+#undef class
+#include <gpgme.h>
+
+/*
+ * 1.2.0 is what dpkg-shlibdeps generates, based on used symbols and
+ * libgpgme11.symbols
+ * https://salsa.debian.org/debian/gpgme/blob/debian/master/debian/libgpgme11.symbols
+ */
+
+#define MINIMUM_GPGME_VERSION "1.2.0"
+#endif
+
+#undef strncasecmp
+#undef strcasecmp
+
+/* If we have decided there is a reason to work on this request, then
+ * setup all the password hash types correctly.
+ *
+ * If we haven't the hashes yet but the password given as plain-text (attributes
+ * 'unicodePwd', 'userPassword' and 'clearTextPassword') we have to check for
+ * the constraints. Once this is done, we calculate the password hashes.
+ *
+ * Notice: unlike the real AD which only supports the UTF16 special based
+ * 'unicodePwd' and the UTF8 based 'userPassword' plaintext attribute we
+ * understand also a UTF16 based 'clearTextPassword' one.
+ * The latter is also accessible through LDAP so it can also be set by external
+ * tools and scripts. But be aware that this isn't portable on non SAMBA 4 ADs!
+ *
+ * Also when the module receives only the password hashes (possible through
+ * specifying an internal LDB control - for security reasons) some checks are
+ * performed depending on the operation mode (see below) (e.g. if the password
+ * has been in use before if the password memory policy was activated).
+ *
+ * Attention: There is a difference between "modify" and "reset" operations
+ * (see MS-ADTS 3.1.1.3.1.5). If the client sends a "add" and "remove"
+ * operation for a password attribute we thread this as a "modify"; if it sends
+ * only a "replace" one we have an (administrative) reset.
+ *
+ * Finally, if the administrator has requested that a password history
+ * be maintained, then this should also be written out.
+ *
+ */
+
+/* TODO: [consider always MS-ADTS 3.1.1.3.1.5]
+ * - Check for right connection encryption
+ */
+
+/* Notice: Definition of "dsdb_control_password_change_status" moved into
+ * "samdb.h" */
+
+struct ph_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+
+ struct ldb_request *dom_req;
+ struct ldb_reply *dom_res;
+
+ struct ldb_reply *pso_res;
+
+ struct ldb_reply *search_res;
+
+ struct ldb_message *update_msg;
+
+ struct dsdb_control_password_change_status *status;
+ struct dsdb_control_password_change *change;
+
+ const char **gpg_key_ids;
+
+ bool pwd_reset;
+ bool change_status;
+ bool hash_values;
+ bool userPassword;
+ bool update_password;
+ bool update_lastset;
+ bool pwd_last_set_bypass;
+ bool pwd_last_set_default;
+ bool smartcard_reset;
+ const char **userPassword_schemes;
+};
+
+
+struct setup_password_fields_io {
+ struct ph_context *ac;
+
+ struct smb_krb5_context *smb_krb5_context;
+
+ /* info about the user account */
+ struct {
+ uint32_t userAccountControl;
+ NTTIME pwdLastSet;
+ const char *sAMAccountName;
+ const char *user_principal_name;
+ const char *displayName; /* full name */
+ bool is_krbtgt;
+ uint32_t restrictions;
+ struct dom_sid *account_sid;
+ bool store_nt_hash;
+ } u;
+
+ /* new credentials and old given credentials */
+ struct setup_password_fields_given {
+ const struct ldb_val *cleartext_utf8;
+ const struct ldb_val *cleartext_utf16;
+
+ struct samr_Password *nt_hash;
+
+ /*
+ * The AES256 kerberos key to confirm the previous password was
+ * not reused (for n) and to prove the old password was known
+ * (for og).
+ *
+ * We don't have any old salts, so we won't catch password reuse
+ * if said password was used prior to an account rename and
+ * another password change.
+ */
+ DATA_BLOB aes_256;
+ } n, og;
+
+ /* old credentials */
+ struct {
+ struct samr_Password *nt_hash;
+ uint32_t nt_history_len;
+ struct samr_Password *nt_history;
+ const struct ldb_val *supplemental;
+ struct supplementalCredentialsBlob scb;
+
+ /*
+ * The AES256 kerberos key as stored in the DB.
+ * Used to confirm the given password was correct
+ * and in case the previous password was reused.
+ */
+ DATA_BLOB aes_256;
+ DATA_BLOB salt;
+ uint32_t kvno;
+ } o;
+
+ /* generated credentials */
+ struct {
+ struct samr_Password *nt_hash;
+ uint32_t nt_history_len;
+ struct samr_Password *nt_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;
+ } g;
+};
+
+static int msg_find_old_and_new_pwd_val(const struct ldb_message *msg,
+ const char *name,
+ enum ldb_request_type operation,
+ const struct ldb_val **new_val,
+ const struct ldb_val **old_val);
+
+static int password_hash_bypass(struct ldb_module *module, struct ldb_request *request)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const struct ldb_message *msg;
+ struct ldb_message_element *nte;
+ struct ldb_message_element *lme;
+ struct ldb_message_element *nthe;
+ struct ldb_message_element *lmhe;
+ struct ldb_message_element *sce;
+ int ret;
+
+ switch (request->operation) {
+ case LDB_ADD:
+ msg = request->op.add.message;
+ break;
+ case LDB_MODIFY:
+ msg = request->op.mod.message;
+ break;
+ default:
+ return ldb_next_request(module, request);
+ }
+
+ /* nobody must touch password histories and 'supplementalCredentials' */
+
+#define GET_VALUES(el, attr) do { \
+ ret = dsdb_get_expected_new_values(request, \
+ msg, \
+ attr, \
+ &el, \
+ request->operation); \
+ \
+ if (ret != LDB_SUCCESS) { \
+ return ret; \
+ } \
+} while(0)
+
+ GET_VALUES(nte, "unicodePwd");
+
+ /*
+ * Even as Samba contiuues to ignore the LM hash, and reset it
+ * when practical, we keep the constraint that it must be a 16
+ * byte value if specified.
+ */
+ GET_VALUES(lme, "dBCSPwd");
+ GET_VALUES(nthe, "ntPwdHistory");
+ GET_VALUES(lmhe, "lmPwdHistory");
+ GET_VALUES(sce, "supplementalCredentials");
+
+#undef GET_VALUES
+#define CHECK_HASH_ELEMENT(e, min, max) do {\
+ if (e && e->num_values) { \
+ unsigned int _count; \
+ if (e->num_values != 1) { \
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION, \
+ "num_values != 1"); \
+ } \
+ if ((e->values[0].length % 16) != 0) { \
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION, \
+ "length % 16 != 0"); \
+ } \
+ _count = e->values[0].length / 16; \
+ if (_count < min) { \
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION, \
+ "count < min"); \
+ } \
+ if (_count > max) { \
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION, \
+ "count > max"); \
+ } \
+ } \
+} while (0)
+
+ CHECK_HASH_ELEMENT(nte, 1, 1);
+ CHECK_HASH_ELEMENT(lme, 1, 1);
+ CHECK_HASH_ELEMENT(nthe, 1, INT32_MAX);
+ CHECK_HASH_ELEMENT(lmhe, 1, INT32_MAX);
+
+ if (sce && sce->num_values) {
+ enum ndr_err_code ndr_err;
+ struct supplementalCredentialsBlob *scb;
+ struct supplementalCredentialsPackage *scpp = NULL;
+ struct supplementalCredentialsPackage *scpk = NULL;
+ struct supplementalCredentialsPackage *scpkn = NULL;
+ struct supplementalCredentialsPackage *scpct = NULL;
+ DATA_BLOB scpbp = data_blob_null;
+ DATA_BLOB scpbk = data_blob_null;
+ DATA_BLOB scpbkn = data_blob_null;
+ DATA_BLOB scpbct = data_blob_null;
+ DATA_BLOB blob;
+ uint32_t i;
+
+ if (sce->num_values != 1) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "num_values != 1");
+ }
+
+ scb = talloc_zero(request, struct supplementalCredentialsBlob);
+ if (!scb) {
+ return ldb_module_oom(module);
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(&sce->values[0], scb, scb,
+ (ndr_pull_flags_fn_t)ndr_pull_supplementalCredentialsBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "ndr_pull_struct_blob_all");
+ }
+
+ if (scb->sub.num_packages < 2) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "num_packages < 2");
+ }
+
+ for (i=0; i < scb->sub.num_packages; i++) {
+ DATA_BLOB subblob;
+
+ subblob = strhex_to_data_blob(scb, scb->sub.packages[i].data);
+ if (subblob.data == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ if (strcmp(scb->sub.packages[i].name, "Packages") == 0) {
+ if (scpp) {
+ return ldb_error(ldb,
+ LDB_ERR_CONSTRAINT_VIOLATION,
+ "Packages twice");
+ }
+ scpp = &scb->sub.packages[i];
+ scpbp = subblob;
+ continue;
+ }
+ if (strcmp(scb->sub.packages[i].name, "Primary:Kerberos") == 0) {
+ if (scpk) {
+ return ldb_error(ldb,
+ LDB_ERR_CONSTRAINT_VIOLATION,
+ "Primary:Kerberos twice");
+ }
+ scpk = &scb->sub.packages[i];
+ scpbk = subblob;
+ continue;
+ }
+ if (strcmp(scb->sub.packages[i].name, "Primary:Kerberos-Newer-Keys") == 0) {
+ if (scpkn) {
+ return ldb_error(ldb,
+ LDB_ERR_CONSTRAINT_VIOLATION,
+ "Primary:Kerberos-Newer-Keys twice");
+ }
+ scpkn = &scb->sub.packages[i];
+ scpbkn = subblob;
+ continue;
+ }
+ if (strcmp(scb->sub.packages[i].name, "Primary:CLEARTEXT") == 0) {
+ if (scpct) {
+ return ldb_error(ldb,
+ LDB_ERR_CONSTRAINT_VIOLATION,
+ "Primary:CLEARTEXT twice");
+ }
+ scpct = &scb->sub.packages[i];
+ scpbct = subblob;
+ continue;
+ }
+
+ data_blob_free(&subblob);
+ }
+
+ if (scpp == NULL) {
+ return ldb_error(ldb,
+ LDB_ERR_CONSTRAINT_VIOLATION,
+ "Primary:Packages missing");
+ }
+
+ if (scpk == NULL) {
+ /*
+ * If Primary:Kerberos is missing w2k8r2 reboots
+ * when a password is changed.
+ */
+ return ldb_error(ldb,
+ LDB_ERR_CONSTRAINT_VIOLATION,
+ "Primary:Kerberos missing");
+ }
+
+ if (scpp) {
+ struct package_PackagesBlob *p;
+ uint32_t n;
+
+ p = talloc_zero(scb, struct package_PackagesBlob);
+ if (p == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ ndr_err = ndr_pull_struct_blob(&scpbp, p, p,
+ (ndr_pull_flags_fn_t)ndr_pull_package_PackagesBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "ndr_pull_struct_blob Packages");
+ }
+
+ if (p->names == NULL) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "Packages names == NULL");
+ }
+
+ for (n = 0; p->names[n]; n++) {
+ /* noop */
+ }
+
+ if (scb->sub.num_packages != (n + 1)) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "Packages num_packages != num_names + 1");
+ }
+
+ talloc_free(p);
+ }
+
+ if (scpk) {
+ struct package_PrimaryKerberosBlob *k;
+
+ k = talloc_zero(scb, struct package_PrimaryKerberosBlob);
+ if (k == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ ndr_err = ndr_pull_struct_blob(&scpbk, k, k,
+ (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "ndr_pull_struct_blob PrimaryKerberos");
+ }
+
+ if (k->version != 3) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "PrimaryKerberos version != 3");
+ }
+
+ if (k->ctr.ctr3.salt.string == NULL) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "PrimaryKerberos salt == NULL");
+ }
+
+ if (strlen(k->ctr.ctr3.salt.string) == 0) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "PrimaryKerberos strlen(salt) == 0");
+ }
+
+ if (k->ctr.ctr3.num_keys != 2) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "PrimaryKerberos num_keys != 2");
+ }
+
+ if (k->ctr.ctr3.num_old_keys > k->ctr.ctr3.num_keys) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "PrimaryKerberos num_old_keys > num_keys");
+ }
+
+ if (k->ctr.ctr3.keys[0].keytype != ENCTYPE_DES_CBC_MD5) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "PrimaryKerberos key[0] != DES_CBC_MD5");
+ }
+ if (k->ctr.ctr3.keys[1].keytype != ENCTYPE_DES_CBC_CRC) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "PrimaryKerberos key[1] != DES_CBC_CRC");
+ }
+
+ if (k->ctr.ctr3.keys[0].value_len != 8) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "PrimaryKerberos key[0] value_len != 8");
+ }
+ if (k->ctr.ctr3.keys[1].value_len != 8) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "PrimaryKerberos key[1] value_len != 8");
+ }
+
+ for (i = 0; i < k->ctr.ctr3.num_old_keys; i++) {
+ if (k->ctr.ctr3.old_keys[i].keytype ==
+ k->ctr.ctr3.keys[i].keytype &&
+ k->ctr.ctr3.old_keys[i].value_len ==
+ k->ctr.ctr3.keys[i].value_len) {
+ continue;
+ }
+
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "PrimaryKerberos old_keys type/value_len doesn't match");
+ }
+
+ talloc_free(k);
+ }
+
+ if (scpkn) {
+ struct package_PrimaryKerberosBlob *k;
+
+ k = talloc_zero(scb, struct package_PrimaryKerberosBlob);
+ if (k == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ ndr_err = ndr_pull_struct_blob(&scpbkn, k, k,
+ (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "ndr_pull_struct_blob PrimaryKerberosNeverKeys");
+ }
+
+ if (k->version != 4) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNerverKeys version != 4");
+ }
+
+ if (k->ctr.ctr4.salt.string == NULL) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNewerKeys salt == NULL");
+ }
+
+ if (strlen(k->ctr.ctr4.salt.string) == 0) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNewerKeys strlen(salt) == 0");
+ }
+
+ if (k->ctr.ctr4.num_keys != 4) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNewerKeys num_keys != 2");
+ }
+
+ if (k->ctr.ctr4.num_old_keys > k->ctr.ctr4.num_keys) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNewerKeys num_old_keys > num_keys");
+ }
+
+ if (k->ctr.ctr4.num_older_keys > k->ctr.ctr4.num_old_keys) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNewerKeys num_older_keys > num_old_keys");
+ }
+
+ if (k->ctr.ctr4.keys[0].keytype != ENCTYPE_AES256_CTS_HMAC_SHA1_96) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNewerKeys key[0] != AES256");
+ }
+ if (k->ctr.ctr4.keys[1].keytype != ENCTYPE_AES128_CTS_HMAC_SHA1_96) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNewerKeys key[1] != AES128");
+ }
+ if (k->ctr.ctr4.keys[2].keytype != ENCTYPE_DES_CBC_MD5) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNewerKeys key[2] != DES_CBC_MD5");
+ }
+ if (k->ctr.ctr4.keys[3].keytype != ENCTYPE_DES_CBC_CRC) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNewerKeys key[3] != DES_CBC_CRC");
+ }
+
+ if (k->ctr.ctr4.keys[0].value_len != 32) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNewerKeys key[0] value_len != 32");
+ }
+ if (k->ctr.ctr4.keys[1].value_len != 16) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNewerKeys key[1] value_len != 16");
+ }
+ if (k->ctr.ctr4.keys[2].value_len != 8) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNewerKeys key[2] value_len != 8");
+ }
+ if (k->ctr.ctr4.keys[3].value_len != 8) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "KerberosNewerKeys key[3] value_len != 8");
+ }
+
+ /*
+ * TODO:
+ * Maybe we can check old and older keys here.
+ * But we need to do some tests, if the old keys
+ * can be taken from the PrimaryKerberos blob
+ * (with only des keys), when the domain was upgraded
+ * from w2k3 to w2k8.
+ */
+
+ talloc_free(k);
+ }
+
+ if (scpct) {
+ struct package_PrimaryCLEARTEXTBlob *ct;
+
+ ct = talloc_zero(scb, struct package_PrimaryCLEARTEXTBlob);
+ if (ct == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ ndr_err = ndr_pull_struct_blob(&scpbct, ct, ct,
+ (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryCLEARTEXTBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "ndr_pull_struct_blob PrimaryCLEARTEXT");
+ }
+
+ if ((ct->cleartext.length % 2) != 0) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "PrimaryCLEARTEXT length % 2 != 0");
+ }
+
+ talloc_free(ct);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, scb, scb,
+ (ndr_push_flags_fn_t)ndr_push_supplementalCredentialsBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "ndr_pull_struct_blob_all");
+ }
+
+ if (sce->values[0].length != blob.length) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "supplementalCredentialsBlob length differ");
+ }
+
+ if (!mem_equal_const_time(sce->values[0].data, blob.data, blob.length)) {
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION,
+ "supplementalCredentialsBlob memcmp differ");
+ }
+
+ talloc_free(scb);
+ }
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "password_hash_bypass - validated\n");
+ return ldb_next_request(module, request);
+}
+
+/* Get the NT hash, and fill it in as an entry in the password history,
+ and specify it into io->g.nt_hash */
+
+static int setup_nt_fields(struct setup_password_fields_io *io)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+ uint32_t i;
+ if (io->u.store_nt_hash) {
+ io->g.nt_hash = io->n.nt_hash;
+ }
+
+ if (io->ac->status->domain_data.pwdHistoryLength == 0) {
+ return LDB_SUCCESS;
+ }
+
+ /* We might not have an old NT password */
+
+ if (io->g.nt_hash == NULL) {
+ /*
+ * If there was not an NT hash specified, then don't
+ * store the NT password history.
+ *
+ * While the NTLM code on a Windows DC will cope with
+ * a missing unicodePwd, if it finds a last password
+ * in the ntPwdHistory, even if the bytes are zero ,
+ * it will (quite reasonably) treat it as a valid NT
+ * hash. NTLM logins with the previous password are
+ * allowed for a short time after the password is
+ * changed to allow for password propagation delays.
+ */
+ return LDB_SUCCESS;
+ }
+
+ io->g.nt_history = talloc_array(io->ac,
+ struct samr_Password,
+ io->ac->status->domain_data.pwdHistoryLength);
+ if (!io->g.nt_history) {
+ return ldb_oom(ldb);
+ }
+
+ for (i = 0; i < MIN(io->ac->status->domain_data.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;
+
+ io->g.nt_history[0] = *io->g.nt_hash;
+
+ return LDB_SUCCESS;
+}
+
+static int setup_kerberos_keys(struct setup_password_fields_io *io)
+{
+ struct ldb_context *ldb;
+ krb5_error_code krb5_ret;
+ krb5_principal salt_principal = NULL;
+ krb5_data salt_data;
+ krb5_data salt;
+ krb5_keyblock key;
+ krb5_data cleartext_data;
+ uint32_t uac_flags = 0;
+
+ ldb = ldb_module_get_ctx(io->ac->module);
+ cleartext_data.data = (char *)io->n.cleartext_utf8->data;
+ cleartext_data.length = io->n.cleartext_utf8->length;
+
+ uac_flags = io->u.userAccountControl & UF_ACCOUNT_TYPE_MASK;
+ krb5_ret = smb_krb5_salt_principal(io->smb_krb5_context->krb5_context,
+ io->ac->status->domain_data.realm,
+ io->u.sAMAccountName,
+ io->u.user_principal_name,
+ uac_flags,
+ &salt_principal);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(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 = smb_krb5_get_pw_salt(io->smb_krb5_context->krb5_context,
+ salt_principal, &salt_data);
+
+ krb5_free_principal(io->smb_krb5_context->krb5_context, salt_principal);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(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;
+ }
+
+ /* now use the talloced copy of the salt */
+ salt.data = talloc_strndup(io->ac,
+ (char *)salt_data.data,
+ salt_data.length);
+ io->g.salt = salt.data;
+ salt.length = strlen(io->g.salt);
+
+ smb_krb5_free_data_contents(io->smb_krb5_context->krb5_context,
+ &salt_data);
+
+ /*
+ * create ENCTYPE_AES256_CTS_HMAC_SHA1_96 key out of
+ * the salt and the cleartext password
+ */
+ krb5_ret = smb_krb5_create_key_from_string(io->smb_krb5_context->krb5_context,
+ NULL,
+ &salt,
+ &cleartext_data,
+ ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+ &key);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(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,
+ KRB5_KEY_DATA(&key),
+ KRB5_KEY_LENGTH(&key));
+ krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key);
+ if (!io->g.aes_256.data) {
+ return ldb_oom(ldb);
+ }
+
+ /*
+ * create ENCTYPE_AES128_CTS_HMAC_SHA1_96 key out of
+ * the salt and the cleartext password
+ */
+ krb5_ret = smb_krb5_create_key_from_string(io->smb_krb5_context->krb5_context,
+ NULL,
+ &salt,
+ &cleartext_data,
+ ENCTYPE_AES128_CTS_HMAC_SHA1_96,
+ &key);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(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,
+ KRB5_KEY_DATA(&key),
+ KRB5_KEY_LENGTH(&key));
+ krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key);
+ if (!io->g.aes_128.data) {
+ return ldb_oom(ldb);
+ }
+
+ /*
+ * As per RFC-6649 single DES encryption types are no longer considered
+ * secure to be used in Kerberos, we store random keys instead of the
+ * ENCTYPE_DES_CBC_MD5 and ENCTYPE_DES_CBC_CRC keys.
+ */
+ io->g.des_md5 = data_blob_talloc(io->ac, NULL, 8);
+ if (!io->g.des_md5.data) {
+ return ldb_oom(ldb);
+ }
+ generate_secret_buffer(io->g.des_md5.data, 8);
+
+ io->g.des_crc = data_blob_talloc(io->ac, NULL, 8);
+ if (!io->g.des_crc.data) {
+ return ldb_oom(ldb);
+ }
+ generate_secret_buffer(io->g.des_crc.data, 8);
+
+ return LDB_SUCCESS;
+}
+
+static int setup_kerberos_key_hash(struct setup_password_fields_io *io,
+ struct setup_password_fields_given *g)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+ krb5_error_code krb5_ret;
+ krb5_data salt;
+ krb5_keyblock key;
+ krb5_data cleartext_data;
+
+ if (io->ac->search_res == NULL) {
+ /* No old data so nothing to do */
+ return LDB_SUCCESS;
+ }
+
+ if (io->o.salt.data == NULL) {
+ /* We didn't fetch the salt in setup_io(), so nothing to do */
+ return LDB_SUCCESS;
+ }
+
+ salt.data = (char *)io->o.salt.data;
+ salt.length = io->o.salt.length;
+
+ cleartext_data.data = (char *)g->cleartext_utf8->data;
+ cleartext_data.length = g->cleartext_utf8->length;
+
+ /*
+ * create ENCTYPE_AES256_CTS_HMAC_SHA1_96 key out of the salt
+ * and the cleartext password
+ */
+ krb5_ret = smb_krb5_create_key_from_string(io->smb_krb5_context->krb5_context,
+ NULL,
+ &salt,
+ &cleartext_data,
+ ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+ &key);
+ if (krb5_ret) {
+ ldb_asprintf_errstring(ldb,
+ "setup_kerberos_key_hash: "
+ "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;
+ }
+
+ g->aes_256 = data_blob_talloc(io->ac,
+ KRB5_KEY_DATA(&key),
+ KRB5_KEY_LENGTH(&key));
+ krb5_free_keyblock_contents(io->smb_krb5_context->krb5_context, &key);
+ if (g->aes_256.data == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ talloc_keep_secret(g->aes_256.data);
+
+ return LDB_SUCCESS;
+}
+
+static int setup_primary_kerberos(struct setup_password_fields_io *io,
+ const struct supplementalCredentialsBlob *old_scb,
+ struct package_PrimaryKerberosBlob *pkb)
+{
+ struct ldb_context *ldb;
+ 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;
+
+ ldb = ldb_module_get_ctx(io->ac->module);
+
+ /*
+ * 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) {
+ return ldb_oom(ldb);
+ }
+
+ 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(io->ac, old_scp->data);
+ if (!blob.data) {
+ return ldb_oom(ldb);
+ }
+
+ /* 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, &_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(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(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 ldb_context *ldb;
+ 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;
+
+ ldb = ldb_module_get_ctx(io->ac->module);
+
+ /*
+ * 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) {
+ return ldb_oom(ldb);
+ }
+
+ 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(io->ac, old_scp->data);
+ if (!blob.data) {
+ return ldb_oom(ldb);
+ }
+
+ /* 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,
+ &_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(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(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)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+ 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 digest;
+ DATA_BLOB delim;
+ DATA_BLOB backslash;
+ uint8_t i;
+ struct {
+ DATA_BLOB *user;
+ DATA_BLOB *realm;
+ DATA_BLOB *nt4dom;
+ } wdigest[] = {
+ /*
+ * See 3.1.1.8.11.3.1 WDIGEST_CREDENTIALS Construction
+ * https://msdn.microsoft.com/en-us/library/cc245680.aspx
+ * 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
+ *
+ * TODO:
+ * Windows preserves the case of the DNS domain,
+ * Samba lower cases the domain at provision time
+ * This means that for mixed case Domains, the WDigest08 hash
+ * calculated by Samba differs from that calculated by Windows.
+ * Until we get a real world use case this will remain a known
+ * bug, as changing the case could have unforeseen impacts.
+ *
+ */
+ {
+ .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
+ },
+ };
+ int rc = LDB_ERR_OTHER;
+
+ /* 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) {
+ return ldb_oom(ldb);
+ }
+ sAMAccountName_u = data_blob_string_const(strupper_talloc(io->ac, io->u.sAMAccountName));
+ if (!sAMAccountName_u.data) {
+ return ldb_oom(ldb);
+ }
+
+ /* 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->ac->status->domain_data.dns_domain);
+ if (!user_principal_name) {
+ return ldb_oom(ldb);
+ }
+ }
+ 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) {
+ return ldb_oom(ldb);
+ }
+ userPrincipalName_u = data_blob_string_const(strupper_talloc(io->ac, user_principal_name));
+ if (!userPrincipalName_u.data) {
+ return ldb_oom(ldb);
+ }
+
+ netbios_domain = data_blob_string_const(io->ac->status->domain_data.netbios_domain);
+ netbios_domain_l = data_blob_string_const(strlower_talloc(io->ac,
+ io->ac->status->domain_data.netbios_domain));
+ if (!netbios_domain_l.data) {
+ return ldb_oom(ldb);
+ }
+ netbios_domain_u = data_blob_string_const(strupper_talloc(io->ac,
+ io->ac->status->domain_data.netbios_domain));
+ if (!netbios_domain_u.data) {
+ return ldb_oom(ldb);
+ }
+
+ dns_domain = data_blob_string_const(io->ac->status->domain_data.dns_domain);
+ dns_domain_l = data_blob_string_const(io->ac->status->domain_data.dns_domain);
+ dns_domain_u = data_blob_string_const(io->ac->status->domain_data.realm);
+
+ 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) {
+ return ldb_oom(ldb);
+ }
+
+ for (i=0; i < ARRAY_SIZE(wdigest); i++) {
+ gnutls_hash_hd_t hash_hnd = NULL;
+
+ rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5);
+ if (rc < 0) {
+ rc = ldb_oom(ldb);
+ goto out;
+ }
+
+ if (wdigest[i].nt4dom) {
+ rc = gnutls_hash(hash_hnd,
+ wdigest[i].nt4dom->data,
+ wdigest[i].nt4dom->length);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ rc = LDB_ERR_UNWILLING_TO_PERFORM;
+ goto out;
+ }
+ rc = gnutls_hash(hash_hnd,
+ backslash.data,
+ backslash.length);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ rc = LDB_ERR_UNWILLING_TO_PERFORM;
+ goto out;
+ }
+ }
+ rc = gnutls_hash(hash_hnd,
+ wdigest[i].user->data,
+ wdigest[i].user->length);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ rc = LDB_ERR_UNWILLING_TO_PERFORM;
+ goto out;
+ }
+ rc = gnutls_hash(hash_hnd, delim.data, delim.length);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ rc = LDB_ERR_UNWILLING_TO_PERFORM;
+ goto out;
+ }
+ if (wdigest[i].realm) {
+ rc = gnutls_hash(hash_hnd,
+ wdigest[i].realm->data,
+ wdigest[i].realm->length);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ rc = LDB_ERR_UNWILLING_TO_PERFORM;
+ goto out;
+ }
+ }
+ rc = gnutls_hash(hash_hnd, delim.data, delim.length);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ rc = LDB_ERR_UNWILLING_TO_PERFORM;
+ goto out;
+ }
+ rc = gnutls_hash(hash_hnd,
+ io->n.cleartext_utf8->data,
+ io->n.cleartext_utf8->length);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ rc = LDB_ERR_UNWILLING_TO_PERFORM;
+ goto out;
+ }
+
+ gnutls_hash_deinit(hash_hnd, pdb->hashes[i].hash);
+ }
+
+ rc = LDB_SUCCESS;
+out:
+ return rc;
+}
+
+#define SHA_SALT_PERMITTED_CHARS "abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ "0123456789./"
+#define SHA_SALT_SIZE 16
+#define SHA_256_SCHEME "CryptSHA256"
+#define SHA_512_SCHEME "CryptSHA512"
+#define CRYPT "{CRYPT}"
+#define SHA_ID_LEN 3
+#define SHA_256_ALGORITHM_ID 5
+#define SHA_512_ALGORITHM_ID 6
+#define ROUNDS_PARAMETER "rounds="
+
+/*
+ * Extract the crypt (3) algorithm number and number of hash rounds from the
+ * supplied scheme string
+ */
+static bool parse_scheme(const char *scheme, int *algorithm, int *rounds) {
+
+ const char *rp = NULL; /* Pointer to the 'rounds=' option */
+ char digits[21]; /* digits extracted from the rounds option */
+ int i = 0; /* loop index variable */
+
+ if (strncasecmp(SHA_256_SCHEME, scheme, strlen(SHA_256_SCHEME)) == 0) {
+ *algorithm = SHA_256_ALGORITHM_ID;
+ } else if (strncasecmp(SHA_512_SCHEME, scheme, strlen(SHA_256_SCHEME))
+ == 0) {
+ *algorithm = SHA_512_ALGORITHM_ID;
+ } else {
+ return false;
+ }
+
+ rp = strcasestr(scheme, ROUNDS_PARAMETER);
+ if (rp == NULL) {
+ /* No options specified, use crypt default number of rounds */
+ *rounds = 0;
+ return true;
+ }
+ rp += strlen(ROUNDS_PARAMETER);
+ for (i = 0; isdigit(rp[i]) && i < (sizeof(digits) - 1); i++) {
+ digits[i] = rp[i];
+ }
+ digits[i] = '\0';
+ *rounds = atoi(digits);
+ return true;
+}
+
+/*
+ * Calculate the password hash specified by scheme, and return it in
+ * hash_value
+ */
+static int setup_primary_userPassword_hash(
+ TALLOC_CTX *ctx,
+ struct setup_password_fields_io *io,
+ const char* scheme,
+ struct package_PrimaryUserPasswordValue *hash_value)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+ const char *salt = NULL; /* Randomly generated salt */
+ const char *cmd = NULL; /* command passed to crypt */
+ const char *hash = NULL; /* password hash generated by crypt */
+ int algorithm = 0; /* crypt hash algorithm number */
+ int rounds = 0; /* The number of hash rounds */
+ DATA_BLOB *hash_blob = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+#if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT_RN)
+ struct crypt_data crypt_data = {
+ .initialized = 0 /* working storage used by crypt */
+ };
+#endif
+
+ /* Genrate a random password salt */
+ salt = generate_random_str_list(frame,
+ SHA_SALT_SIZE,
+ SHA_SALT_PERMITTED_CHARS);
+ if (salt == NULL) {
+ TALLOC_FREE(frame);
+ return ldb_oom(ldb);
+ }
+
+ /* determine the hashing algoritm and number of rounds*/
+ if (!parse_scheme(scheme, &algorithm, &rounds)) {
+ ldb_asprintf_errstring(
+ ldb,
+ "setup_primary_userPassword: Invalid scheme of [%s] "
+ "specified for 'password hash userPassword schemes' in "
+ "samba.conf",
+ scheme);
+ TALLOC_FREE(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ hash_value->scheme = talloc_strdup(ctx, CRYPT);
+ hash_value->scheme_len = strlen(CRYPT) + 1;
+
+ /* generate the id/salt parameter used by crypt */
+ if (rounds) {
+ cmd = talloc_asprintf(frame,
+ "$%d$rounds=%d$%s",
+ algorithm,
+ rounds,
+ salt);
+ } else {
+ cmd = talloc_asprintf(frame, "$%d$%s", algorithm, salt);
+ }
+
+ /*
+ * Relies on the assertion that cleartext_utf8->data is a zero
+ * terminated UTF-8 string
+ */
+
+ /*
+ * crypt_r() and crypt() may return a null pointer upon error
+ * depending on how libcrypt was configured, so we prefer
+ * crypt_rn() from libcrypt / libxcrypt which always returns
+ * NULL on error.
+ *
+ * POSIX specifies returning a null pointer and setting
+ * errno.
+ *
+ * RHEL 7 (which does not use libcrypt / libxcrypt) returns a
+ * non-NULL pointer from crypt_r() on success but (always?)
+ * sets errno during internal processing in the NSS crypto
+ * subsystem.
+ *
+ * By preferring crypt_rn we avoid the 'return non-NULL but
+ * set-errno' that we otherwise cannot tell apart from the
+ * RHEL 7 behaviour.
+ */
+ errno = 0;
+
+#ifdef HAVE_CRYPT_RN
+ hash = crypt_rn((char *)io->n.cleartext_utf8->data,
+ cmd,
+ &crypt_data,
+ sizeof(crypt_data));
+#elif HAVE_CRYPT_R
+ hash = crypt_r((char *)io->n.cleartext_utf8->data, cmd, &crypt_data);
+#else
+ /*
+ * No crypt_r falling back to crypt, which is NOT thread safe
+ * Thread safety MT-Unsafe race:crypt
+ */
+ hash = crypt((char *)io->n.cleartext_utf8->data, cmd);
+#endif
+ /*
+ * On error, crypt() and crypt_r() may return a null pointer,
+ * or a pointer to an invalid hash beginning with a '*'.
+ */
+ if (hash == NULL || hash[0] == '*') {
+ char buf[1024];
+ const char *reason = NULL;
+ if (errno == ERANGE) {
+ reason = "Password exceeds maximum length allowed for crypt() hashing";
+ } else {
+ int err = strerror_r(errno, buf, sizeof(buf));
+ if (err == 0) {
+ reason = buf;
+ } else {
+ reason = "Unknown error";
+ }
+ }
+ ldb_asprintf_errstring(
+ ldb,
+ "setup_primary_userPassword: generation of a %s "
+ "password hash failed: (%s)",
+ scheme,
+ reason);
+ TALLOC_FREE(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ hash_blob = talloc_zero(ctx, DATA_BLOB);
+
+ if (hash_blob == NULL) {
+ TALLOC_FREE(frame);
+ return ldb_oom(ldb);
+ }
+
+ *hash_blob = data_blob_talloc(hash_blob,
+ (const uint8_t *)hash,
+ strlen(hash));
+ if (hash_blob->data == NULL) {
+ TALLOC_FREE(frame);
+ return ldb_oom(ldb);
+ }
+ hash_value->value = hash_blob;
+ TALLOC_FREE(frame);
+ return LDB_SUCCESS;
+}
+
+/*
+ * Calculate the desired extra password hashes
+ */
+static int setup_primary_userPassword(
+ struct setup_password_fields_io *io,
+ const struct supplementalCredentialsBlob *old_scb,
+ struct package_PrimaryUserPasswordBlob *p_userPassword_b)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+ TALLOC_CTX *frame = talloc_stackframe();
+ int i;
+ int ret;
+
+ /*
+ * Save the current nt_hash, use this to determine if the password
+ * has been changed by windows. Which will invalidate the userPassword
+ * hash. Note once NTLM-Strong-NOWTF becomes available it should be
+ * used in preference to the NT password hash
+ */
+ if (io->g.nt_hash == NULL) {
+ /*
+ * When the NT hash is not available, we use this field to store
+ * the first 16 bytes of the AES256 key instead. This allows
+ * 'samba-tool user' to verify that the user's password is in
+ * sync with the userPassword package.
+ */
+ uint8_t hash_len = MIN(16, io->g.aes_256.length);
+
+ ZERO_STRUCT(p_userPassword_b->current_nt_hash);
+ memcpy(p_userPassword_b->current_nt_hash.hash,
+ io->g.aes_256.data,
+ hash_len);
+ } else {
+ p_userPassword_b->current_nt_hash = *io->g.nt_hash;
+ }
+
+ /*
+ * Determine the number of hashes
+ * Note: that currently there is no limit on the number of hashes
+ * no checking is done on the number of schemes specified
+ * or for uniqueness.
+ */
+ p_userPassword_b->num_hashes = 0;
+ for (i = 0; io->ac->userPassword_schemes[i]; i++) {
+ p_userPassword_b->num_hashes++;
+ }
+
+ p_userPassword_b->hashes
+ = talloc_array(io->ac,
+ struct package_PrimaryUserPasswordValue,
+ p_userPassword_b->num_hashes);
+ if (p_userPassword_b->hashes == NULL) {
+ TALLOC_FREE(frame);
+ return ldb_oom(ldb);
+ }
+
+ for (i = 0; io->ac->userPassword_schemes[i]; i++) {
+ ret = setup_primary_userPassword_hash(
+ p_userPassword_b->hashes,
+ io,
+ io->ac->userPassword_schemes[i],
+ &p_userPassword_b->hashes[i]);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+
+static int setup_primary_samba_gpg(struct setup_password_fields_io *io,
+ struct package_PrimarySambaGPGBlob *pgb)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+#ifdef ENABLE_GPGME
+ gpgme_error_t gret;
+ gpgme_ctx_t ctx = NULL;
+ size_t num_keys = str_list_length(io->ac->gpg_key_ids);
+ gpgme_key_t keys[num_keys+1];
+ size_t ki = 0;
+ size_t kr = 0;
+ gpgme_data_t plain_data = NULL;
+ gpgme_data_t crypt_data = NULL;
+ size_t crypt_length = 0;
+ char *crypt_mem = NULL;
+
+ gret = gpgme_new(&ctx);
+ if (gret != GPG_ERR_NO_ERROR) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "%s:%s: gret[%u] %s\n",
+ __location__, __func__,
+ gret, gpgme_strerror(gret));
+ return ldb_module_operr(io->ac->module);
+ }
+
+ gpgme_set_armor(ctx, 1);
+
+ gret = gpgme_data_new_from_mem(&plain_data,
+ (const char *)io->n.cleartext_utf16->data,
+ io->n.cleartext_utf16->length,
+ 0 /* no copy */);
+ if (gret != GPG_ERR_NO_ERROR) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "%s:%s: gret[%u] %s\n",
+ __location__, __func__,
+ gret, gpgme_strerror(gret));
+ gpgme_release(ctx);
+ return ldb_module_operr(io->ac->module);
+ }
+ gret = gpgme_data_new(&crypt_data);
+ if (gret != GPG_ERR_NO_ERROR) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "%s:%s: gret[%u] %s\n",
+ __location__, __func__,
+ gret, gpgme_strerror(gret));
+ gpgme_data_release(plain_data);
+ gpgme_release(ctx);
+ return ldb_module_operr(io->ac->module);
+ }
+
+ for (ki = 0; ki < num_keys; ki++) {
+ const char *key_id = io->ac->gpg_key_ids[ki];
+ size_t len = strlen(key_id);
+
+ keys[ki] = NULL;
+
+ if (len < 16) {
+ ldb_debug(ldb, LDB_DEBUG_FATAL,
+ "%s:%s: ki[%zu] key_id[%s] strlen < 16, "
+ "please specify at least the 64bit key id\n",
+ __location__, __func__,
+ ki, key_id);
+ for (kr = 0; keys[kr] != NULL; kr++) {
+ gpgme_key_release(keys[kr]);
+ }
+ gpgme_data_release(crypt_data);
+ gpgme_data_release(plain_data);
+ gpgme_release(ctx);
+ return ldb_module_operr(io->ac->module);
+ }
+
+ gret = gpgme_get_key(ctx, key_id, &keys[ki], 0 /* public key */);
+ if (gret != GPG_ERR_NO_ERROR) {
+ keys[ki] = NULL;
+ if (gpg_err_source(gret) == GPG_ERR_SOURCE_GPGME
+ && gpg_err_code(gret) == GPG_ERR_EOF) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Invalid "
+ "'password hash gpg key ids': "
+ "Public Key ID [%s] "
+ "not found in keyring\n",
+ key_id);
+
+ } else {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "%s:%s: ki[%zu] key_id[%s] "
+ "gret[%u] %s\n",
+ __location__, __func__,
+ ki, key_id,
+ gret, gpgme_strerror(gret));
+ }
+ for (kr = 0; keys[kr] != NULL; kr++) {
+ gpgme_key_release(keys[kr]);
+ }
+ gpgme_data_release(crypt_data);
+ gpgme_data_release(plain_data);
+ gpgme_release(ctx);
+ return ldb_module_operr(io->ac->module);
+ }
+ }
+ keys[ki] = NULL;
+
+ gret = gpgme_op_encrypt(ctx, keys,
+ GPGME_ENCRYPT_ALWAYS_TRUST,
+ plain_data, crypt_data);
+ gpgme_data_release(plain_data);
+ plain_data = NULL;
+ for (kr = 0; keys[kr] != NULL; kr++) {
+ gpgme_key_release(keys[kr]);
+ keys[kr] = NULL;
+ }
+ gpgme_release(ctx);
+ ctx = NULL;
+ if (gret != GPG_ERR_NO_ERROR) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "%s:%s: gret[%u] %s\n",
+ __location__, __func__,
+ gret, gpgme_strerror(gret));
+ gpgme_data_release(crypt_data);
+ return ldb_module_operr(io->ac->module);
+ }
+
+ crypt_mem = gpgme_data_release_and_get_mem(crypt_data, &crypt_length);
+ crypt_data = NULL;
+ if (crypt_mem == NULL) {
+ return ldb_module_oom(io->ac->module);
+ }
+
+ pgb->gpg_blob = data_blob_talloc(io->ac,
+ (const uint8_t *)crypt_mem,
+ crypt_length);
+ gpgme_free(crypt_mem);
+ crypt_mem = NULL;
+ crypt_length = 0;
+ if (pgb->gpg_blob.data == NULL) {
+ return ldb_module_oom(io->ac->module);
+ }
+
+ return LDB_SUCCESS;
+#else /* ENABLE_GPGME */
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "You configured 'password hash gpg key ids', "
+ "but GPGME support is missing. (%s:%d)",
+ __FILE__, __LINE__);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+#endif /* else ENABLE_GPGME */
+}
+
+#define NUM_PACKAGES 6
+static int setup_supplemental_field(struct setup_password_fields_io *io)
+{
+ struct ldb_context *ldb;
+ struct supplementalCredentialsBlob scb;
+ struct supplementalCredentialsBlob *old_scb = NULL;
+ /*
+ * Packages +
+ * ( Kerberos-Newer-Keys, Kerberos,
+ * WDigest, CLEARTEXT, userPassword, SambaGPG)
+ */
+ uint32_t num_names = 0;
+ const char *names[1+NUM_PACKAGES];
+ uint32_t num_packages = 0;
+ struct supplementalCredentialsPackage packages[1+NUM_PACKAGES];
+ struct supplementalCredentialsPackage *pp = packages;
+ int ret;
+ enum ndr_err_code ndr_err;
+ bool do_newer_keys = false;
+ bool do_cleartext = false;
+ bool do_samba_gpg = false;
+ struct loadparm_context *lp_ctx = NULL;
+
+ ZERO_STRUCT(names);
+ ZERO_STRUCT(packages);
+
+ ldb = ldb_module_get_ctx(io->ac->module);
+ lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+
+ if (!io->n.cleartext_utf8) {
+ /*
+ * 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 use it */
+ if (io->o.supplemental) {
+ if (io->o.scb.sub.signature == SUPPLEMENTAL_CREDENTIALS_SIGNATURE) {
+ old_scb = &io->o.scb;
+ } else {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "setup_supplemental_field: "
+ "supplementalCredentialsBlob "
+ "signature[0x%04X] expected[0x%04X]",
+ io->o.scb.sub.signature,
+ SUPPLEMENTAL_CREDENTIALS_SIGNATURE);
+ }
+ }
+ /* Per MS-SAMR 3.1.1.8.11.6 we create AES keys if our domain functionality level is 2008 or higher */
+
+
+
+ /*
+ * The ordering is this
+ *
+ * Primary:Kerberos-Newer-Keys (optional)
+ * Primary:Kerberos
+ * Primary:WDigest
+ * Primary:CLEARTEXT (optional)
+ * Primary:userPassword
+ * Primary:SambaGPG (optional)
+ *
+ * And the 'Packages' package is insert before the last
+ * other package.
+ *
+ * Note: it's important that Primary:SambaGPG is added as
+ * the last element. This is the indication that it matches
+ * the current password. When a password change happens on
+ * a Windows DC, it will keep the old Primary:SambaGPG value,
+ * but as the first element.
+ */
+ do_newer_keys = (dsdb_functional_level(ldb) >= DS_DOMAIN_FUNCTION_2008);
+ if (do_newer_keys) {
+ struct package_PrimaryKerberosBlob pknb;
+ DATA_BLOB pknb_blob;
+ char *pknb_hexstr;
+ /*
+ * setup 'Primary:Kerberos-Newer-Keys' element
+ */
+ names[num_names++] = "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,
+ &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(
+ ldb,
+ "setup_supplemental_field: "
+ "failed to push "
+ "package_PrimaryKerberosNeverBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pknb_hexstr = data_blob_hex_string_upper(io->ac, &pknb_blob);
+ if (!pknb_hexstr) {
+ return ldb_oom(ldb);
+ }
+ pp->name = "Primary:Kerberos-Newer-Keys";
+ pp->reserved = 1;
+ pp->data = pknb_hexstr;
+ pp++;
+ num_packages++;
+ }
+
+ {
+ /*
+ * setup 'Primary:Kerberos' element
+ */
+ /* Primary:Kerberos */
+ struct package_PrimaryKerberosBlob pkb;
+ DATA_BLOB pkb_blob;
+ char *pkb_hexstr;
+
+ names[num_names++] = "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,
+ &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(
+ ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PrimaryKerberosBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pkb_hexstr = data_blob_hex_string_upper(io->ac, &pkb_blob);
+ if (!pkb_hexstr) {
+ return ldb_oom(ldb);
+ }
+ pp->name = "Primary:Kerberos";
+ pp->reserved = 1;
+ pp->data = pkb_hexstr;
+ pp++;
+ num_packages++;
+ }
+
+ if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_ALLOWED) {
+ /*
+ * setup 'Primary:WDigest' element
+ */
+ struct package_PrimaryWDigestBlob pdb;
+ DATA_BLOB pdb_blob;
+ char *pdb_hexstr;
+
+ names[num_names++] = "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,
+ &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(
+ ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PrimaryWDigestBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pdb_hexstr = data_blob_hex_string_upper(io->ac, &pdb_blob);
+ if (!pdb_hexstr) {
+ return ldb_oom(ldb);
+ }
+ pp->name = "Primary:WDigest";
+ pp->reserved = 1;
+ pp->data = pdb_hexstr;
+ pp++;
+ num_packages++;
+ }
+
+ /*
+ * setup 'Primary:CLEARTEXT' element
+ */
+ if (io->ac->status->domain_data.store_cleartext &&
+ (io->u.userAccountControl & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED)) {
+ do_cleartext = true;
+ }
+ if (do_cleartext) {
+ struct package_PrimaryCLEARTEXTBlob pcb;
+ DATA_BLOB pcb_blob;
+ char *pcb_hexstr;
+
+ names[num_names++] = "CLEARTEXT";
+
+ pcb.cleartext = *io->n.cleartext_utf16;
+
+ ndr_err = ndr_push_struct_blob(
+ &pcb_blob, io->ac,
+ &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(
+ ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PrimaryCLEARTEXTBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pcb_hexstr = data_blob_hex_string_upper(io->ac, &pcb_blob);
+ if (!pcb_hexstr) {
+ return ldb_oom(ldb);
+ }
+ pp->name = "Primary:CLEARTEXT";
+ pp->reserved = 1;
+ pp->data = pcb_hexstr;
+ pp++;
+ num_packages++;
+ }
+
+ /*
+ * Don't generate crypt() or similar password for the krbtgt account.
+ * It's unnecessary, and the length of the cleartext in UTF-8 form
+ * exceeds the maximum (CRYPT_MAX_PASSPHRASE_SIZE) allowed by crypt().
+ */
+ if (io->ac->userPassword_schemes && !io->u.is_krbtgt) {
+ /*
+ * setup 'Primary:userPassword' element
+ */
+ struct package_PrimaryUserPasswordBlob
+ p_userPassword_b;
+ DATA_BLOB p_userPassword_b_blob;
+ char *p_userPassword_b_hexstr;
+
+ names[num_names++] = "userPassword";
+
+ ret = setup_primary_userPassword(io,
+ old_scb,
+ &p_userPassword_b);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ndr_err = ndr_push_struct_blob(
+ &p_userPassword_b_blob,
+ io->ac,
+ &p_userPassword_b,
+ (ndr_push_flags_fn_t)
+ ndr_push_package_PrimaryUserPasswordBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(
+ ldb,
+ "setup_supplemental_field: failed to push "
+ "package_PrimaryUserPasswordBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ p_userPassword_b_hexstr
+ = data_blob_hex_string_upper(
+ io->ac,
+ &p_userPassword_b_blob);
+ if (!p_userPassword_b_hexstr) {
+ return ldb_oom(ldb);
+ }
+ pp->name = "Primary:userPassword";
+ pp->reserved = 1;
+ pp->data = p_userPassword_b_hexstr;
+ pp++;
+ num_packages++;
+ }
+
+ /*
+ * setup 'Primary:SambaGPG' element
+ */
+ if (io->ac->gpg_key_ids != NULL) {
+ do_samba_gpg = true;
+ }
+ if (do_samba_gpg) {
+ struct package_PrimarySambaGPGBlob pgb;
+ DATA_BLOB pgb_blob;
+ char *pgb_hexstr;
+
+ names[num_names++] = "SambaGPG";
+
+ ret = setup_primary_samba_gpg(io, &pgb);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ndr_err = ndr_push_struct_blob(&pgb_blob, io->ac, &pgb,
+ (ndr_push_flags_fn_t)ndr_push_package_PrimarySambaGPGBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(ldb,
+ "setup_supplemental_field: failed to "
+ "push package_PrimarySambaGPGBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pgb_hexstr = data_blob_hex_string_upper(io->ac, &pgb_blob);
+ if (!pgb_hexstr) {
+ return ldb_oom(ldb);
+ }
+ pp->name = "Primary:SambaGPG";
+ pp->reserved = 1;
+ pp->data = pgb_hexstr;
+ pp++;
+ num_packages++;
+ }
+
+ /*
+ * setup 'Packages' element
+ */
+ {
+ struct package_PackagesBlob pb;
+ DATA_BLOB pb_blob;
+ char *pb_hexstr;
+
+ pb.names = names;
+ ndr_err = ndr_push_struct_blob(
+ &pb_blob, io->ac,
+ &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(
+ ldb,
+ "setup_supplemental_field: "
+ "failed to push package_PackagesBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ pb_hexstr = data_blob_hex_string_upper(io->ac, &pb_blob);
+ if (!pb_hexstr) {
+ return ldb_oom(ldb);
+ }
+ pp->name = "Packages";
+ pp->reserved = 2;
+ pp->data = pb_hexstr;
+ num_packages++;
+ /*
+ * We don't increment pp so it's pointing to the last package
+ */
+ }
+
+ /*
+ * setup 'supplementalCredentials' value
+ */
+ {
+ /*
+ * The 'Packages' element needs to be the second last element
+ * in supplementalCredentials
+ */
+ struct supplementalCredentialsPackage temp;
+ struct supplementalCredentialsPackage *prev;
+
+ prev = pp-1;
+ temp = *prev;
+ *prev = *pp;
+ *pp = temp;
+
+ ZERO_STRUCT(scb);
+ scb.sub.signature = SUPPLEMENTAL_CREDENTIALS_SIGNATURE;
+ scb.sub.num_packages = num_packages;
+ scb.sub.packages = packages;
+
+ ndr_err = ndr_push_struct_blob(
+ &io->g.supplemental, io->ac,
+ &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(
+ 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)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+ const struct ldb_message *msg = NULL;
+ struct timeval tv = { .tv_sec = 0 };
+ const struct ldb_val *old_val = NULL;
+ const struct ldb_val *new_val = NULL;
+ int ret;
+
+ switch (io->ac->req->operation) {
+ case LDB_ADD:
+ msg = io->ac->req->op.add.message;
+ break;
+ case LDB_MODIFY:
+ msg = io->ac->req->op.mod.message;
+ break;
+ default:
+ return LDB_ERR_OPERATIONS_ERROR;
+ break;
+ }
+
+ if (io->ac->pwd_last_set_bypass) {
+ struct ldb_message_element *el = NULL;
+ size_t i;
+ size_t count = 0;
+ /*
+ * This is a message from pdb_samba_dsdb_replace_by_sam()
+ *
+ * We want to ensure there is only one pwdLastSet element, and
+ * it isn't deleting.
+ */
+ if (msg == NULL) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ for (i = 0; i < msg->num_elements; i++) {
+ if (ldb_attr_cmp(msg->elements[i].name,
+ "pwdLastSet") == 0) {
+ count++;
+ el = &msg->elements[i];
+ }
+ }
+ if (count != 1) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ io->g.last_set = samdb_result_nttime(msg, "pwdLastSet", 0);
+ return LDB_SUCCESS;
+ }
+
+ ret = msg_find_old_and_new_pwd_val(msg, "pwdLastSet",
+ io->ac->req->operation,
+ &new_val, &old_val);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (old_val != NULL && new_val == NULL) {
+ ldb_set_errstring(ldb,
+ "'pwdLastSet' deletion is not allowed!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ io->g.last_set = UINT64_MAX;
+ if (new_val != NULL) {
+ struct ldb_message *tmp_msg = NULL;
+
+ tmp_msg = ldb_msg_new(io->ac);
+ if (tmp_msg == NULL) {
+ return ldb_module_oom(io->ac->module);
+ }
+
+ if (old_val != NULL) {
+ NTTIME old_last_set = 0;
+
+ ret = ldb_msg_add_value(tmp_msg, "oldval",
+ old_val, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ old_last_set = samdb_result_nttime(tmp_msg,
+ "oldval",
+ 1);
+ if (io->u.pwdLastSet != old_last_set) {
+ return dsdb_module_werror(io->ac->module,
+ LDB_ERR_NO_SUCH_ATTRIBUTE,
+ WERR_DS_CANT_REM_MISSING_ATT_VAL,
+ "setup_last_set_field: old pwdLastSet "
+ "value not found!");
+ }
+ }
+
+ ret = ldb_msg_add_value(tmp_msg, "newval",
+ new_val, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ io->g.last_set = samdb_result_nttime(tmp_msg,
+ "newval",
+ 1);
+ } else if (ldb_msg_find_element(msg, "pwdLastSet")) {
+ ldb_set_errstring(ldb,
+ "'pwdLastSet' deletion is not allowed!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ } else if (io->ac->smartcard_reset) {
+ /*
+ * adding UF_SMARTCARD_REQUIRED doesn't update
+ * pwdLastSet implicitly.
+ */
+ io->ac->update_lastset = false;
+ }
+
+ /* only 0 or -1 (0xFFFFFFFFFFFFFFFF) are allowed */
+ switch (io->g.last_set) {
+ case 0:
+ if (!io->ac->pwd_last_set_default) {
+ break;
+ }
+ if (!io->ac->update_password) {
+ break;
+ }
+ FALL_THROUGH;
+ case UINT64_MAX:
+ if (!io->ac->update_password &&
+ io->u.pwdLastSet != 0 &&
+ io->u.pwdLastSet != UINT64_MAX)
+ {
+ /*
+ * Just setting pwdLastSet to -1, while not changing
+ * any password field has no effect if pwdLastSet
+ * is already non-zero.
+ */
+ io->ac->update_lastset = false;
+ break;
+ }
+ /* -1 means set it as now */
+ GetTimeOfDay(&tv);
+ io->g.last_set = timeval_to_nttime(&tv);
+ break;
+ default:
+ return dsdb_module_werror(io->ac->module,
+ LDB_ERR_OTHER,
+ WERR_INVALID_PARAMETER,
+ "setup_last_set_field: "
+ "pwdLastSet must be 0 or -1 only!");
+ }
+
+ if (io->ac->req->operation == LDB_ADD) {
+ /*
+ * We always need to store the value on add
+ * operations.
+ */
+ return LDB_SUCCESS;
+ }
+
+ if (io->g.last_set == io->u.pwdLastSet) {
+ /*
+ * Just setting pwdLastSet to 0, is no-op if it's already 0.
+ */
+ io->ac->update_lastset = false;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_given_passwords(struct setup_password_fields_io *io,
+ struct setup_password_fields_given *g)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+
+ if (g->cleartext_utf8) {
+ struct ldb_val *cleartext_utf16_blob;
+
+ cleartext_utf16_blob = talloc(io->ac, struct ldb_val);
+ if (!cleartext_utf16_blob) {
+ return ldb_oom(ldb);
+ }
+ if (!convert_string_talloc(io->ac,
+ CH_UTF8, CH_UTF16,
+ g->cleartext_utf8->data,
+ g->cleartext_utf8->length,
+ (void *)&cleartext_utf16_blob->data,
+ &cleartext_utf16_blob->length)) {
+ if (g->cleartext_utf8->length != 0) {
+ talloc_free(cleartext_utf16_blob);
+ ldb_asprintf_errstring(ldb,
+ "setup_password_fields: "
+ "failed to generate UTF16 password from cleartext UTF8 one for user '%s'!",
+ io->u.sAMAccountName);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ } else {
+ /* passwords with length "0" are valid! */
+ cleartext_utf16_blob->data = NULL;
+ cleartext_utf16_blob->length = 0;
+ }
+ }
+ g->cleartext_utf16 = cleartext_utf16_blob;
+ } else if (g->cleartext_utf16) {
+ struct ldb_val *cleartext_utf8_blob;
+
+ cleartext_utf8_blob = talloc(io->ac, struct ldb_val);
+ if (!cleartext_utf8_blob) {
+ return ldb_oom(ldb);
+ }
+ if (!convert_string_talloc(io->ac,
+ CH_UTF16MUNGED, CH_UTF8,
+ g->cleartext_utf16->data,
+ g->cleartext_utf16->length,
+ (void *)&cleartext_utf8_blob->data,
+ &cleartext_utf8_blob->length)) {
+ if (g->cleartext_utf16->length != 0) {
+ /* We must bail out here, the input wasn't even
+ * a multiple of 2 bytes */
+ talloc_free(cleartext_utf8_blob);
+ ldb_asprintf_errstring(ldb,
+ "setup_password_fields: "
+ "failed to generate UTF8 password from cleartext UTF 16 one for user '%s' - the latter had odd length (length must be a multiple of 2)!",
+ io->u.sAMAccountName);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ } else {
+ /* passwords with length "0" are valid! */
+ cleartext_utf8_blob->data = NULL;
+ cleartext_utf8_blob->length = 0;
+ }
+ }
+ g->cleartext_utf8 = cleartext_utf8_blob;
+ }
+
+ if (g->cleartext_utf16) {
+ struct samr_Password *nt_hash;
+
+ nt_hash = talloc(io->ac, struct samr_Password);
+ if (!nt_hash) {
+ return ldb_oom(ldb);
+ }
+ g->nt_hash = nt_hash;
+
+ /* compute the new nt hash */
+ mdfour(nt_hash->hash,
+ g->cleartext_utf16->data,
+ g->cleartext_utf16->length);
+ }
+
+ /*
+ * We need to build one more hash, so we can compare with what might
+ * have been stored in the old password (for the LDAP password change)
+ *
+ * We don't have any old salts, so we won't catch password reuse if said
+ * password was used prior to an account rename and another password
+ * change.
+ *
+ * We don't have to store the 'opaque' (string2key iterations)
+ * as Heimdal doesn't allow that to be changed.
+ */
+ if (g->cleartext_utf8 != NULL) {
+ int ret = setup_kerberos_key_hash(io, g);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_password_fields(struct setup_password_fields_io *io)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+ int ret;
+
+ ret = setup_last_set_field(io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (!io->ac->update_password) {
+ return LDB_SUCCESS;
+ }
+
+ if (io->u.is_krbtgt) {
+ size_t min = 196;
+ size_t max = 255;
+ size_t diff = max - min;
+ size_t len = max;
+ struct ldb_val *krbtgt_utf16 = NULL;
+
+ if (!io->ac->pwd_reset) {
+ return dsdb_module_werror(io->ac->module,
+ LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS,
+ WERR_DS_ATT_ALREADY_EXISTS,
+ "Password change on krbtgt not permitted!");
+ }
+
+ if (io->n.cleartext_utf16 == NULL) {
+ return dsdb_module_werror(io->ac->module,
+ LDB_ERR_UNWILLING_TO_PERFORM,
+ WERR_DS_INVALID_ATTRIBUTE_SYNTAX,
+ "Password reset on krbtgt requires UTF16!");
+ }
+
+ /*
+ * Instead of taking the callers value,
+ * we just generate a new random value here.
+ *
+ * Include null termination in the array.
+ */
+ if (diff > 0) {
+ size_t tmp;
+
+ generate_random_buffer((uint8_t *)&tmp, sizeof(tmp));
+
+ tmp %= diff;
+
+ len = min + tmp;
+ }
+
+ krbtgt_utf16 = talloc_zero(io->ac, struct ldb_val);
+ if (krbtgt_utf16 == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ *krbtgt_utf16 = data_blob_talloc_zero(krbtgt_utf16,
+ (len+1)*2);
+ if (krbtgt_utf16->data == NULL) {
+ return ldb_oom(ldb);
+ }
+ krbtgt_utf16->length = len * 2;
+ generate_secret_buffer(krbtgt_utf16->data,
+ krbtgt_utf16->length);
+ io->n.cleartext_utf16 = krbtgt_utf16;
+ }
+
+ /* transform the old password (for password changes) */
+ ret = setup_given_passwords(io, &io->og);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* transform the new password */
+ ret = setup_given_passwords(io, &io->n);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (io->n.cleartext_utf8) {
+ ret = setup_kerberos_keys(io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /*
+ * This relies on setup_kerberos_keys to make a NT-hash-like
+ * value for password history purposes
+ */
+
+ ret = setup_nt_fields(io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = setup_supplemental_field(io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_smartcard_reset(struct setup_password_fields_io *io)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+ struct supplementalCredentialsBlob scb = { .__ndr_size = 0 };
+ enum ndr_err_code ndr_err;
+
+ if (!io->ac->smartcard_reset) {
+ return LDB_SUCCESS;
+ }
+
+ io->g.nt_hash = talloc(io->ac, struct samr_Password);
+ if (io->g.nt_hash == NULL) {
+ return ldb_module_oom(io->ac->module);
+ }
+ generate_secret_buffer(io->g.nt_hash->hash,
+ sizeof(io->g.nt_hash->hash));
+ io->g.nt_history_len = 0;
+
+ /*
+ * We take the "old" value and store it
+ * with num_packages = 0.
+ *
+ * On "add" we have scb.sub.signature == 0, which
+ * results in:
+ *
+ * [0000] 00 00 00 00 00 00 00 00 00 00 00 00 00
+ *
+ * On modify it's likely to be scb.sub.signature ==
+ * SUPPLEMENTAL_CREDENTIALS_SIGNATURE (0x0050), which results in
+ * something like:
+ *
+ * [0000] 00 00 00 00 62 00 00 00 00 00 00 00 20 00 20 00
+ * [0010] 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00
+ * [0020] 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00
+ * [0030] 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00
+ * [0040] 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00
+ * [0050] 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00
+ * [0060] 20 00 20 00 20 00 20 00 20 00 20 00 50 00 00
+ *
+ * See https://bugzilla.samba.org/show_bug.cgi?id=11441
+ * and ndr_{push,pull}_supplementalCredentialsSubBlob().
+ */
+ scb = io->o.scb;
+ scb.sub.num_packages = 0;
+
+ /*
+ * setup 'supplementalCredentials' value without packages
+ */
+ ndr_err = ndr_push_struct_blob(&io->g.supplemental, io->ac,
+ &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(ldb,
+ "setup_smartcard_reset: "
+ "failed to push supplementalCredentialsBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ io->ac->update_password = true;
+ return LDB_SUCCESS;
+}
+
+static int make_error_and_update_badPwdCount(struct setup_password_fields_io *io, WERROR *werror)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+ struct ldb_message *mod_msg = NULL;
+ struct ldb_message *pso_msg = NULL;
+ struct ldb_message *current = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ int ret; /* The errors we will actually return */
+ int dbg_ret; /* The errors we can only complain about in logs */
+
+ /*
+ * OK, horrible semantics ahead.
+ *
+ * - We need to abort any existing transaction
+ * - create a transaction arround the badPwdCount update
+ * - re-open the transaction so the upper layer
+ * doesn't know what happened.
+ *
+ * This is needed because returning an error to the upper
+ * layer will cancel the transaction and undo the badPwdCount
+ * update.
+ */
+
+ /*
+ * Checking errors here is a bit pointless.
+ * What can we do if we can't end the transaction?
+ */
+ dbg_ret = ldb_next_del_trans(io->ac->module);
+ if (dbg_ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_FATAL,
+ "Failed to abort transaction prior to update of badPwdCount of %s: %s",
+ ldb_dn_get_linearized(io->ac->search_res->message->dn),
+ ldb_errstring(ldb));
+ /*
+ * just return the original error
+ */
+ goto done;
+ }
+
+ /* Likewise, what should we do if we can't open a new transaction? */
+ dbg_ret = ldb_next_start_trans(io->ac->module);
+ if (dbg_ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Failed to open transaction to update badPwdCount of %s: %s",
+ ldb_dn_get_linearized(io->ac->search_res->message->dn),
+ ldb_errstring(ldb));
+ /*
+ * just return the original error
+ */
+ goto done;
+ }
+
+ /*
+ * Re-read the account details, using the GUID in case the DN
+ * is being changed.
+ */
+ status = authsam_reread_user_logon_data(
+ ldb, io->ac,
+ io->ac->search_res->message,
+ &current);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* The re-read can return account locked out, as well
+ * as an internal error
+ */
+ goto end_transaction;
+ }
+
+ /* PSO search result is optional (NULL if no PSO applies) */
+ if (io->ac->pso_res != NULL) {
+ pso_msg = io->ac->pso_res->message;
+ }
+
+ status = dsdb_update_bad_pwd_count(io->ac, ldb,
+ current,
+ io->ac->dom_res->message,
+ pso_msg,
+ &mod_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto end_transaction;
+ }
+
+ if (mod_msg == NULL) {
+ goto end_transaction;
+ }
+
+ dbg_ret = dsdb_module_modify(io->ac->module, mod_msg,
+ DSDB_FLAG_NEXT_MODULE,
+ io->ac->req);
+ if (dbg_ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Failed to update badPwdCount of %s: %s",
+ ldb_dn_get_linearized(io->ac->search_res->message->dn),
+ ldb_errstring(ldb));
+ /*
+ * We can only ignore this...
+ */
+ }
+
+end_transaction:
+ dbg_ret = ldb_next_end_trans(io->ac->module);
+ if (dbg_ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Failed to close transaction to update badPwdCount of %s: %s",
+ ldb_dn_get_linearized(io->ac->search_res->message->dn),
+ ldb_errstring(ldb));
+ /*
+ * We can only ignore this...
+ */
+ }
+
+ dbg_ret = ldb_next_start_trans(io->ac->module);
+ if (dbg_ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "Failed to open transaction after update of badPwdCount of %s: %s",
+ ldb_dn_get_linearized(io->ac->search_res->message->dn),
+ ldb_errstring(ldb));
+ /*
+ * We can only ignore this...
+ */
+ }
+
+done:
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)) {
+ *werror = WERR_ACCOUNT_LOCKED_OUT;
+ } else {
+ *werror = WERR_INVALID_PASSWORD;
+ }
+ ldb_asprintf_errstring(ldb,
+ "%08X: %s - check_password_restrictions: "
+ "The old password specified doesn't match!",
+ W_ERROR_V(*werror),
+ ldb_strerror(ret));
+ return ret;
+}
+
+static int check_password_restrictions(struct setup_password_fields_io *io, WERROR *werror)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+ int ret;
+ uint32_t i;
+ struct loadparm_context *lp_ctx =
+ talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+
+ *werror = WERR_INVALID_PARAMETER;
+
+ if (!io->ac->update_password) {
+ return LDB_SUCCESS;
+ }
+
+ /*
+ * First check the old password is correct, for password
+ * changes when this hasn't already been checked by a
+ * trustwrothy layer above
+ */
+ if (!io->ac->pwd_reset && !(io->ac->change
+ && io->ac->change->old_password_checked == DSDB_PASSWORD_CHECKED_AND_CORRECT)) {
+ bool hash_checked = false;
+ /*
+ * we need the old nt hash given by the client (this
+ * is for the plaintext over LDAP password change,
+ * Kpasswd and SAMR supply the control)
+ */
+ if (io->og.nt_hash == NULL && io->og.aes_256.length == 0) {
+ ldb_asprintf_errstring(ldb,
+ "check_password_restrictions: "
+ "You need to provide the old password in order "
+ "to change it!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /*
+ * First compare the ENCTYPE_AES256_CTS_HMAC_SHA1_96 password and see if we have a match
+ */
+
+ if (io->og.aes_256.length > 0 && io->o.aes_256.length) {
+ hash_checked = data_blob_equal_const_time(&io->og.aes_256, &io->o.aes_256);
+ }
+
+ /* The password modify through the NT hash is encouraged and
+ has no problems at all */
+ if (!hash_checked && io->og.nt_hash && io->o.nt_hash) {
+ hash_checked = mem_equal_const_time(io->og.nt_hash->hash, io->o.nt_hash->hash, 16);
+ }
+
+ if (!hash_checked) {
+ return make_error_and_update_badPwdCount(io, werror);
+ }
+ }
+
+ if (io->u.restrictions == 0) {
+ /* FIXME: Is this right? */
+ return LDB_SUCCESS;
+ }
+
+ /* Password minimum age: yes, this is a minus. The ages are in negative 100nsec units! */
+ if ((io->u.pwdLastSet - io->ac->status->domain_data.minPwdAge > io->g.last_set) &&
+ !io->ac->pwd_reset)
+ {
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ *werror = WERR_PASSWORD_RESTRICTION;
+ ldb_asprintf_errstring(ldb,
+ "%08X: %s - check_password_restrictions: "
+ "password is too young to change!",
+ W_ERROR_V(*werror),
+ ldb_strerror(ret));
+ return ret;
+ }
+
+ /*
+ * Fundamental password checks done by the call
+ * "samdb_check_password".
+ * It is also in use by "dcesrv_samr_ValidatePassword".
+ */
+ if (io->n.cleartext_utf8 != NULL) {
+ enum samr_ValidationStatus vstat;
+ vstat = samdb_check_password(io->ac, lp_ctx,
+ io->u.sAMAccountName,
+ io->u.user_principal_name,
+ io->u.displayName,
+ io->n.cleartext_utf8,
+ io->ac->status->domain_data.pwdProperties,
+ io->ac->status->domain_data.minPwdLength);
+ switch (vstat) {
+ case SAMR_VALIDATION_STATUS_SUCCESS:
+ /* perfect -> proceed! */
+ break;
+
+ case SAMR_VALIDATION_STATUS_PWD_TOO_SHORT:
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ *werror = WERR_PASSWORD_RESTRICTION;
+ ldb_asprintf_errstring(ldb,
+ "%08X: %s - check_password_restrictions: "
+ "the password is too short. It should be equal or longer than %u characters!",
+ W_ERROR_V(*werror),
+ ldb_strerror(ret),
+ io->ac->status->domain_data.minPwdLength);
+ io->ac->status->reject_reason = SAM_PWD_CHANGE_PASSWORD_TOO_SHORT;
+ return ret;
+
+ case SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH:
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ *werror = WERR_PASSWORD_RESTRICTION;
+ ldb_asprintf_errstring(ldb,
+ "%08X: %s - check_password_restrictions: "
+ "the password does not meet the complexity criteria!",
+ W_ERROR_V(*werror),
+ ldb_strerror(ret));
+ io->ac->status->reject_reason = SAM_PWD_CHANGE_NOT_COMPLEX;
+ return ret;
+
+ default:
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ *werror = WERR_PASSWORD_RESTRICTION;
+ ldb_asprintf_errstring(ldb,
+ "%08X: %s - check_password_restrictions: "
+ "the password doesn't fit due to a miscellaneous restriction!",
+ W_ERROR_V(*werror),
+ ldb_strerror(ret));
+ return ret;
+ }
+ }
+
+ if (io->ac->pwd_reset) {
+ *werror = WERR_OK;
+ return LDB_SUCCESS;
+ }
+
+ /*
+ * This check works by using the current Kerberos password to
+ * make up a password history. We already did the salted hash
+ * creation to pass the password change check.
+ *
+ * We check the pwdHistoryLength to ensure we honour the
+ * policy on if the history should be checked
+ */
+ if (io->ac->status->domain_data.pwdHistoryLength > 0
+ && io->g.aes_256.length && io->o.aes_256.length)
+ {
+ bool equal = data_blob_equal_const_time(&io->g.aes_256,
+ &io->o.aes_256);
+ if (equal) {
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ *werror = WERR_PASSWORD_RESTRICTION;
+ ldb_asprintf_errstring(ldb,
+ "%08X: %s - check_password_restrictions: "
+ "the password was already used (previous password)!",
+ W_ERROR_V(*werror),
+ ldb_strerror(ret));
+ io->ac->status->reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY;
+ return ret;
+ }
+ }
+
+ if (io->n.nt_hash) {
+ /*
+ * checks the NT hash password history, against the
+ * generated NT hash
+ */
+ for (i = 0; i < io->o.nt_history_len; i++) {
+ bool pw_cmp = mem_equal_const_time(io->n.nt_hash, io->o.nt_history[i].hash, 16);
+ if (pw_cmp) {
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ *werror = WERR_PASSWORD_RESTRICTION;
+ ldb_asprintf_errstring(ldb,
+ "%08X: %s - check_password_restrictions: "
+ "the password was already used (in history)!",
+ W_ERROR_V(*werror),
+ ldb_strerror(ret));
+ io->ac->status->reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY;
+ return ret;
+ }
+ }
+ }
+
+ /*
+ * This check works by using the old Kerberos passwords
+ * (old and older) to make up a password history.
+ *
+ * We check the pwdHistoryLength to ensure we honour the
+ * policy on if the history should be checked
+ */
+ for (i = 1;
+ i <= io->o.kvno && i < MIN(3, io->ac->status->domain_data.pwdHistoryLength);
+ i++)
+ {
+ krb5_error_code krb5_ret;
+ const uint32_t request_kvno = io->o.kvno - i;
+ DATA_BLOB db_key_blob;
+ bool pw_equal;
+
+ if (io->n.cleartext_utf8 == NULL) {
+ /*
+ * No point checking history if we don't have
+ * a cleartext password.
+ */
+ break;
+ }
+
+ if (io->ac->search_res == NULL) {
+ /*
+ * This is an ADD, no existing history to check
+ */
+ break;
+ }
+
+ /*
+ * If this account requires a smartcard for login, we don't
+ * attempt a comparison with the old password.
+ */
+ if (io->u.userAccountControl & UF_SMARTCARD_REQUIRED) {
+ break;
+ }
+
+ /*
+ * Extract the old ENCTYPE_AES256_CTS_HMAC_SHA1_96 value from
+ * the supplementalCredentials.
+ */
+ krb5_ret = dsdb_extract_aes_256_key(io->smb_krb5_context->krb5_context,
+ io->ac,
+ io->ac->search_res->message,
+ io->u.userAccountControl,
+ &request_kvno, /* kvno */
+ NULL, /* kvno_out */
+ &db_key_blob,
+ NULL); /* salt */
+ if (krb5_ret == ENOENT) {
+ /*
+ * If there is no old AES hash (perhaps an imported DB with
+ * just unicodePwd) then we just wont have an old
+ * password to compare to if there is no NT hash
+ */
+ break;
+ } else if (krb5_ret) {
+ ldb_asprintf_errstring(ldb,
+ "check_password_restrictions: "
+ "extraction of old[%u - %d = %d] aes256-cts-hmac-sha1-96 key failed: %s",
+ io->o.kvno, i, io->o.kvno - i,
+ smb_get_krb5_error_message(io->smb_krb5_context->krb5_context,
+ krb5_ret, io->ac));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* This is the actual history check */
+ pw_equal = data_blob_equal_const_time(&io->n.aes_256,
+ &db_key_blob);
+ if (pw_equal) {
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ *werror = WERR_PASSWORD_RESTRICTION;
+ ldb_asprintf_errstring(ldb,
+ "%08X: %s - check_password_restrictions: "
+ "the password was already used (in history)!",
+ W_ERROR_V(*werror),
+ ldb_strerror(ret));
+ io->ac->status->reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY;
+ return ret;
+ }
+ }
+
+ /* are all password changes disallowed? */
+ if (io->ac->status->domain_data.pwdProperties & DOMAIN_REFUSE_PASSWORD_CHANGE) {
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ *werror = WERR_PASSWORD_RESTRICTION;
+ ldb_asprintf_errstring(ldb,
+ "%08X: %s - check_password_restrictions: "
+ "password changes disabled!",
+ W_ERROR_V(*werror),
+ ldb_strerror(ret));
+ return ret;
+ }
+
+ /* can this user change the password? */
+ if (io->u.userAccountControl & UF_PASSWD_CANT_CHANGE) {
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ *werror = WERR_PASSWORD_RESTRICTION;
+ ldb_asprintf_errstring(ldb,
+ "%08X: %s - check_password_restrictions: "
+ "password can't be changed on this account!",
+ W_ERROR_V(*werror),
+ ldb_strerror(ret));
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int check_password_restrictions_and_log(struct setup_password_fields_io *io)
+{
+ WERROR werror;
+ int ret = check_password_restrictions(io, &werror);
+ struct ph_context *ac = io->ac;
+ /*
+ * Password resets are not authentication events, and if the
+ * upper layer checked the password and supplied the hash
+ * values as proof, then this is also not an authentication
+ * even at this layer (already logged). This is to log LDAP
+ * password changes.
+ */
+
+ /* Do not record a failure in the auth log below in the success case */
+ if (ret == LDB_SUCCESS) {
+ werror = WERR_OK;
+ }
+
+ if (ac->pwd_reset == false && ac->change == NULL) {
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct imessaging_context *msg_ctx;
+ struct loadparm_context *lp_ctx
+ = talloc_get_type_abort(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+ NTSTATUS status = werror_to_ntstatus(werror);
+ const char *domain_name = lpcfg_sam_name(lp_ctx);
+ void *opaque_remote_address = NULL;
+ /*
+ * Forcing this via the NTLM auth structure is not ideal, but
+ * it is the most practical option right now, and ensures the
+ * logs are consistent, even if some elements are always NULL.
+ */
+ struct auth_usersupplied_info ui = {
+ .was_mapped = true,
+ .client = {
+ .account_name = io->u.sAMAccountName,
+ .domain_name = domain_name,
+ },
+ .mapped = {
+ .account_name = io->u.sAMAccountName,
+ .domain_name = domain_name,
+ },
+ .service_description = "LDAP Password Change",
+ .auth_description = "LDAP Modify",
+ .password_type = "plaintext"
+ };
+
+ opaque_remote_address = ldb_get_opaque(ldb,
+ "remoteAddress");
+ if (opaque_remote_address == NULL) {
+ ldb_asprintf_errstring(ldb,
+ "Failed to obtain remote address for "
+ "the LDAP client while changing the "
+ "password");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ui.remote_host = talloc_get_type(opaque_remote_address,
+ struct tsocket_address);
+
+ msg_ctx = imessaging_client_init(ac, lp_ctx,
+ ldb_get_event_context(ldb));
+ if (!msg_ctx) {
+ ldb_asprintf_errstring(ldb,
+ "Failed to generate client messaging context in %s",
+ lpcfg_imessaging_path(ac, lp_ctx));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ log_authentication_event(msg_ctx,
+ lp_ctx,
+ NULL,
+ &ui,
+ status,
+ domain_name,
+ io->u.sAMAccountName,
+ io->u.account_sid);
+
+ }
+ return ret;
+}
+
+static int update_final_msg(struct setup_password_fields_io *io)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(io->ac->module);
+ int ret;
+ int el_flags = 0;
+ bool update_password = io->ac->update_password;
+ bool update_scb = io->ac->update_password;
+
+ /*
+ * If we add a user without initial password,
+ * we need to add replication meta data for
+ * following attributes:
+ * - unicodePwd
+ * - dBCSPwd
+ * - ntPwdHistory
+ * - lmPwdHistory
+ *
+ * If we add a user with initial password or a
+ * password is changed of an existing user,
+ * we need to replace the following attributes
+ * with a forced meta data update, e.g. also
+ * when updating an empty attribute with an empty value:
+ * - unicodePwd
+ * - dBCSPwd
+ * - ntPwdHistory
+ * - lmPwdHistory
+ * - supplementalCredentials
+ */
+
+ switch (io->ac->req->operation) {
+ case LDB_ADD:
+ update_password = true;
+ el_flags |= DSDB_FLAG_INTERNAL_FORCE_META_DATA;
+ break;
+ case LDB_MODIFY:
+ el_flags |= LDB_FLAG_MOD_REPLACE;
+ el_flags |= DSDB_FLAG_INTERNAL_FORCE_META_DATA;
+ break;
+ default:
+ return ldb_module_operr(io->ac->module);
+ }
+
+ if (update_password) {
+ ret = ldb_msg_add_empty(io->ac->update_msg,
+ "unicodePwd",
+ el_flags, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ * This wipes any old LM password after any password
+ * update operation.
+ *
+ * This is the same as the previous default behaviour
+ * of 'lanman auth = no'
+ */
+ ret = ldb_msg_add_empty(io->ac->update_msg,
+ "dBCSPwd",
+ el_flags, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = ldb_msg_add_empty(io->ac->update_msg,
+ "ntPwdHistory",
+ el_flags, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ /*
+ * This wipes any LM password history after any password
+ * update operation.
+ *
+ * This is the same as the previous default behaviour
+ * of 'lanman auth = no'
+ */
+ ret = ldb_msg_add_empty(io->ac->update_msg,
+ "lmPwdHistory",
+ el_flags, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (update_scb) {
+ ret = ldb_msg_add_empty(io->ac->update_msg,
+ "supplementalCredentials",
+ el_flags, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io->ac->update_lastset) {
+ ret = ldb_msg_add_empty(io->ac->update_msg,
+ "pwdLastSet",
+ el_flags, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (io->g.nt_hash != NULL) {
+ ret = samdb_msg_add_hash(ldb, io->ac,
+ io->ac->update_msg,
+ "unicodePwd",
+ io->g.nt_hash);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (io->g.nt_history_len > 0) {
+ ret = samdb_msg_add_hashes(ldb, io->ac,
+ io->ac->update_msg,
+ "ntPwdHistory",
+ io->g.nt_history,
+ io->g.nt_history_len);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io->g.supplemental.length > 0) {
+ ret = ldb_msg_add_value(io->ac->update_msg,
+ "supplementalCredentials",
+ &io->g.supplemental, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (io->ac->update_lastset) {
+ ret = samdb_msg_add_uint64(ldb, io->ac,
+ io->ac->update_msg,
+ "pwdLastSet",
+ io->g.last_set);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * This is intended for use by the "password_hash" module since there
+ * password changes can be specified through one message element with the
+ * new password (to set) and another one with the old password (to unset).
+ *
+ * The first which sets a password (new value) can have flags
+ * (LDB_FLAG_MOD_ADD, LDB_FLAG_MOD_REPLACE) but also none (on "add" operations
+ * for entries). The latter (old value) has always specified
+ * LDB_FLAG_MOD_DELETE.
+ *
+ * Returns LDB_ERR_CONSTRAINT_VIOLATION and LDB_ERR_UNWILLING_TO_PERFORM if
+ * matching message elements are malformed in respect to the set/change rules.
+ * Otherwise it returns LDB_SUCCESS.
+ */
+static int msg_find_old_and_new_pwd_val(const struct ldb_message *msg,
+ const char *name,
+ enum ldb_request_type operation,
+ const struct ldb_val **new_val,
+ const struct ldb_val **old_val)
+{
+ unsigned int i;
+
+ *new_val = NULL;
+ *old_val = NULL;
+
+ if (msg == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ for (i = 0; i < msg->num_elements; i++) {
+ if (ldb_attr_cmp(msg->elements[i].name, name) != 0) {
+ continue;
+ }
+
+ if ((operation == LDB_MODIFY) &&
+ (LDB_FLAG_MOD_TYPE(msg->elements[i].flags) == LDB_FLAG_MOD_DELETE)) {
+ /* 0 values are allowed */
+ if (msg->elements[i].num_values == 1) {
+ *old_val = &msg->elements[i].values[0];
+ } else if (msg->elements[i].num_values > 1) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ } else if ((operation == LDB_MODIFY) &&
+ (LDB_FLAG_MOD_TYPE(msg->elements[i].flags) == LDB_FLAG_MOD_REPLACE)) {
+ if (msg->elements[i].num_values > 0) {
+ *new_val = &msg->elements[i].values[msg->elements[i].num_values - 1];
+ } else {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ } else {
+ /* Add operations and LDB_FLAG_MOD_ADD */
+ if (msg->elements[i].num_values > 0) {
+ *new_val = &msg->elements[i].values[msg->elements[i].num_values - 1];
+ } else {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int setup_io(struct ph_context *ac,
+ const struct ldb_message *client_msg,
+ const struct ldb_message *existing_msg,
+ struct setup_password_fields_io *io)
+{
+ const struct ldb_val *quoted_utf16, *old_quoted_utf16, *lm_hash, *old_lm_hash;
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct loadparm_context *lp_ctx = talloc_get_type(
+ ldb_get_opaque(ldb, "loadparm"), struct loadparm_context);
+ enum store_nt_hash store_hash_setting =
+ lpcfg_nt_hash_store(lp_ctx);
+ int ret;
+ const struct ldb_message *info_msg = NULL;
+ struct dom_sid *account_sid = NULL;
+ int rodc_krbtgt = 0;
+
+ ZERO_STRUCTP(io);
+
+ /* Some operations below require kerberos contexts */
+
+ if (existing_msg != NULL) {
+ /*
+ * This is a modify operation
+ */
+ info_msg = existing_msg;
+ } else {
+ /*
+ * This is an add operation
+ */
+ info_msg = client_msg;
+ }
+
+ ret = smb_krb5_init_context(ac,
+ (struct loadparm_context *)ldb_get_opaque(ldb, "loadparm"),
+ &io->smb_krb5_context);
+
+ if (ret != 0) {
+ /*
+ * In the special case of mit krb5.conf vs heimdal, the includedir
+ * statement causes ret == 22 (KRB5_CONFIG_BADFORMAT) to be returned.
+ * We look for this case so that we can give a more instructional
+ * message to the administrator.
+ */
+ if (ret == KRB5_CONFIG_BADFORMAT || ret == EINVAL) {
+ ldb_asprintf_errstring(ldb, "Failed to setup krb5_context: %s - "
+ "This could be due to an invalid krb5 configuration. "
+ "Please check your system's krb5 configuration is correct.",
+ error_message(ret));
+ } else {
+ ldb_asprintf_errstring(ldb, "Failed to setup krb5_context: %s",
+ error_message(ret));
+ }
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ io->ac = ac;
+
+ io->u.userAccountControl = ldb_msg_find_attr_as_uint(info_msg,
+ "userAccountControl", 0);
+ if (info_msg == existing_msg) {
+ /*
+ * We only take pwdLastSet from the existing object
+ * otherwise we leave it as 0.
+ *
+ * If no attribute is available, e.g. on deleted objects
+ * we remember that as UINT64_MAX.
+ */
+ io->u.pwdLastSet = samdb_result_nttime(info_msg, "pwdLastSet",
+ UINT64_MAX);
+ }
+ io->u.sAMAccountName = ldb_msg_find_attr_as_string(info_msg,
+ "sAMAccountName", NULL);
+ io->u.user_principal_name = ldb_msg_find_attr_as_string(info_msg,
+ "userPrincipalName", NULL);
+ io->u.displayName = ldb_msg_find_attr_as_string(info_msg,
+ "displayName", NULL);
+
+ /* Ensure it has an objectSID too */
+ io->u.account_sid = samdb_result_dom_sid(ac, info_msg, "objectSid");
+ if (io->u.account_sid != NULL) {
+ NTSTATUS status;
+ uint32_t rid = 0;
+
+ status = dom_sid_split_rid(account_sid, io->u.account_sid, NULL, &rid);
+ if (NT_STATUS_IS_OK(status)) {
+ if (rid == DOMAIN_RID_KRBTGT) {
+ io->u.is_krbtgt = true;
+ }
+ }
+ }
+
+ rodc_krbtgt = ldb_msg_find_attr_as_int(info_msg,
+ "msDS-SecondaryKrbTgtNumber", 0);
+ if (rodc_krbtgt != 0) {
+ io->u.is_krbtgt = true;
+ }
+
+ if (io->u.sAMAccountName == NULL) {
+ ldb_asprintf_errstring(ldb,
+ "setup_io: sAMAccountName attribute is missing on %s for attempted password set/change",
+ ldb_dn_get_linearized(info_msg->dn));
+
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ if (io->u.userAccountControl & UF_INTERDOMAIN_TRUST_ACCOUNT) {
+ struct ldb_control *permit_trust = ldb_request_get_control(ac->req,
+ DSDB_CONTROL_PERMIT_INTERDOMAIN_TRUST_UAC_OID);
+
+ if (permit_trust == NULL) {
+ ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ ldb_asprintf_errstring(ldb,
+ "%08X: %s - setup_io: changing the interdomain trust password "
+ "on %s not allowed via LDAP. Use LSA or NETLOGON",
+ W_ERROR_V(WERR_ACCESS_DENIED),
+ ldb_strerror(ret),
+ ldb_dn_get_linearized(info_msg->dn));
+ return ret;
+ }
+ }
+
+ /* Only non-trust accounts have restrictions (possibly this test is the
+ * wrong way around, but we like to be restrictive if possible */
+ io->u.restrictions = !(io->u.userAccountControl & UF_TRUST_ACCOUNT_MASK);
+
+ if (io->u.is_krbtgt) {
+ io->u.restrictions = 0;
+ io->ac->status->domain_data.pwdHistoryLength =
+ MAX(io->ac->status->domain_data.pwdHistoryLength, 3);
+ }
+
+ /*
+ * Machine accounts need the NT hash to operate the NETLOGON
+ * ServerAuthenticate{,2,3} logic
+ */
+ if (!(io->u.userAccountControl & UF_NORMAL_ACCOUNT)) {
+ store_hash_setting = NT_HASH_STORE_ALWAYS;
+ }
+
+ switch (store_hash_setting) {
+ case NT_HASH_STORE_ALWAYS:
+ io->u.store_nt_hash = true;
+ break;
+ case NT_HASH_STORE_NEVER:
+ io->u.store_nt_hash = false;
+ break;
+ case NT_HASH_STORE_AUTO:
+ if (lpcfg_ntlm_auth(lp_ctx) == NTLM_AUTH_DISABLED) {
+ io->u.store_nt_hash = false;
+ break;
+ }
+ io->u.store_nt_hash = true;
+ break;
+ }
+
+ if (ac->userPassword) {
+ ret = msg_find_old_and_new_pwd_val(client_msg, "userPassword",
+ ac->req->operation,
+ &io->n.cleartext_utf8,
+ &io->og.cleartext_utf8);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "it's only allowed to set the old password once!");
+ return ret;
+ }
+ }
+
+ if (io->n.cleartext_utf8 != NULL) {
+ struct ldb_val *cleartext_utf8_blob;
+ char *p;
+
+ cleartext_utf8_blob = talloc(io->ac, struct ldb_val);
+ if (!cleartext_utf8_blob) {
+ return ldb_oom(ldb);
+ }
+
+ *cleartext_utf8_blob = *io->n.cleartext_utf8;
+
+ /* make sure we have a null terminated string */
+ p = talloc_strndup(cleartext_utf8_blob,
+ (const char *)io->n.cleartext_utf8->data,
+ io->n.cleartext_utf8->length);
+ if ((p == NULL) && (io->n.cleartext_utf8->length > 0)) {
+ return ldb_oom(ldb);
+ }
+ cleartext_utf8_blob->data = (uint8_t *)p;
+
+ io->n.cleartext_utf8 = cleartext_utf8_blob;
+ }
+
+ ret = msg_find_old_and_new_pwd_val(client_msg, "clearTextPassword",
+ ac->req->operation,
+ &io->n.cleartext_utf16,
+ &io->og.cleartext_utf16);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "it's only allowed to set the old password once!");
+ return ret;
+ }
+
+ /* this rather strange looking piece of code is there to
+ handle a ldap client setting a password remotely using the
+ unicodePwd ldap field. The syntax is that the password is
+ in UTF-16LE, with a " at either end. Unfortunately the
+ unicodePwd field is also used to store the nt hashes
+ internally in Samba, and is used in the nt hash format on
+ the wire in DRS replication, so we have a single name for
+ two distinct values. The code below leaves us with a small
+ chance (less than 1 in 2^32) of a mixup, if someone manages
+ to create a MD4 hash which starts and ends in 0x22 0x00, as
+ that would then be treated as a UTF16 password rather than
+ a nthash */
+
+ ret = msg_find_old_and_new_pwd_val(client_msg, "unicodePwd",
+ ac->req->operation,
+ &quoted_utf16,
+ &old_quoted_utf16);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "it's only allowed to set the old password once!");
+ return ret;
+ }
+
+ /* Checks and converts the actual "unicodePwd" attribute */
+ if (!ac->hash_values &&
+ quoted_utf16 &&
+ quoted_utf16->length >= 4 &&
+ quoted_utf16->data[0] == '"' &&
+ quoted_utf16->data[1] == 0 &&
+ quoted_utf16->data[quoted_utf16->length-2] == '"' &&
+ quoted_utf16->data[quoted_utf16->length-1] == 0) {
+ struct ldb_val *quoted_utf16_2;
+
+ if (io->n.cleartext_utf16) {
+ /* refuse the change if someone wants to change with
+ with both UTF16 possibilities at the same time... */
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "it's only allowed to set the cleartext password as 'unicodePwd' or as 'clearTextPassword'");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /*
+ * adapt the quoted UTF16 string to be a real
+ * cleartext one
+ */
+ quoted_utf16_2 = talloc(io->ac, struct ldb_val);
+ if (quoted_utf16_2 == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ quoted_utf16_2->data = quoted_utf16->data + 2;
+ quoted_utf16_2->length = quoted_utf16->length-4;
+ io->n.cleartext_utf16 = quoted_utf16_2;
+ io->n.nt_hash = NULL;
+
+ } else if (quoted_utf16) {
+ /* We have only the hash available -> so no plaintext here */
+ if (!ac->hash_values) {
+ /* refuse the change if someone wants to change
+ the hash without control specified... */
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "it's not allowed to set the NT hash password directly'");
+ /* this looks odd but this is what Windows does:
+ returns "UNWILLING_TO_PERFORM" on wrong
+ password sets and "CONSTRAINT_VIOLATION" on
+ wrong password changes. */
+ if (old_quoted_utf16 == NULL) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ io->n.nt_hash = talloc(io->ac, struct samr_Password);
+ memcpy(io->n.nt_hash->hash, quoted_utf16->data,
+ MIN(quoted_utf16->length, sizeof(io->n.nt_hash->hash)));
+ }
+
+ /* Checks and converts the previous "unicodePwd" attribute */
+ if (!ac->hash_values &&
+ old_quoted_utf16 &&
+ old_quoted_utf16->length >= 4 &&
+ old_quoted_utf16->data[0] == '"' &&
+ old_quoted_utf16->data[1] == 0 &&
+ old_quoted_utf16->data[old_quoted_utf16->length-2] == '"' &&
+ old_quoted_utf16->data[old_quoted_utf16->length-1] == 0) {
+ struct ldb_val *old_quoted_utf16_2;
+
+ if (io->og.cleartext_utf16) {
+ /* refuse the change if someone wants to change with
+ both UTF16 possibilities at the same time... */
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "it's only allowed to set the cleartext password as 'unicodePwd' or as 'clearTextPassword'");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /*
+ * adapt the quoted UTF16 string to be a real
+ * cleartext one
+ */
+ old_quoted_utf16_2 = talloc(io->ac, struct ldb_val);
+ if (old_quoted_utf16_2 == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ old_quoted_utf16_2->data = old_quoted_utf16->data + 2;
+ old_quoted_utf16_2->length = old_quoted_utf16->length-4;
+
+ io->og.cleartext_utf16 = old_quoted_utf16_2;
+ io->og.nt_hash = NULL;
+ } else if (old_quoted_utf16) {
+ /* We have only the hash available -> so no plaintext here */
+ if (!ac->hash_values) {
+ /* refuse the change if someone wants to change
+ the hash without control specified... */
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "it's not allowed to set the NT hash password directly'");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ io->og.nt_hash = talloc(io->ac, struct samr_Password);
+ memcpy(io->og.nt_hash->hash, old_quoted_utf16->data,
+ MIN(old_quoted_utf16->length, sizeof(io->og.nt_hash->hash)));
+ }
+
+ /* Handles the "dBCSPwd" attribute (LM hash) */
+ ret = msg_find_old_and_new_pwd_val(client_msg, "dBCSPwd",
+ ac->req->operation,
+ &lm_hash, &old_lm_hash);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "it's only allowed to set the old password once!");
+ return ret;
+ }
+
+ if (((lm_hash != NULL) || (old_lm_hash != NULL))) {
+ /* refuse the change if someone wants to change the LM hash */
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "it's not allowed to set the LM hash password (dBCSPwd)'");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /*
+ * Handles the password change control if it's specified. It has the
+ * precedance and overrides already specified old password values of
+ * change requests (but that shouldn't happen since the control is
+ * fully internal and only used in conjunction with replace requests!).
+ */
+ if (ac->change != NULL) {
+ io->og.nt_hash = NULL;
+ }
+
+ /* refuse the change if someone wants to change the clear-
+ text and supply his own hashes at the same time... */
+ if ((io->n.cleartext_utf8 || io->n.cleartext_utf16)
+ && (io->n.nt_hash)) {
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "it's only allowed to set the password in form of cleartext attributes or as hashes");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* refuse the change if someone wants to change the password
+ using both plaintext methods (UTF8 and UTF16) at the same time... */
+ if (io->n.cleartext_utf8 && io->n.cleartext_utf16) {
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "it's only allowed to set the cleartext password as 'unicodePwd' or as 'userPassword' or as 'clearTextPassword'");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* refuse the change if someone tries to set/change the password by
+ * any method that would leave us without a password! */
+ if (io->ac->update_password
+ && (!io->n.cleartext_utf8) && (!io->n.cleartext_utf16)
+ && (!io->n.nt_hash)) {
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "It's not possible to delete the password (changes using the LAN Manager hash alone could be deactivated)!");
+ /* on "userPassword" and "clearTextPassword" we've to return
+ * something different, since these are virtual attributes */
+ if ((ldb_msg_find_element(client_msg, "userPassword") != NULL) ||
+ (ldb_msg_find_element(client_msg, "clearTextPassword") != NULL)) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /*
+ * refuse the change if someone wants to compare against a
+ * plaintext or dsdb_control_password_change at the same time
+ * for a "password modify" operation...
+ */
+ if ((io->og.cleartext_utf8 || io->og.cleartext_utf16)
+ && ac->change) {
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "it's only allowed to provide the old password in form of cleartext attributes or as the dsdb_control_password_change");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* refuse the change if someone wants to compare against both
+ * plaintexts at the same time for a "password modify" operation... */
+ if (io->og.cleartext_utf8 && io->og.cleartext_utf16) {
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "it's only allowed to provide the old cleartext password as 'unicodePwd' or as 'userPassword' or as 'clearTextPassword'");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Decides if we have a password modify or password reset operation */
+ if (ac->req->operation == LDB_ADD) {
+ /* On "add" we have only "password reset" */
+ ac->pwd_reset = true;
+ } else if (ac->req->operation == LDB_MODIFY) {
+ struct ldb_control *pav_ctrl = NULL;
+ struct dsdb_control_password_acl_validation *pav = NULL;
+
+ pav_ctrl = ldb_request_get_control(ac->req,
+ DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID);
+ if (pav_ctrl != NULL) {
+ pav = talloc_get_type_abort(pav_ctrl->data,
+ struct dsdb_control_password_acl_validation);
+ }
+
+ if (pav == NULL && ac->update_password) {
+ bool ok;
+
+ /*
+ * If the DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID
+ * control is missing, we require system access!
+ */
+ ok = dsdb_module_am_system(ac->module);
+ if (!ok) {
+ return ldb_module_operr(ac->module);
+ }
+ }
+
+ if (pav != NULL) {
+ /*
+ * We assume what the acl module has validated.
+ */
+ ac->pwd_reset = pav->pwd_reset;
+ } else if (io->og.cleartext_utf8 || io->og.cleartext_utf16
+ || ac->change) {
+ /*
+ * If we have an old password specified or the
+ * dsdb_control_password_change then for sure
+ * it is a user "password change"
+ */
+ ac->pwd_reset = false;
+ } else {
+ /* Otherwise we have also here a "password reset" */
+ ac->pwd_reset = true;
+ }
+ } else {
+ /* this shouldn't happen */
+ return ldb_operr(ldb);
+ }
+
+ if (existing_msg != NULL) {
+ NTSTATUS status;
+ krb5_error_code krb5_ret;
+ DATA_BLOB key_blob;
+ DATA_BLOB salt_blob;
+ uint32_t kvno;
+
+ if (ac->pwd_reset) {
+ /* Get the old password from the database */
+ status = samdb_result_passwords_no_lockout(ac,
+ lp_ctx,
+ existing_msg,
+ &io->o.nt_hash);
+ } else {
+ /* Get the old password from the database */
+ status = samdb_result_passwords(ac,
+ lp_ctx,
+ existing_msg,
+ &io->o.nt_hash);
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_LOCKED_OUT)) {
+ return dsdb_module_werror(ac->module,
+ LDB_ERR_CONSTRAINT_VIOLATION,
+ WERR_ACCOUNT_LOCKED_OUT,
+ "Password change not permitted,"
+ " account locked out!");
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * This only happens if the database has gone weird,
+ * not if we are just missing the passwords
+ */
+ return ldb_operr(ldb);
+ }
+
+ io->o.nt_history_len = samdb_result_hashes(ac, existing_msg,
+ "ntPwdHistory",
+ &io->o.nt_history);
+ io->o.supplemental = ldb_msg_find_ldb_val(existing_msg,
+ "supplementalCredentials");
+
+ if (io->o.supplemental != NULL) {
+ enum ndr_err_code ndr_err;
+
+ ndr_err = ndr_pull_struct_blob_all(io->o.supplemental, io->ac,
+ &io->o.scb,
+ (ndr_pull_flags_fn_t)ndr_pull_supplementalCredentialsBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ ldb_asprintf_errstring(ldb,
+ "setup_io: failed to pull "
+ "old supplementalCredentialsBlob: %s",
+ nt_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ /*
+ * If this account requires a smartcard for login, we don't
+ * attempt a comparison with the old password.
+ */
+ if (io->u.userAccountControl & UF_SMARTCARD_REQUIRED) {
+ return LDB_SUCCESS;
+ }
+
+ /*
+ * Extract the old ENCTYPE_AES256_CTS_HMAC_SHA1_96
+ * value from the supplementalCredentials.
+ */
+ krb5_ret = dsdb_extract_aes_256_key(io->smb_krb5_context->krb5_context,
+ io->ac,
+ existing_msg,
+ io->u.userAccountControl,
+ NULL, /* kvno */
+ &kvno, /* kvno_out */
+ &key_blob,
+ &salt_blob);
+ if (krb5_ret == ENOENT) {
+ /*
+ * If there is no old AES hash (perhaps an imported DB with
+ * just unicodePwd) then we just wont have an old
+ * password to compare to if there is no NT hash
+ */
+ return LDB_SUCCESS;
+ }
+ if (krb5_ret) {
+ ldb_asprintf_errstring(ldb,
+ "setup_io: "
+ "extraction of salt for old 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->o.salt = salt_blob;
+ io->o.aes_256 = key_blob;
+ io->o.kvno = kvno;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static struct ph_context *ph_init_context(struct ldb_module *module,
+ struct ldb_request *req,
+ bool userPassword,
+ bool update_password)
+{
+ struct ldb_context *ldb;
+ struct ph_context *ac;
+ struct loadparm_context *lp_ctx = NULL;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = talloc_zero(req, struct ph_context);
+ if (ac == NULL) {
+ ldb_set_errstring(ldb, "Out of Memory");
+ return NULL;
+ }
+
+ ac->module = module;
+ ac->req = req;
+ ac->userPassword = userPassword;
+ ac->update_password = update_password;
+ ac->update_lastset = true;
+
+ lp_ctx = talloc_get_type_abort(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+ ac->gpg_key_ids = lpcfg_password_hash_gpg_key_ids(lp_ctx);
+ ac->userPassword_schemes
+ = lpcfg_password_hash_userpassword_schemes(lp_ctx);
+ return ac;
+}
+
+static void ph_apply_controls(struct ph_context *ac)
+{
+ struct ldb_control *ctrl;
+
+ ac->change_status = false;
+ ctrl = ldb_request_get_control(ac->req,
+ DSDB_CONTROL_PASSWORD_CHANGE_STATUS_OID);
+ if (ctrl != NULL) {
+ ac->change_status = true;
+
+ /* Mark the "change status" control as uncritical (done) */
+ ctrl->critical = false;
+ }
+
+ ac->hash_values = false;
+ ctrl = ldb_request_get_control(ac->req,
+ DSDB_CONTROL_PASSWORD_HASH_VALUES_OID);
+ if (ctrl != NULL) {
+ ac->hash_values = true;
+
+ /* Mark the "hash values" control as uncritical (done) */
+ ctrl->critical = false;
+ }
+
+ ctrl = ldb_request_get_control(ac->req,
+ DSDB_CONTROL_PASSWORD_CHANGE_OLD_PW_CHECKED_OID);
+ if (ctrl != NULL) {
+ ac->change = talloc_get_type_abort(ctrl->data, struct dsdb_control_password_change);
+
+ /* Mark the "change" control as uncritical (done) */
+ ctrl->critical = false;
+ }
+
+ ac->pwd_last_set_bypass = false;
+ ctrl = ldb_request_get_control(ac->req,
+ DSDB_CONTROL_PASSWORD_BYPASS_LAST_SET_OID);
+ if (ctrl != NULL) {
+ ac->pwd_last_set_bypass = true;
+
+ /* Mark the "bypass pwdLastSet" control as uncritical (done) */
+ ctrl->critical = false;
+ }
+
+ ac->pwd_last_set_default = false;
+ ctrl = ldb_request_get_control(ac->req,
+ DSDB_CONTROL_PASSWORD_DEFAULT_LAST_SET_OID);
+ if (ctrl != NULL) {
+ ac->pwd_last_set_default = true;
+
+ /* Mark the "bypass pwdLastSet" control as uncritical (done) */
+ ctrl->critical = false;
+ }
+
+ ac->smartcard_reset = false;
+ ctrl = ldb_request_get_control(ac->req,
+ DSDB_CONTROL_PASSWORD_USER_ACCOUNT_CONTROL_OID);
+ if (ctrl != NULL) {
+ struct dsdb_control_password_user_account_control *uac = NULL;
+ uint32_t added_flags = 0;
+
+ uac = talloc_get_type_abort(ctrl->data,
+ struct dsdb_control_password_user_account_control);
+
+ added_flags = uac->new_flags & ~uac->old_flags;
+
+ if (added_flags & UF_SMARTCARD_REQUIRED) {
+ ac->smartcard_reset = true;
+ }
+
+ /* Mark the "smartcard required" control as uncritical (done) */
+ ctrl->critical = false;
+ }
+}
+
+static int ph_op_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct ph_context *ac;
+
+ ac = talloc_get_type(req->context, struct ph_context);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ares->type == LDB_REPLY_REFERRAL) {
+ return ldb_module_send_referral(ac->req, ares->referral);
+ }
+
+ if ((ares->error != LDB_ERR_OPERATIONS_ERROR) && (ac->change_status)) {
+ /* On success and trivial errors a status control is being
+ * added (used for example by the "samdb_set_password" call) */
+ ldb_reply_add_control(ares,
+ DSDB_CONTROL_PASSWORD_CHANGE_STATUS_OID,
+ false,
+ ac->status);
+ }
+
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+}
+
+static int password_hash_add_do_add(struct ph_context *ac);
+static int ph_modify_callback(struct ldb_request *req, struct ldb_reply *ares);
+static int password_hash_mod_search_self(struct ph_context *ac);
+static int ph_mod_search_callback(struct ldb_request *req, struct ldb_reply *ares);
+static int password_hash_mod_do_mod(struct ph_context *ac);
+
+/*
+ * LDB callback handler for searching for a user's PSO. Once we have all the
+ * Password Settings that apply to the user, we can continue with the modify
+ * operation
+ */
+static int get_pso_data_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct ldb_context *ldb = NULL;
+ struct ph_context *ac = NULL;
+ bool domain_complexity = true;
+ bool pso_complexity = true;
+ struct dsdb_user_pwd_settings *settings = NULL;
+ int ret = LDB_SUCCESS;
+
+ ac = talloc_get_type(req->context, struct ph_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+
+ /* check status was initialized by the domain query */
+ if (ac->status == NULL) {
+ talloc_free(ares);
+ ldb_set_errstring(ldb, "Uninitialized status");
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ /*
+ * use the PSO's values instead of the domain defaults (the PSO
+ * attributes should always exist, but use the domain default
+ * values as a fallback).
+ */
+ settings = &ac->status->domain_data;
+ settings->store_cleartext =
+ ldb_msg_find_attr_as_bool(ares->message,
+ "msDS-PasswordReversibleEncryptionEnabled",
+ settings->store_cleartext);
+
+ settings->pwdHistoryLength =
+ ldb_msg_find_attr_as_uint(ares->message,
+ "msDS-PasswordHistoryLength",
+ settings->pwdHistoryLength);
+ settings->maxPwdAge =
+ ldb_msg_find_attr_as_int64(ares->message,
+ "msDS-MaximumPasswordAge",
+ settings->maxPwdAge);
+ settings->minPwdAge =
+ ldb_msg_find_attr_as_int64(ares->message,
+ "msDS-MinimumPasswordAge",
+ settings->minPwdAge);
+ settings->minPwdLength =
+ ldb_msg_find_attr_as_uint(ares->message,
+ "msDS-MinimumPasswordLength",
+ settings->minPwdLength);
+ domain_complexity =
+ (settings->pwdProperties & DOMAIN_PASSWORD_COMPLEX);
+ pso_complexity =
+ ldb_msg_find_attr_as_bool(ares->message,
+ "msDS-PasswordComplexityEnabled",
+ domain_complexity);
+
+ /* set or clear the complexity bit if required */
+ if (pso_complexity && !domain_complexity) {
+ settings->pwdProperties |= DOMAIN_PASSWORD_COMPLEX;
+ } else if (domain_complexity && !pso_complexity) {
+ settings->pwdProperties &= ~DOMAIN_PASSWORD_COMPLEX;
+ }
+
+ if (ac->pso_res != NULL) {
+ DBG_ERR("Too many PSO results for %s",
+ ldb_dn_get_linearized(ac->search_res->message->dn));
+ talloc_free(ac->pso_res);
+ }
+
+ /* store the PSO result (we may need its lockout settings) */
+ ac->pso_res = talloc_steal(ac, ares);
+ ret = LDB_SUCCESS;
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ talloc_free(ares);
+ ret = LDB_SUCCESS;
+ break;
+
+ case LDB_REPLY_DONE:
+ talloc_free(ares);
+
+ /*
+ * perform the next step of the modify operation (this code
+ * shouldn't get called in the 'user add' case)
+ */
+ if (ac->req->operation == LDB_MODIFY) {
+ ret = password_hash_mod_do_mod(ac);
+ } else {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ }
+ break;
+ }
+
+done:
+ if (ret != LDB_SUCCESS) {
+ struct ldb_reply *new_ares;
+
+ new_ares = talloc_zero(ac->req, struct ldb_reply);
+ if (new_ares == NULL) {
+ ldb_oom(ldb);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ new_ares->error = ret;
+ if ((ret != LDB_ERR_OPERATIONS_ERROR) && (ac->change_status)) {
+ /* On success and trivial errors a status control is being
+ * added (used for example by the "samdb_set_password" call) */
+ ldb_reply_add_control(new_ares,
+ DSDB_CONTROL_PASSWORD_CHANGE_STATUS_OID,
+ false,
+ ac->status);
+ }
+
+ return ldb_module_done(ac->req, new_ares->controls,
+ new_ares->response, new_ares->error);
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * Builds and returns a search request to lookup up the PSO that applies to
+ * the user in question. Returns NULL if no PSO applies, or could not be found
+ */
+static struct ldb_request * build_pso_data_request(struct ph_context *ac)
+{
+ /* attrs[] is returned from this function in
+ pso_req->op.search.attrs, so it must be static, as
+ otherwise the compiler can put it on the stack */
+ static const char * const attrs[] = { "msDS-PasswordComplexityEnabled",
+ "msDS-PasswordReversibleEncryptionEnabled",
+ "msDS-PasswordHistoryLength",
+ "msDS-MaximumPasswordAge",
+ "msDS-MinimumPasswordAge",
+ "msDS-MinimumPasswordLength",
+ "msDS-LockoutThreshold",
+ "msDS-LockoutObservationWindow",
+ NULL };
+ struct ldb_context *ldb = NULL;
+ struct ldb_request *pso_req = NULL;
+ struct ldb_dn *pso_dn = NULL;
+ TALLOC_CTX *mem_ctx = ac;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ /* if a PSO applies to the user, we need to lookup the PSO as well */
+ pso_dn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, ac->search_res->message,
+ "msDS-ResultantPSO");
+ if (pso_dn == NULL) {
+ return NULL;
+ }
+
+ ret = ldb_build_search_req(&pso_req, ldb, mem_ctx, pso_dn,
+ LDB_SCOPE_BASE, NULL, attrs, NULL,
+ ac, get_pso_data_callback,
+ ac->dom_req);
+
+ /* log errors, but continue with the default domain settings */
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("Error %d constructing PSO query for user %s", ret,
+ ldb_dn_get_linearized(ac->search_res->message->dn));
+ }
+ LDB_REQ_SET_LOCATION(pso_req);
+ return pso_req;
+}
+
+
+static int get_domain_data_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct ph_context *ac;
+ struct loadparm_context *lp_ctx;
+ struct ldb_request *pso_req = NULL;
+ int ret = LDB_SUCCESS;
+
+ ac = talloc_get_type(req->context, struct ph_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ if (ac->status != NULL) {
+ talloc_free(ares);
+
+ ldb_set_errstring(ldb, "Too many results");
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ /* Setup the "status" structure (used as control later) */
+ ac->status = talloc_zero(ac->req,
+ struct dsdb_control_password_change_status);
+ if (ac->status == NULL) {
+ talloc_free(ares);
+
+ ldb_oom(ldb);
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ /* Setup the "domain data" structure */
+ ac->status->domain_data.pwdProperties =
+ ldb_msg_find_attr_as_uint(ares->message, "pwdProperties", -1);
+ ac->status->domain_data.pwdHistoryLength =
+ ldb_msg_find_attr_as_uint(ares->message, "pwdHistoryLength", -1);
+ ac->status->domain_data.maxPwdAge =
+ ldb_msg_find_attr_as_int64(ares->message, "maxPwdAge", -1);
+ ac->status->domain_data.minPwdAge =
+ ldb_msg_find_attr_as_int64(ares->message, "minPwdAge", -1);
+ ac->status->domain_data.minPwdLength =
+ ldb_msg_find_attr_as_uint(ares->message, "minPwdLength", -1);
+ ac->status->domain_data.store_cleartext =
+ ac->status->domain_data.pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT;
+
+ /* 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 */
+
+ lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+
+ ac->status->domain_data.dns_domain = lpcfg_dnsdomain(lp_ctx);
+ ac->status->domain_data.realm = lpcfg_realm(lp_ctx);
+ ac->status->domain_data.netbios_domain = lpcfg_sam_name(lp_ctx);
+
+ ac->status->reject_reason = SAM_PWD_CHANGE_NO_ERROR;
+
+ if (ac->dom_res != NULL) {
+ talloc_free(ares);
+
+ ldb_set_errstring(ldb, "Too many results");
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ ac->dom_res = talloc_steal(ac, ares);
+ ret = LDB_SUCCESS;
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ talloc_free(ares);
+ ret = LDB_SUCCESS;
+ break;
+
+ case LDB_REPLY_DONE:
+ talloc_free(ares);
+ /* call the next step */
+ switch (ac->req->operation) {
+ case LDB_ADD:
+ ret = password_hash_add_do_add(ac);
+ break;
+
+ case LDB_MODIFY:
+
+ /*
+ * The user may have an optional PSO applied. If so,
+ * query the PSO to get the Fine-Grained Password Policy
+ * for the user, before we perform the modify
+ */
+ pso_req = build_pso_data_request(ac);
+ if (pso_req != NULL) {
+ ret = ldb_next_request(ac->module, pso_req);
+ } else {
+
+ /* no PSO, so we can perform the modify now */
+ ret = password_hash_mod_do_mod(ac);
+ }
+ break;
+
+ default:
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ break;
+ }
+ break;
+ }
+
+done:
+ if (ret != LDB_SUCCESS) {
+ struct ldb_reply *new_ares;
+
+ new_ares = talloc_zero(ac->req, struct ldb_reply);
+ if (new_ares == NULL) {
+ ldb_oom(ldb);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ new_ares->error = ret;
+ if ((ret != LDB_ERR_OPERATIONS_ERROR) && (ac->change_status)) {
+ /* On success and trivial errors a status control is being
+ * added (used for example by the "samdb_set_password" call) */
+ ldb_reply_add_control(new_ares,
+ DSDB_CONTROL_PASSWORD_CHANGE_STATUS_OID,
+ false,
+ ac->status);
+ }
+
+ return ldb_module_done(ac->req, new_ares->controls,
+ new_ares->response, new_ares->error);
+ }
+
+ 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 */
+ struct ldb_context *ldb;
+ static const char * const attrs[] = { "pwdProperties",
+ "pwdHistoryLength",
+ "maxPwdAge",
+ "minPwdAge",
+ "minPwdLength",
+ "lockoutThreshold",
+ "lockOutObservationWindow",
+ NULL };
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ ret = ldb_build_search_req(&ac->dom_req, ldb, ac,
+ ldb_get_default_basedn(ldb),
+ LDB_SCOPE_BASE,
+ NULL, attrs,
+ NULL,
+ ac, get_domain_data_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(ac->dom_req);
+ return ret;
+}
+
+static int password_hash_needed(struct ldb_module *module,
+ struct ldb_request *req,
+ struct ph_context **_ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const char *operation = NULL;
+ const struct ldb_message *msg = NULL;
+ struct ph_context *ac = NULL;
+ const char *passwordAttrs[] = {
+ DSDB_PASSWORD_ATTRIBUTES,
+ NULL
+ };
+ const char **a = NULL;
+ unsigned int attr_cnt = 0;
+ struct ldb_control *bypass = NULL;
+ struct ldb_control *uac_ctrl = NULL;
+ bool userPassword = dsdb_user_password_support(module, req, req);
+ bool update_password = false;
+ bool processing_needed = false;
+
+ *_ac = NULL;
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "password_hash_needed\n");
+
+ switch (req->operation) {
+ case LDB_ADD:
+ operation = "add";
+ msg = req->op.add.message;
+ break;
+ case LDB_MODIFY:
+ operation = "modify";
+ msg = req->op.mod.message;
+ break;
+ default:
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ bypass = ldb_request_get_control(req,
+ DSDB_CONTROL_BYPASS_PASSWORD_HASH_OID);
+ if (bypass != NULL) {
+ /* Mark the "bypass" control as uncritical (done) */
+ bypass->critical = false;
+ ldb_debug(ldb, LDB_DEBUG_TRACE,
+ "password_hash_needed(%s) (bypassing)\n",
+ operation);
+ return password_hash_bypass(module, req);
+ }
+
+ /* nobody must touch password histories and 'supplementalCredentials' */
+ if (ldb_msg_find_element(msg, "ntPwdHistory")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (ldb_msg_find_element(msg, "lmPwdHistory")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (ldb_msg_find_element(msg, "supplementalCredentials")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /*
+ * If no part of this touches the 'userPassword' OR 'clearTextPassword'
+ * OR 'unicodePwd' OR 'dBCSPwd' we don't need to make any changes.
+ * For password changes/set there should be a 'delete' or a 'modify'
+ * on these attributes.
+ */
+ for (a = passwordAttrs; *a != NULL; a++) {
+ if ((!userPassword) && (ldb_attr_cmp(*a, "userPassword") == 0)) {
+ continue;
+ }
+
+ if (ldb_msg_find_element(msg, *a) != NULL) {
+ /* MS-ADTS 3.1.1.3.1.5.2 */
+ if ((ldb_attr_cmp(*a, "userPassword") == 0) &&
+ (dsdb_functional_level(ldb) < DS_DOMAIN_FUNCTION_2003)) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ ++attr_cnt;
+ }
+ }
+
+ if (attr_cnt > 0) {
+ update_password = true;
+ processing_needed = true;
+ }
+
+ if (ldb_msg_find_element(msg, "pwdLastSet")) {
+ processing_needed = true;
+ }
+
+ uac_ctrl = ldb_request_get_control(req,
+ DSDB_CONTROL_PASSWORD_USER_ACCOUNT_CONTROL_OID);
+ if (uac_ctrl != NULL) {
+ struct dsdb_control_password_user_account_control *uac = NULL;
+ uint32_t added_flags = 0;
+
+ uac = talloc_get_type_abort(uac_ctrl->data,
+ struct dsdb_control_password_user_account_control);
+
+ added_flags = uac->new_flags & ~uac->old_flags;
+
+ if (added_flags & UF_SMARTCARD_REQUIRED) {
+ processing_needed = true;
+ }
+ }
+
+ if (!processing_needed) {
+ return ldb_next_request(module, req);
+ }
+
+ ac = ph_init_context(module, req, userPassword, update_password);
+ if (!ac) {
+ DEBUG(0,(__location__ ": %s\n", ldb_errstring(ldb)));
+ return ldb_operr(ldb);
+ }
+ ph_apply_controls(ac);
+
+ /*
+ * Make a copy in order to apply our modifications
+ * to the final update
+ */
+ ac->update_msg = ldb_msg_copy_shallow(ac, msg);
+ if (ac->update_msg == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /*
+ * Remove all password related attributes.
+ */
+ if (ac->userPassword) {
+ ldb_msg_remove_attr(ac->update_msg, "userPassword");
+ }
+ ldb_msg_remove_attr(ac->update_msg, "clearTextPassword");
+ ldb_msg_remove_attr(ac->update_msg, "unicodePwd");
+ ldb_msg_remove_attr(ac->update_msg, "ntPwdHistory");
+ ldb_msg_remove_attr(ac->update_msg, "dBCSPwd");
+ ldb_msg_remove_attr(ac->update_msg, "lmPwdHistory");
+ ldb_msg_remove_attr(ac->update_msg, "supplementalCredentials");
+ ldb_msg_remove_attr(ac->update_msg, "pwdLastSet");
+
+ *_ac = ac;
+ return LDB_SUCCESS;
+}
+
+static int password_hash_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ph_context *ac = NULL;
+ int ret;
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "password_hash_add\n");
+
+ ret = password_hash_needed(module, req, &ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (ac == NULL) {
+ return ret;
+ }
+
+ /* Make sure we are performing the password set action on a (for us)
+ * valid object. Those are instances of either "user" and/or
+ * "inetOrgPerson". Otherwise continue with the submodules. */
+ if ((!ldb_msg_check_string_attribute(req->op.add.message, "objectClass", "user"))
+ && (!ldb_msg_check_string_attribute(req->op.add.message, "objectClass", "inetOrgPerson"))) {
+
+ TALLOC_FREE(ac);
+
+ if (ldb_msg_find_element(req->op.add.message, "clearTextPassword") != NULL) {
+ ldb_set_errstring(ldb,
+ "'clearTextPassword' is only allowed on objects of class 'user' and/or 'inetOrgPerson'!");
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+
+ return ldb_next_request(module, req);
+ }
+
+ /* get user domain data */
+ ret = build_domain_data_request(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, ac->dom_req);
+}
+
+static int password_hash_add_do_add(struct ph_context *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct ldb_request *down_req;
+ struct setup_password_fields_io io;
+ int ret;
+
+ /* Prepare the internal data structure containing the passwords */
+ ret = setup_io(ac, ac->req->op.add.message, NULL, &io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = setup_password_fields(&io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = check_password_restrictions_and_log(&io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = setup_smartcard_reset(&io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = update_final_msg(&io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_build_add_req(&down_req, ldb, ac,
+ ac->update_msg,
+ ac->req->controls,
+ ac, ph_op_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ac->module, down_req);
+}
+
+static int password_hash_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ph_context *ac = NULL;
+ const char *passwordAttrs[] = {DSDB_PASSWORD_ATTRIBUTES, NULL}, **l;
+ unsigned int del_attr_cnt, add_attr_cnt, rep_attr_cnt;
+ struct ldb_message_element *passwordAttr;
+ struct ldb_message *msg;
+ struct ldb_request *down_req;
+ struct ldb_control *restore = NULL;
+ int ret;
+ unsigned int i = 0;
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "password_hash_modify\n");
+
+ ret = password_hash_needed(module, req, &ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (ac == NULL) {
+ return ret;
+ }
+
+ /* use a new message structure so that we can modify it */
+ msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
+ if (msg == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /* - check for single-valued password attributes
+ * (if not return "CONSTRAINT_VIOLATION")
+ * - check that for a password change operation one add and one delete
+ * operation exists
+ * (if not return "CONSTRAINT_VIOLATION" or "UNWILLING_TO_PERFORM")
+ * - check that a password change and a password set operation cannot
+ * be mixed
+ * (if not return "UNWILLING_TO_PERFORM")
+ * - remove all password attributes modifications from the first change
+ * operation (anything without the passwords) - we will make the real
+ * modification later */
+ del_attr_cnt = 0;
+ add_attr_cnt = 0;
+ rep_attr_cnt = 0;
+ for (l = passwordAttrs; *l != NULL; l++) {
+ if ((!ac->userPassword) &&
+ (ldb_attr_cmp(*l, "userPassword") == 0)) {
+ continue;
+ }
+
+ while ((passwordAttr = ldb_msg_find_element(msg, *l)) != NULL) {
+ unsigned int mtype = LDB_FLAG_MOD_TYPE(passwordAttr->flags);
+ unsigned int nvalues = passwordAttr->num_values;
+
+ if (mtype == LDB_FLAG_MOD_DELETE) {
+ ++del_attr_cnt;
+ }
+ if (mtype == LDB_FLAG_MOD_ADD) {
+ ++add_attr_cnt;
+ }
+ if (mtype == LDB_FLAG_MOD_REPLACE) {
+ ++rep_attr_cnt;
+ }
+ if ((nvalues != 1) && (mtype == LDB_FLAG_MOD_ADD)) {
+ talloc_free(ac);
+ ldb_asprintf_errstring(ldb,
+ "'%s' attribute must have exactly one value on add operations!",
+ *l);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if ((nvalues > 1) && (mtype == LDB_FLAG_MOD_DELETE)) {
+ talloc_free(ac);
+ ldb_asprintf_errstring(ldb,
+ "'%s' attribute must have zero or one value(s) on delete operations!",
+ *l);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ ldb_msg_remove_element(msg, passwordAttr);
+ }
+ }
+ if ((del_attr_cnt == 0) && (add_attr_cnt > 0)) {
+ talloc_free(ac);
+ ldb_set_errstring(ldb,
+ "Only the add action for a password change specified!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if ((del_attr_cnt > 1) || (add_attr_cnt > 1)) {
+ talloc_free(ac);
+ ldb_set_errstring(ldb,
+ "Only one delete and one add action for a password change allowed!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if ((rep_attr_cnt > 0) && ((del_attr_cnt > 0) || (add_attr_cnt > 0))) {
+ talloc_free(ac);
+ ldb_set_errstring(ldb,
+ "Either a password change or a password set operation is allowed!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ restore = ldb_request_get_control(req,
+ DSDB_CONTROL_RESTORE_TOMBSTONE_OID);
+ if (restore == NULL) {
+ /*
+ * A tomstone reanimation generates a double update
+ * of pwdLastSet.
+ *
+ * So we only remove it without the
+ * DSDB_CONTROL_RESTORE_TOMBSTONE_OID control.
+ */
+ ldb_msg_remove_attr(msg, "pwdLastSet");
+ }
+
+
+ /* if there was nothing else to be modified skip to next step */
+ if (msg->num_elements == 0) {
+ return password_hash_mod_search_self(ac);
+ }
+
+ /*
+ * Now we apply all changes remaining in msg
+ * and remove them from our final update_msg
+ */
+
+ for (i = 0; i < msg->num_elements; i++) {
+ ldb_msg_remove_attr(ac->update_msg,
+ msg->elements[i].name);
+ }
+
+ ret = ldb_build_mod_req(&down_req, ldb, ac,
+ msg,
+ req->controls,
+ ac, ph_modify_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, down_req);
+}
+
+static int ph_modify_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct ph_context *ac;
+
+ ac = talloc_get_type(req->context, struct ph_context);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ares->type == LDB_REPLY_REFERRAL) {
+ return ldb_module_send_referral(ac->req, ares->referral);
+ }
+
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ talloc_free(ares);
+
+ return password_hash_mod_search_self(ac);
+}
+
+static int ph_mod_search_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct ph_context *ac;
+ int ret = LDB_SUCCESS;
+
+ ac = talloc_get_type(req->context, struct ph_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ /* we are interested only in the single reply (base search) */
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ /* Make sure we are performing the password change action on a
+ * (for us) valid object. Those are instances of either "user"
+ * and/or "inetOrgPerson". Otherwise continue with the
+ * submodules. */
+ if ((!ldb_msg_check_string_attribute(ares->message, "objectClass", "user"))
+ && (!ldb_msg_check_string_attribute(ares->message, "objectClass", "inetOrgPerson"))) {
+ talloc_free(ares);
+
+ if (ldb_msg_find_element(ac->req->op.mod.message, "clearTextPassword") != NULL) {
+ ldb_set_errstring(ldb,
+ "'clearTextPassword' is only allowed on objects of class 'user' and/or 'inetOrgPerson'!");
+ ret = LDB_ERR_NO_SUCH_ATTRIBUTE;
+ goto done;
+ }
+
+ ret = ldb_next_request(ac->module, ac->req);
+ goto done;
+ }
+
+ if (ac->search_res != NULL) {
+ talloc_free(ares);
+
+ ldb_set_errstring(ldb, "Too many results");
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ goto done;
+ }
+
+ ac->search_res = talloc_steal(ac, ares);
+ ret = LDB_SUCCESS;
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* ignore anything else for now */
+ talloc_free(ares);
+ ret = LDB_SUCCESS;
+ break;
+
+ case LDB_REPLY_DONE:
+ talloc_free(ares);
+
+ /* get user domain data */
+ ret = build_domain_data_request(ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ ret = ldb_next_request(ac->module, ac->dom_req);
+ break;
+ }
+
+done:
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int password_hash_mod_search_self(struct ph_context *ac)
+{
+ struct ldb_context *ldb;
+ static const char * const attrs[] = { "objectClass",
+ "userAccountControl",
+ "msDS-ResultantPSO",
+ "msDS-User-Account-Control-Computed",
+ "pwdLastSet",
+ "sAMAccountName",
+ "objectSid",
+ "userPrincipalName",
+ "displayName",
+ "supplementalCredentials",
+ "lmPwdHistory",
+ "ntPwdHistory",
+ "dBCSPwd",
+ "unicodePwd",
+ "badPasswordTime",
+ "badPwdCount",
+ "lockoutTime",
+ "msDS-KeyVersionNumber",
+ "msDS-SecondaryKrbTgtNumber",
+ NULL };
+ struct ldb_request *search_req;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ ret = ldb_build_search_req(&search_req, ldb, ac,
+ ac->req->op.mod.message->dn,
+ LDB_SCOPE_BASE,
+ "(objectclass=*)",
+ attrs,
+ NULL,
+ ac, ph_mod_search_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ac->module, search_req);
+}
+
+static int password_hash_mod_do_mod(struct ph_context *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct ldb_request *mod_req;
+ struct setup_password_fields_io io;
+ int ret;
+
+ /* Prepare the internal data structure containing the passwords */
+ ret = setup_io(ac, ac->req->op.mod.message,
+ ac->search_res->message, &io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = setup_password_fields(&io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = check_password_restrictions_and_log(&io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = setup_smartcard_reset(&io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = update_final_msg(&io);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_build_mod_req(&mod_req, ldb, ac,
+ ac->update_msg,
+ ac->req->controls,
+ ac, ph_op_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(mod_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ac->module, mod_req);
+}
+
+static const struct ldb_module_ops ldb_password_hash_module_ops = {
+ .name = "password_hash",
+ .add = password_hash_add,
+ .modify = password_hash_modify
+};
+
+int ldb_password_hash_module_init(const char *version)
+{
+#ifdef ENABLE_GPGME
+ const char *gversion = NULL;
+#endif /* ENABLE_GPGME */
+
+ LDB_MODULE_CHECK_VERSION(version);
+
+#ifdef ENABLE_GPGME
+ /*
+ * Note: this sets a SIGPIPE handler
+ * if none is active already. See:
+ * https://www.gnupg.org/documentation/manuals/gpgme/Signal-Handling.html#Signal-Handling
+ */
+ gversion = gpgme_check_version(MINIMUM_GPGME_VERSION);
+ if (gversion == NULL) {
+ fprintf(stderr, "%s() in %s version[%s]: "
+ "gpgme_check_version(%s) not available, "
+ "gpgme_check_version(NULL) => '%s'\n",
+ __func__, __FILE__, version,
+ MINIMUM_GPGME_VERSION, gpgme_check_version(NULL));
+ return LDB_ERR_UNAVAILABLE;
+ }
+#endif /* ENABLE_GPGME */
+
+ return ldb_register_module(&ldb_password_hash_module_ops);
+}
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 0000000..40d0144
--- /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/proxy.c b/source4/dsdb/samdb/ldb_modules/proxy.c
new file mode 100644
index 0000000..e363534
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/proxy.c
@@ -0,0 +1,415 @@
+/*
+ samdb proxy module
+
+ Copyright (C) Andrew Tridgell 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/>.
+*/
+
+/*
+ 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_module.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;
+};
+
+struct proxy_ctx {
+ struct ldb_module *module;
+ struct ldb_request *req;
+
+#ifdef DEBUG_PROXY
+ int count;
+#endif
+};
+
+/*
+ load the @PROXYINFO record
+*/
+static int load_proxy_info(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct proxy_data *proxy = talloc_get_type(ldb_module_get_private(module), 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;
+ bool ok;
+
+ /* see if we have already loaded it */
+ if (proxy->upstream != NULL) {
+ return LDB_SUCCESS;
+ }
+
+ dn = ldb_dn_new(proxy, ldb, "@PROXYINFO");
+ if (dn == NULL) {
+ goto failed;
+ }
+ ret = ldb_search(ldb, proxy, &res, dn, LDB_SCOPE_BASE, NULL, NULL);
+ talloc_free(dn);
+ if (ret != LDB_SUCCESS || res->count != 1) {
+ ldb_debug(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(ldb, LDB_DEBUG_FATAL, "Need url, olddn, newdn, oldstr, newstr, username and password in @PROXYINFO\n");
+ goto failed;
+ }
+
+ proxy->olddn = ldb_dn_new(proxy, ldb, olddn);
+ if (proxy->olddn == NULL) {
+ ldb_debug(ldb, LDB_DEBUG_FATAL, "Failed to explode olddn '%s'\n", olddn);
+ goto failed;
+ }
+
+ proxy->newdn = ldb_dn_new(proxy, ldb, newdn);
+ if (proxy->newdn == NULL) {
+ ldb_debug(ldb, LDB_DEBUG_FATAL, "Failed to explode newdn '%s'\n", newdn);
+ goto failed;
+ }
+
+ proxy->upstream = ldb_init(proxy, ldb_get_event_context(ldb));
+ if (proxy->upstream == NULL) {
+ ldb_oom(ldb);
+ goto failed;
+ }
+
+ proxy->oldstr = str_list_make(proxy, oldstr, ", ");
+ if (proxy->oldstr == NULL) {
+ ldb_oom(ldb);
+ goto failed;
+ }
+
+ proxy->newstr = str_list_make(proxy, newstr, ", ");
+ if (proxy->newstr == NULL) {
+ ldb_oom(ldb);
+ goto failed;
+ }
+
+ /* setup credentials for connection */
+ creds = cli_credentials_init(proxy->upstream);
+ if (creds == NULL) {
+ ldb_oom(ldb);
+ goto failed;
+ }
+ ok = cli_credentials_guess(creds, ldb_get_opaque(ldb, "loadparm"));
+ if (!ok) {
+ ldb_oom(ldb);
+ goto failed;
+ }
+
+ 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 != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_FATAL, "proxy failed to connect to %s\n", url);
+ goto failed;
+ }
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "proxy connected to %s\n", url);
+
+ talloc_free(res);
+
+ return LDB_SUCCESS;
+
+failed:
+ talloc_free(res);
+ talloc_free(proxy->olddn);
+ talloc_free(proxy->newdn);
+ talloc_free(proxy->upstream);
+ proxy->upstream = NULL;
+ return ldb_operr(ldb);
+}
+
+
+/*
+ convert a binary blob
+*/
+static void proxy_convert_blob(TALLOC_CTX *mem_ctx, struct ldb_val *v,
+ const char *oldstr, const char *newstr)
+{
+ size_t 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 proxy_data *proxy, struct ldb_message *msg, struct ldb_val *v)
+{
+ size_t 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(TALLOC_CTX *mem_ctx,
+ struct proxy_data *proxy,
+ struct ldb_parse_tree *tree)
+{
+ size_t i;
+ char *expression = ldb_filter_from_tree(mem_ctx, 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(mem_ctx, &v, proxy->newstr[i], proxy->oldstr[i]);
+ return ldb_parse_tree(mem_ctx, (const char *)v.data);
+ }
+ return tree;
+}
+
+
+
+/*
+ convert a returned record
+*/
+static void proxy_convert_record(struct ldb_context *ldb,
+ struct proxy_data *proxy,
+ struct ldb_message *msg)
+{
+ unsigned int attr, v;
+
+ /* fix the message DN */
+ if (ldb_dn_compare_base(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(proxy, 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(proxy, msg, &msg->elements[attr].values[v]);
+ }
+ }
+}
+
+static int proxy_search_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct proxy_data *proxy;
+ struct proxy_ctx *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct proxy_ctx);
+ ldb = ldb_module_get_ctx(ac->module);
+ proxy = talloc_get_type(ldb_module_get_private(module), struct proxy_data);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ /* Only entries are interesting, and we only want the olddn */
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+
+#ifdef DEBUG_PROXY
+ ac->count++;
+#endif
+ proxy_convert_record(ldb, proxy, ares->message);
+ ret = ldb_module_send_entry(ac->req, ares->message, ares->controls);
+ break;
+
+ case LDB_REPLY_REFERRAL:
+
+ /* ignore remote referrals */
+ break;
+
+ case LDB_REPLY_DONE:
+
+#ifdef DEBUG_PROXY
+ printf("# record %d\n", ac->count+1);
+#endif
+
+ return ldb_module_done(ac->req, NULL, NULL, LDB_SUCCESS);
+ }
+
+ talloc_free(ares);
+ return ret;
+}
+
+/* search */
+static int proxy_search_bytree(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct proxy_ctx *ac;
+ struct ldb_parse_tree *newtree;
+ struct proxy_data *proxy = talloc_get_type(ldb_module_get_private(module), struct proxy_data);
+ struct ldb_request *newreq;
+ struct ldb_dn *base;
+ unsigned int i;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ 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) != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ /* see if the dn is within olddn */
+ if (ldb_dn_compare_base(proxy->newdn, req->op.search.base) != 0) {
+ goto passthru;
+ }
+
+ ac = talloc(req, struct proxy_ctx);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ac->module = module;
+ ac->req = req;
+#ifdef DEBUG_PROXY
+ ac->count = 0;
+#endif
+
+ newtree = proxy_convert_tree(ac, proxy, req->op.search.tree);
+ if (newtree == NULL) {
+ goto failed;
+ }
+
+ /* convert the basedn of this search */
+ base = ldb_dn_copy(ac, req->op.search.base);
+ if (base == NULL) {
+ goto failed;
+ }
+ ldb_dn_remove_base_components(base, ldb_dn_get_comp_num(proxy->newdn));
+ ldb_dn_add_base(base, proxy->olddn);
+
+ ldb_debug(ldb, LDB_DEBUG_FATAL, "proxying: '%s' with dn '%s' \n",
+ ldb_filter_from_tree(ac, 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(ldb, LDB_DEBUG_FATAL, "attr: '%s'\n", req->op.search.attrs[i]);
+ }
+
+ ret = ldb_build_search_req_ex(&newreq, ldb, ac,
+ base, req->op.search.scope,
+ newtree, req->op.search.attrs,
+ req->controls,
+ ac, proxy_search_callback,
+ req);
+ LDB_REQ_SET_LOCATION(newreq);
+ /* FIXME: warning, need a real event system hooked up for this to work properly,
+ * for now this makes the module *not* ASYNC */
+ ret = ldb_request(proxy->upstream, newreq);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(ldb, ldb_errstring(proxy->upstream));
+ }
+ ret = ldb_wait(newreq->handle, LDB_WAIT_ALL);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_errstring(ldb, ldb_errstring(proxy->upstream));
+ }
+ return ret;
+
+failed:
+ ldb_debug(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);
+
+ }
+}
+
+static const struct ldb_module_ops ldb_proxy_module_ops = {
+ .name = "proxy",
+ .request = proxy_request
+};
+
+int ldb_proxy_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_proxy_module_ops);
+}
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 0000000..b010abb
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/ranged_results.c
@@ -0,0 +1,295 @@
+/*
+ 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 "includes.h"
+#include "ldb_module.h"
+
+#undef strncasecmp
+
+struct rr_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+ bool dirsync_in_use;
+};
+
+static struct rr_context *rr_init_context(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct ldb_control *dirsync_control = NULL;
+ struct rr_context *ac = talloc_zero(req, struct rr_context);
+ if (ac == NULL) {
+ ldb_set_errstring(ldb_module_get_ctx(module), "Out of Memory");
+ return NULL;
+ }
+
+ ac->module = module;
+ ac->req = req;
+
+ /*
+ * check if there's a dirsync control (as there is an
+ * interaction between these modules)
+ */
+ dirsync_control = ldb_request_get_control(req,
+ LDB_CONTROL_DIRSYNC_OID);
+ if (dirsync_control != NULL) {
+ ac->dirsync_in_use = true;
+ }
+
+ return ac;
+}
+
+static int rr_search_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct rr_context *ac;
+ unsigned int i, j;
+ TALLOC_CTX *temp_ctx;
+
+ ac = talloc_get_type(req->context, struct rr_context);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type == LDB_REPLY_REFERRAL) {
+ return ldb_module_send_referral(ac->req, ares->referral);
+ }
+
+ if (ares->type == LDB_REPLY_DONE) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ac->dirsync_in_use) {
+ /*
+ * We return full attribute values when mixed with
+ * dirsync
+ */
+ return ldb_module_send_entry(ac->req,
+ ares->message,
+ ares->controls);
+ }
+ /* LDB_REPLY_ENTRY */
+
+ temp_ctx = talloc_new(ac->req);
+ if (!temp_ctx) {
+ ldb_module_oom(ac->module);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* Find those that are range requests from the attribute list */
+ for (i = 0; ac->req->op.search.attrs[i]; i++) {
+ char *p, *new_attr;
+ const char *end_str;
+ unsigned int start, end;
+ struct ldb_message_element *el;
+ struct ldb_val *orig_values;
+
+ p = strchr(ac->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) {
+ if (sscanf(p, ";range=%u-*", &start) == 1) {
+ end = (unsigned int)-1;
+ } else {
+ continue;
+ }
+ }
+ new_attr = talloc_strndup(temp_ctx,
+ ac->req->op.search.attrs[i],
+ (size_t)(p - ac->req->op.search.attrs[i]));
+
+ if (!new_attr) {
+ ldb_oom(ldb);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ el = ldb_msg_find_element(ares->message, new_attr);
+ talloc_free(new_attr);
+ if (!el) {
+ continue;
+ }
+ 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(temp_ctx, "%u", end);
+ if (!end_str) {
+ ldb_oom(ldb);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+ /* If start is greater then where we are find the end to be */
+ if (start > end) {
+ el->num_values = 0;
+ el->values = NULL;
+ } else {
+ orig_values = el->values;
+
+ if ((start + end < start) || (start + end < end)) {
+ ldb_asprintf_errstring(ldb,
+ "range request error: start or end would overflow!");
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_UNWILLING_TO_PERFORM);
+ }
+
+ el->num_values = 0;
+
+ el->values = talloc_array(ares->message->elements,
+ struct ldb_val,
+ (end - start) + 1);
+ if (!el->values) {
+ ldb_oom(ldb);
+ return ldb_module_done(ac->req, NULL, NULL,
+ 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(ares->message->elements,
+ "%s;range=%u-%s", el->name, start,
+ end_str);
+ if (!el->name) {
+ ldb_oom(ldb);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+
+ talloc_free(temp_ctx);
+
+ return ldb_module_send_entry(ac->req, ares->message, ares->controls);
+}
+
+/* search */
+static int rr_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ unsigned int i;
+ unsigned int start, end;
+ const char **new_attrs = NULL;
+ bool found_rr = false;
+ struct ldb_request *down_req;
+ struct rr_context *ac;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ /* Strip the range request from the attribute */
+ for (i = 0; req->op.search.attrs && req->op.search.attrs[i]; i++) {
+ char *p;
+ size_t range_len = strlen(";range=");
+
+ 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(new_attrs[i], ';');
+ if (!p) {
+ continue;
+ }
+ if (strncasecmp(p, ";range=", range_len) != 0) {
+ continue;
+ }
+ end = (unsigned int)-1;
+ if (sscanf(p + range_len, "%u-*", &start) != 1) {
+ if (sscanf(p + range_len, "%u-%u", &start, &end) != 2) {
+ ldb_asprintf_errstring(ldb,
+ "range request error: "
+ "range request malformed");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+ if (start > end) {
+ ldb_asprintf_errstring(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, new_attrs[i],
+ (size_t)(p - new_attrs[i]));
+
+ if (!new_attrs[i]) {
+ return ldb_oom(ldb);
+ }
+ }
+
+ if (found_rr) {
+ ac = rr_init_context(module, req);
+ if (!ac) {
+ return ldb_operr(ldb);
+ }
+
+ ret = ldb_build_search_req_ex(&down_req, ldb, ac,
+ req->op.search.base,
+ req->op.search.scope,
+ req->op.search.tree,
+ new_attrs,
+ req->controls,
+ ac, rr_search_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(module, down_req);
+ }
+
+ /* No change, just run the original request as if we were never here */
+ talloc_free(new_attrs);
+ return ldb_next_request(module, req);
+}
+
+static const struct ldb_module_ops ldb_ranged_results_module_ops = {
+ .name = "ranged_results",
+ .search = rr_search,
+};
+
+int ldb_ranged_results_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_ranged_results_module_ops);
+}
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 0000000..175a02d
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
@@ -0,0 +1,8658 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2004-2008
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2013
+ Copyright (C) Andrew Tridgell 2005-2009
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+ Copyright (C) Matthieu Patou <mat@samba.org> 2010-2011
+
+ 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 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 "ldb_module.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/proto.h"
+#include "dsdb/common/util.h"
+#include "../libds/common/flags.h"
+#include "librpc/gen_ndr/irpc.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+#include "libcli/security/security.h"
+#include "lib/util/dlinklist.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "lib/util/tsort.h"
+#include "lib/util/binsearch.h"
+
+#undef strcasecmp
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_DRS_REPL
+
+/* the RMD_VERSION for linked attributes starts from 1 */
+#define RMD_VERSION_INITIAL 1
+
+/*
+ * It's 29/12/9999 at 23:59:59 UTC as specified in MS-ADTS 7.1.1.4.2
+ * Deleted Objects Container
+ */
+static const NTTIME DELETED_OBJECT_CONTAINER_CHANGE_TIME = 2650466015990000000ULL;
+
+struct replmd_private {
+ TALLOC_CTX *la_ctx;
+ struct la_group *la_list;
+ struct nc_entry {
+ struct nc_entry *prev, *next;
+ struct ldb_dn *dn;
+ uint64_t mod_usn;
+ uint64_t mod_usn_urgent;
+ } *ncs;
+ struct ldb_dn *schema_dn;
+ bool originating_updates;
+ bool sorted_links;
+ uint32_t total_links;
+ uint32_t num_processed;
+ bool recyclebin_enabled;
+ bool recyclebin_state_known;
+};
+
+/*
+ * groups link attributes together by source-object and attribute-ID,
+ * to improve processing efficiency (i.e. for 'member' attribute, which
+ * could have 100s or 1000s of links).
+ * Note this grouping is best effort - the same source object could still
+ * correspond to several la_groups (a lot depends on the order DRS sends
+ * the links in). The groups currently don't span replication chunks (which
+ * caps the size to ~1500 links by default).
+ */
+struct la_group {
+ struct la_group *next, *prev;
+ struct la_entry *la_entries;
+};
+
+struct la_entry {
+ struct la_entry *next, *prev;
+ struct drsuapi_DsReplicaLinkedAttribute *la;
+ uint32_t dsdb_repl_flags;
+};
+
+struct replmd_replicated_request {
+ struct ldb_module *module;
+ struct ldb_request *req;
+
+ const struct dsdb_schema *schema;
+ struct GUID our_invocation_id;
+
+ /* the controls we pass down */
+ struct ldb_control **controls;
+
+ /*
+ * Backlinks for the replmd_add() case (we want to create
+ * backlinks after creating the user, but before the end of
+ * the ADD request)
+ */
+ struct la_backlink *la_backlinks;
+
+ /* details for the mode where we apply a bunch of inbound replication meessages */
+ bool apply_mode;
+ uint32_t index_current;
+ struct dsdb_extended_replicated_objects *objs;
+
+ struct ldb_message *search_msg;
+ struct GUID local_parent_guid;
+
+ uint64_t seq_num;
+ bool is_urgent;
+
+ bool isDeleted;
+
+ bool fix_link_sid;
+};
+
+/*
+ * the result of replmd_process_linked_attribute(): either there was no change
+ * (update was ignored), a new link was added (either inactive or active), or
+ * an existing link was modified (active/inactive status may have changed).
+ */
+typedef enum {
+ LINK_CHANGE_NONE,
+ LINK_CHANGE_ADDED,
+ LINK_CHANGE_MODIFIED,
+} replmd_link_changed;
+
+static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar);
+static int replmd_delete_internals(struct ldb_module *module, struct ldb_request *req, bool re_delete);
+static int replmd_check_upgrade_links(struct ldb_context *ldb,
+ struct parsed_dn *dns, uint32_t count,
+ struct ldb_message_element *el,
+ const char *ldap_oid);
+static int replmd_verify_link_target(struct replmd_replicated_request *ar,
+ TALLOC_CTX *mem_ctx,
+ struct la_entry *la_entry,
+ struct ldb_dn *src_dn,
+ const struct dsdb_attribute *attr);
+static int replmd_get_la_entry_source(struct ldb_module *module,
+ struct la_entry *la_entry,
+ TALLOC_CTX *mem_ctx,
+ const struct dsdb_attribute **ret_attr,
+ struct ldb_message **source_msg);
+static int replmd_set_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct dsdb_dn *dsdb_dn,
+ struct dsdb_dn *old_dsdb_dn, const struct GUID *invocation_id,
+ uint64_t usn, uint64_t local_usn, NTTIME nttime,
+ uint32_t version, bool deleted);
+
+static int replmd_make_deleted_child_dn(TALLOC_CTX *tmp_ctx,
+ struct ldb_context *ldb,
+ struct ldb_dn *dn,
+ const char *rdn_name,
+ const struct ldb_val *rdn_value,
+ struct GUID guid);
+
+enum urgent_situation {
+ REPL_URGENT_ON_CREATE = 1,
+ REPL_URGENT_ON_UPDATE = 2,
+ REPL_URGENT_ON_DELETE = 4
+};
+
+enum deletion_state {
+ OBJECT_NOT_DELETED=1,
+ OBJECT_DELETED=2,
+ OBJECT_RECYCLED=3,
+ OBJECT_TOMBSTONE=4,
+ OBJECT_REMOVED=5
+};
+
+static bool replmd_recyclebin_enabled(struct ldb_module *module)
+{
+ bool enabled = false;
+ struct replmd_private *replmd_private =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct replmd_private);
+
+ /*
+ * only lookup the recycle-bin state once per replication, then cache
+ * the result. This can save us 1000s of DB searches
+ */
+ if (!replmd_private->recyclebin_state_known) {
+ int ret = dsdb_recyclebin_enabled(module, &enabled);
+ if (ret != LDB_SUCCESS) {
+ return false;
+ }
+
+ replmd_private->recyclebin_enabled = enabled;
+ replmd_private->recyclebin_state_known = true;
+ }
+
+ return replmd_private->recyclebin_enabled;
+}
+
+static void replmd_deletion_state(struct ldb_module *module,
+ const struct ldb_message *msg,
+ enum deletion_state *current_state,
+ enum deletion_state *next_state)
+{
+ bool enabled = false;
+
+ if (msg == NULL) {
+ *current_state = OBJECT_REMOVED;
+ if (next_state != NULL) {
+ *next_state = OBJECT_REMOVED;
+ }
+ return;
+ }
+
+ enabled = replmd_recyclebin_enabled(module);
+
+ if (ldb_msg_check_string_attribute(msg, "isDeleted", "TRUE")) {
+ if (!enabled) {
+ *current_state = OBJECT_TOMBSTONE;
+ if (next_state != NULL) {
+ *next_state = OBJECT_REMOVED;
+ }
+ return;
+ }
+
+ if (ldb_msg_check_string_attribute(msg, "isRecycled", "TRUE")) {
+ *current_state = OBJECT_RECYCLED;
+ if (next_state != NULL) {
+ *next_state = OBJECT_REMOVED;
+ }
+ return;
+ }
+
+ *current_state = OBJECT_DELETED;
+ if (next_state != NULL) {
+ *next_state = OBJECT_RECYCLED;
+ }
+ return;
+ }
+
+ *current_state = OBJECT_NOT_DELETED;
+ if (next_state == NULL) {
+ return;
+ }
+
+ if (enabled) {
+ *next_state = OBJECT_DELETED;
+ } else {
+ *next_state = OBJECT_TOMBSTONE;
+ }
+}
+
+static const struct {
+ const char *update_name;
+ enum urgent_situation repl_situation;
+} urgent_objects[] = {
+ {"nTDSDSA", (REPL_URGENT_ON_CREATE | REPL_URGENT_ON_DELETE)},
+ {"crossRef", (REPL_URGENT_ON_CREATE | REPL_URGENT_ON_DELETE)},
+ {"attributeSchema", (REPL_URGENT_ON_CREATE | REPL_URGENT_ON_UPDATE)},
+ {"classSchema", (REPL_URGENT_ON_CREATE | REPL_URGENT_ON_UPDATE)},
+ {"secret", (REPL_URGENT_ON_CREATE | REPL_URGENT_ON_UPDATE)},
+ {"rIDManager", (REPL_URGENT_ON_CREATE | REPL_URGENT_ON_UPDATE)},
+ {NULL, 0}
+};
+
+/* Attributes looked for when updating or deleting, to check for a urgent replication needed */
+static const char *urgent_attrs[] = {
+ "lockoutTime",
+ "pwdLastSet",
+ "userAccountControl",
+ NULL
+};
+
+
+static bool replmd_check_urgent_objectclass(const struct ldb_message_element *objectclass_el,
+ enum urgent_situation situation)
+{
+ unsigned int i, j;
+ for (i=0; urgent_objects[i].update_name; i++) {
+
+ if ((situation & urgent_objects[i].repl_situation) == 0) {
+ continue;
+ }
+
+ for (j=0; j<objectclass_el->num_values; j++) {
+ const struct ldb_val *v = &objectclass_el->values[j];
+ if (ldb_attr_cmp((const char *)v->data, urgent_objects[i].update_name) == 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static bool replmd_check_urgent_attribute(const struct ldb_message_element *el)
+{
+ if (ldb_attr_in_list(urgent_attrs, el->name)) {
+ return true;
+ }
+ return false;
+}
+
+static int replmd_replicated_apply_isDeleted(struct replmd_replicated_request *ar);
+
+/*
+ initialise the module
+ allocate the private structure and build the list
+ of partition DNs for use by replmd_notify()
+ */
+static int replmd_init(struct ldb_module *module)
+{
+ struct replmd_private *replmd_private;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret;
+
+ replmd_private = talloc_zero(module, struct replmd_private);
+ if (replmd_private == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = dsdb_check_samba_compatible_feature(module,
+ SAMBA_SORTED_LINKS_FEATURE,
+ &replmd_private->sorted_links);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(replmd_private);
+ return ret;
+ }
+
+ replmd_private->schema_dn = ldb_get_schema_basedn(ldb);
+ ldb_module_set_private(module, replmd_private);
+ return ldb_next_init(module);
+}
+
+/*
+ cleanup our per-transaction contexts
+ */
+static void replmd_txn_cleanup(struct replmd_private *replmd_private)
+{
+ talloc_free(replmd_private->la_ctx);
+ replmd_private->la_list = NULL;
+ replmd_private->la_ctx = NULL;
+ replmd_private->recyclebin_state_known = false;
+}
+
+
+struct la_backlink {
+ struct la_backlink *next, *prev;
+ const char *attr_name;
+ struct ldb_dn *forward_dn;
+ struct GUID target_guid;
+ bool active;
+};
+
+/*
+ a ldb_modify request operating on modules below the
+ current module
+ */
+static int linked_attr_modify(struct ldb_module *module,
+ const struct ldb_message *message,
+ struct ldb_request *parent)
+{
+ struct ldb_request *mod_req;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct ldb_result *res;
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ ret = ldb_build_mod_req(&mod_req, ldb, tmp_ctx,
+ message,
+ NULL,
+ res,
+ ldb_modify_default_callback,
+ parent);
+ LDB_REQ_SET_LOCATION(mod_req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = ldb_request_add_control(mod_req, DSDB_CONTROL_REPLICATED_UPDATE_OID,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Run the new request */
+ ret = ldb_next_request(module, mod_req);
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(mod_req->handle, LDB_WAIT_ALL);
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ process a backlinks we accumulated during a transaction, adding and
+ deleting the backlinks from the target objects
+ */
+static int replmd_process_backlink(struct ldb_module *module, struct la_backlink *bl, struct ldb_request *parent)
+{
+ struct ldb_dn *target_dn, *source_dn;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_message *msg;
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *dn_string;
+
+ /*
+ - find DN of target
+ - find DN of source
+ - construct ldb_message
+ - either an add or a delete
+ */
+ ret = dsdb_module_dn_by_guid(module, frame, &bl->target_guid, &target_dn, parent);
+ if (ret != LDB_SUCCESS) {
+ struct GUID_txt_buf guid_str;
+ DBG_WARNING("Failed to find target DN for linked attribute with GUID %s\n",
+ GUID_buf_string(&bl->target_guid, &guid_str));
+ DBG_WARNING("Please run 'samba-tool dbcheck' to resolve any missing backlinks.\n");
+ talloc_free(frame);
+ return LDB_SUCCESS;
+ }
+
+ msg = ldb_msg_new(frame);
+ if (msg == NULL) {
+ ldb_module_oom(module);
+ talloc_free(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ source_dn = ldb_dn_copy(frame, bl->forward_dn);
+ if (!source_dn) {
+ ldb_module_oom(module);
+ talloc_free(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ } else {
+ /* Filter down to the attributes we want in the backlink */
+ const char *accept[] = { "GUID", "SID", NULL };
+ ldb_dn_extended_filter(source_dn, accept);
+ }
+
+ /* construct a ldb_message for adding/deleting the backlink */
+ msg->dn = target_dn;
+ dn_string = ldb_dn_get_extended_linearized(frame, bl->forward_dn, 1);
+ if (!dn_string) {
+ ldb_module_oom(module);
+ talloc_free(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = ldb_msg_add_steal_string(msg, bl->attr_name, dn_string);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(frame);
+ return ret;
+ }
+ msg->elements[0].flags = bl->active?LDB_FLAG_MOD_ADD:LDB_FLAG_MOD_DELETE;
+
+ /* a backlink should never be single valued. Unfortunately the
+ exchange schema has a attribute
+ msExchBridgeheadedLocalConnectorsDNBL which is single
+ valued and a backlink. We need to cope with that by
+ ignoring the single value flag */
+ msg->elements[0].flags |= LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK;
+
+ ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE, parent);
+ if (ret == LDB_ERR_NO_SUCH_ATTRIBUTE && !bl->active) {
+ /* we allow LDB_ERR_NO_SUCH_ATTRIBUTE as success to
+ cope with possible corruption where the backlink has
+ already been removed */
+ DEBUG(3,("WARNING: backlink from %s already removed from %s - %s\n",
+ ldb_dn_get_linearized(target_dn),
+ ldb_dn_get_linearized(source_dn),
+ ldb_errstring(ldb)));
+ ret = LDB_SUCCESS;
+ } else if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to %s backlink from %s to %s - %s",
+ bl->active?"add":"remove",
+ ldb_dn_get_linearized(source_dn),
+ ldb_dn_get_linearized(target_dn),
+ ldb_errstring(ldb));
+ talloc_free(frame);
+ return ret;
+ }
+ talloc_free(frame);
+ return ret;
+}
+
+/*
+ add a backlink to the list of backlinks to add/delete in the prepare
+ commit
+
+ forward_dn is stolen onto the defereed context
+ */
+static int replmd_defer_add_backlink(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ const struct dsdb_schema *schema,
+ struct replmd_replicated_request *ac,
+ struct ldb_dn *forward_dn,
+ struct GUID *target_guid, bool active,
+ const struct dsdb_attribute *schema_attr,
+ struct ldb_request *parent)
+{
+ const struct dsdb_attribute *target_attr;
+ struct la_backlink *bl;
+
+ bl = talloc(ac, struct la_backlink);
+ if (bl == NULL) {
+ ldb_module_oom(module);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ target_attr = dsdb_attribute_by_linkID(schema, schema_attr->linkID ^ 1);
+ if (!target_attr) {
+ /*
+ * windows 2003 has a broken schema where the
+ * definition of msDS-IsDomainFor is missing (which is
+ * supposed to be the backlink of the
+ * msDS-HasDomainNCs attribute
+ */
+ return LDB_SUCCESS;
+ }
+
+ bl->attr_name = target_attr->lDAPDisplayName;
+ bl->forward_dn = talloc_steal(bl, forward_dn);
+ bl->target_guid = *target_guid;
+ bl->active = active;
+
+ DLIST_ADD(ac->la_backlinks, bl);
+
+ return LDB_SUCCESS;
+}
+
+/*
+ add a backlink to the list of backlinks to add/delete in the prepare
+ commit
+ */
+static int replmd_add_backlink(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ const struct dsdb_schema *schema,
+ struct ldb_dn *forward_dn,
+ struct GUID *target_guid, bool active,
+ const struct dsdb_attribute *schema_attr,
+ struct ldb_request *parent)
+{
+ const struct dsdb_attribute *target_attr;
+ struct la_backlink bl;
+ int ret;
+
+ target_attr = dsdb_attribute_by_linkID(schema, schema_attr->linkID ^ 1);
+ if (!target_attr) {
+ /*
+ * windows 2003 has a broken schema where the
+ * definition of msDS-IsDomainFor is missing (which is
+ * supposed to be the backlink of the
+ * msDS-HasDomainNCs attribute
+ */
+ return LDB_SUCCESS;
+ }
+
+ bl.attr_name = target_attr->lDAPDisplayName;
+ bl.forward_dn = forward_dn;
+ bl.target_guid = *target_guid;
+ bl.active = active;
+
+ ret = replmd_process_backlink(module, &bl, parent);
+ return ret;
+}
+
+
+/*
+ * Callback for most write operations in this module:
+ *
+ * notify the repl task that a object has changed. The notifies are
+ * gathered up in the replmd_private structure then written to the
+ * @REPLCHANGED object in each partition during the prepare_commit
+ */
+static int replmd_op_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ int ret;
+ struct replmd_replicated_request *ac =
+ talloc_get_type_abort(req->context, struct replmd_replicated_request);
+ struct replmd_private *replmd_private =
+ talloc_get_type_abort(ldb_module_get_private(ac->module), struct replmd_private);
+ struct nc_entry *modified_partition;
+ struct ldb_control *partition_ctrl;
+ const struct dsdb_control_current_partition *partition;
+
+ struct ldb_control **controls;
+
+ partition_ctrl = ldb_reply_get_control(ares, DSDB_CONTROL_CURRENT_PARTITION_OID);
+
+ controls = ares->controls;
+ if (ldb_request_get_control(ac->req,
+ DSDB_CONTROL_CURRENT_PARTITION_OID) == NULL) {
+ /*
+ * Remove the current partition control from what we pass up
+ * the chain if it hasn't been requested manually.
+ */
+ controls = ldb_controls_except_specified(ares->controls, ares,
+ partition_ctrl);
+ }
+
+ if (ares->error != LDB_SUCCESS) {
+ struct GUID_txt_buf guid_txt;
+ struct ldb_message *msg = NULL;
+ char *s = NULL;
+
+ if (ac->apply_mode == false) {
+ DBG_NOTICE("Originating update failure. Error is: %s\n",
+ ldb_strerror(ares->error));
+ return ldb_module_done(ac->req, controls,
+ ares->response, ares->error);
+ }
+
+ msg = ac->objs->objects[ac->index_current].msg;
+ /*
+ * Set at DBG_NOTICE as once these start to happe, they
+ * will happen a lot until resolved, due to repeated
+ * replication. The caller will probably print the
+ * ldb error string anyway.
+ */
+ DBG_NOTICE("DRS replication apply failure for %s. Error is: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_strerror(ares->error));
+
+ s = ldb_ldif_message_redacted_string(ldb_module_get_ctx(ac->module),
+ ac,
+ LDB_CHANGETYPE_ADD,
+ msg);
+
+ DBG_INFO("Failing DRS %s replication message was %s:\n%s\n",
+ ac->search_msg == NULL ? "ADD" : "MODIFY",
+ GUID_buf_string(&ac->objs->objects[ac->index_current].object_guid,
+ &guid_txt),
+ s);
+ talloc_free(s);
+ return ldb_module_done(ac->req, controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ ldb_set_errstring(ldb_module_get_ctx(ac->module), "Invalid reply type for notify\n!");
+ return ldb_module_done(ac->req, NULL,
+ NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ac->apply_mode == false) {
+ struct la_backlink *bl;
+ /*
+ * process our backlink list after an replmd_add(),
+ * creating and deleting backlinks as necessary (this
+ * code is sync). The other cases are handled inline
+ * with the modify.
+ */
+ for (bl=ac->la_backlinks; bl; bl=bl->next) {
+ ret = replmd_process_backlink(ac->module, bl, ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL,
+ NULL, ret);
+ }
+ }
+ }
+
+ if (!partition_ctrl) {
+ ldb_set_errstring(ldb_module_get_ctx(ac->module),"No partition control on reply");
+ return ldb_module_done(ac->req, NULL,
+ NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ partition = talloc_get_type_abort(partition_ctrl->data,
+ struct dsdb_control_current_partition);
+
+ if (ac->seq_num > 0) {
+ for (modified_partition = replmd_private->ncs; modified_partition;
+ modified_partition = modified_partition->next) {
+ if (ldb_dn_compare(modified_partition->dn, partition->dn) == 0) {
+ break;
+ }
+ }
+
+ if (modified_partition == NULL) {
+ modified_partition = talloc_zero(replmd_private, struct nc_entry);
+ if (!modified_partition) {
+ ldb_oom(ldb_module_get_ctx(ac->module));
+ return ldb_module_done(ac->req, NULL,
+ NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+ modified_partition->dn = ldb_dn_copy(modified_partition, partition->dn);
+ if (!modified_partition->dn) {
+ ldb_oom(ldb_module_get_ctx(ac->module));
+ return ldb_module_done(ac->req, NULL,
+ NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+ DLIST_ADD(replmd_private->ncs, modified_partition);
+ }
+
+ if (ac->seq_num > modified_partition->mod_usn) {
+ modified_partition->mod_usn = ac->seq_num;
+ if (ac->is_urgent) {
+ modified_partition->mod_usn_urgent = ac->seq_num;
+ }
+ }
+ if (!ac->apply_mode) {
+ replmd_private->originating_updates = true;
+ }
+ }
+
+ if (ac->apply_mode) {
+ ret = replmd_replicated_apply_isDeleted(ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ return ret;
+ } else {
+ /* free the partition control container here, for the
+ * common path. Other cases will have it cleaned up
+ * eventually with the ares */
+ talloc_free(partition_ctrl);
+ return ldb_module_done(ac->req, controls,
+ ares->response, LDB_SUCCESS);
+ }
+}
+
+
+/*
+ * update a @REPLCHANGED record in each partition if there have been
+ * any writes of replicated data in the partition
+ */
+static int replmd_notify_store(struct ldb_module *module, struct ldb_request *parent)
+{
+ struct replmd_private *replmd_private =
+ talloc_get_type(ldb_module_get_private(module), struct replmd_private);
+
+ while (replmd_private->ncs) {
+ int ret;
+ struct nc_entry *modified_partition = replmd_private->ncs;
+
+ ret = dsdb_module_save_partition_usn(module, modified_partition->dn,
+ modified_partition->mod_usn,
+ modified_partition->mod_usn_urgent, parent);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to save partition uSN for %s\n",
+ ldb_dn_get_linearized(modified_partition->dn)));
+ return ret;
+ }
+
+ if (ldb_dn_compare(modified_partition->dn,
+ replmd_private->schema_dn) == 0) {
+ struct ldb_result *ext_res;
+ ret = dsdb_module_extended(module,
+ replmd_private->schema_dn,
+ &ext_res,
+ DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID,
+ ext_res,
+ DSDB_FLAG_NEXT_MODULE,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_free(ext_res);
+ }
+
+ DLIST_REMOVE(replmd_private->ncs, modified_partition);
+ talloc_free(modified_partition);
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ created a replmd_replicated_request context
+ */
+static struct replmd_replicated_request *replmd_ctx_init(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct replmd_replicated_request *ac;
+ const struct GUID *our_invocation_id;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = talloc_zero(req, struct replmd_replicated_request);
+ if (ac == NULL) {
+ ldb_oom(ldb);
+ return NULL;
+ }
+
+ ac->module = module;
+ ac->req = req;
+
+ ac->schema = dsdb_get_schema(ldb, ac);
+ if (!ac->schema) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "replmd_modify: no dsdb_schema loaded");
+ DEBUG(0,(__location__ ": %s\n", ldb_errstring(ldb)));
+ talloc_free(ac);
+ return NULL;
+ }
+
+ /* get our invocationId */
+ our_invocation_id = samdb_ntds_invocation_id(ldb);
+ if (!our_invocation_id) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "replmd_add: unable to find invocationId\n");
+ talloc_free(ac);
+ return NULL;
+ }
+ ac->our_invocation_id = *our_invocation_id;
+
+ return ac;
+}
+
+/*
+ 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;
+ int ret;
+
+ if (ldb_msg_find_element(msg, attr) != NULL) {
+ return LDB_SUCCESS;
+ }
+
+ s = ldb_timestring(msg, t);
+ if (s == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_msg_add_string(msg, attr, s);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ 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 LDB_SUCCESS;
+}
+
+/*
+ add a uint64_t element to a record
+*/
+static int add_uint64_element(struct ldb_context *ldb, struct ldb_message *msg,
+ const char *attr, uint64_t v)
+{
+ struct ldb_message_element *el;
+ int ret;
+
+ if (ldb_msg_find_element(msg, attr) != NULL) {
+ return LDB_SUCCESS;
+ }
+
+ ret = samdb_msg_add_uint64(ldb, msg, msg, attr, v);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ 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 LDB_SUCCESS;
+}
+
+static int replmd_replPropertyMetaData1_attid_sort(const struct replPropertyMetaData1 *m1,
+ const struct replPropertyMetaData1 *m2)
+{
+ /*
+ * This assignment seems inoccous, but it is critical for the
+ * system, as we need to do the comparisons as a unsigned
+ * quantity, not signed (enums are signed integers)
+ */
+ uint32_t attid_1 = m1->attid;
+ uint32_t attid_2 = m2->attid;
+
+ if (attid_1 == attid_2) {
+ return 0;
+ }
+
+ /*
+ * See above regarding this being an unsigned comparison.
+ * Otherwise when the high bit is set on non-standard
+ * attributes, they would end up first, before objectClass
+ * (0).
+ */
+ return attid_1 > attid_2 ? 1 : -1;
+}
+
+static int replmd_replPropertyMetaDataCtr1_verify(struct ldb_context *ldb,
+ struct replPropertyMetaDataCtr1 *ctr1,
+ struct ldb_dn *dn)
+{
+ if (ctr1->count == 0) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "No elements found in replPropertyMetaData for %s!\n",
+ ldb_dn_get_linearized(dn));
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ /* the objectClass attribute is value 0x00000000, so must be first */
+ if (ctr1->array[0].attid != DRSUAPI_ATTID_objectClass) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "No objectClass found in replPropertyMetaData for %s!\n",
+ ldb_dn_get_linearized(dn));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int replmd_replPropertyMetaDataCtr1_sort_and_verify(struct ldb_context *ldb,
+ struct replPropertyMetaDataCtr1 *ctr1,
+ struct ldb_dn *dn)
+{
+ /* Note this is O(n^2) for the almost-sorted case, which this is */
+ TYPESAFE_QSORT(ctr1->array, ctr1->count,
+ replmd_replPropertyMetaData1_attid_sort);
+ return replmd_replPropertyMetaDataCtr1_verify(ldb, ctr1, dn);
+}
+
+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);
+ }
+ if (a1->attributeID_id == a2->attributeID_id) {
+ return 0;
+ }
+ return a1->attributeID_id > a2->attributeID_id ? 1 : -1;
+}
+
+static void replmd_ldb_message_sort(struct ldb_message *msg,
+ const struct dsdb_schema *schema)
+{
+ LDB_TYPESAFE_QSORT(msg->elements, msg->num_elements, schema, replmd_ldb_message_element_attid_sort);
+}
+
+static int replmd_build_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct dsdb_dn *dsdb_dn,
+ const struct GUID *invocation_id,
+ uint64_t local_usn, NTTIME nttime);
+
+static int parsed_dn_compare(struct parsed_dn *pdn1, struct parsed_dn *pdn2);
+
+static int get_parsed_dns(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *el, struct parsed_dn **pdn,
+ const char *ldap_oid, struct ldb_request *parent);
+
+static int check_parsed_dn_duplicates(struct ldb_module *module,
+ struct ldb_message_element *el,
+ struct parsed_dn *pdn);
+
+/*
+ fix up linked attributes in replmd_add.
+ This involves setting up the right meta-data in extended DN
+ components, and creating backlinks to the object
+ */
+static int replmd_add_fix_la(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct replmd_private *replmd_private,
+ struct ldb_message_element *el,
+ struct replmd_replicated_request *ac,
+ NTTIME now,
+ struct ldb_dn *forward_dn,
+ const struct dsdb_attribute *sa,
+ struct ldb_request *parent)
+{
+ unsigned int i;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct parsed_dn *pdn;
+ /* We will take a reference to the schema in replmd_add_backlink */
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb, NULL);
+ struct ldb_val *new_values = NULL;
+ int ret;
+
+ if (dsdb_check_single_valued_link(sa, el) == LDB_SUCCESS) {
+ el->flags |= LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK;
+ } else {
+ ldb_asprintf_errstring(ldb,
+ "Attribute %s is single valued but "
+ "more than one value has been supplied",
+ el->name);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ /*
+ * At the successful end of these functions el->values is
+ * overwritten with new_values. However get_parsed_dns()
+ * points p->v at the supplied el and it effectively gets used
+ * as a working area by replmd_build_la_val(). So we must
+ * duplicate it because our caller only called
+ * ldb_msg_copy_shallow().
+ */
+
+ el->values = talloc_memdup(tmp_ctx,
+ el->values,
+ sizeof(el->values[0]) * el->num_values);
+ if (el->values == NULL) {
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = get_parsed_dns(module, tmp_ctx, el, &pdn,
+ sa->syntax->ldap_oid, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = check_parsed_dn_duplicates(module, el, pdn);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ new_values = talloc_array(tmp_ctx, struct ldb_val, el->num_values);
+ if (new_values == NULL) {
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ struct parsed_dn *p = &pdn[i];
+ ret = replmd_build_la_val(new_values, p->v, p->dsdb_dn,
+ &ac->our_invocation_id,
+ ac->seq_num, now);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = replmd_defer_add_backlink(module, replmd_private,
+ schema, ac,
+ forward_dn, &p->guid, true, sa,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ new_values[i] = *p->v;
+ }
+ el->values = talloc_steal(mem_ctx, new_values);
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+static int replmd_add_make_extended_dn(struct ldb_request *req,
+ const DATA_BLOB *guid_blob,
+ struct ldb_dn **_extended_dn)
+{
+ int ret;
+ const DATA_BLOB *sid_blob;
+ /* Calculate an extended DN for any linked attributes */
+ struct ldb_dn *extended_dn = ldb_dn_copy(req, req->op.add.message->dn);
+ if (!extended_dn) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret = ldb_dn_set_extended_component(extended_dn, "GUID", guid_blob);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ sid_blob = ldb_msg_find_ldb_val(req->op.add.message, "objectSID");
+ if (sid_blob != NULL) {
+ ret = ldb_dn_set_extended_component(extended_dn, "SID", sid_blob);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ *_extended_dn = extended_dn;
+ return LDB_SUCCESS;
+}
+
+/*
+ intercept add requests
+ */
+static int replmd_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct ldb_control *control;
+ struct replmd_replicated_request *ac;
+ enum ndr_err_code ndr_err;
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ const DATA_BLOB *guid_blob;
+ DATA_BLOB guid_blob_stack;
+ struct GUID guid;
+ uint8_t guid_data[16];
+ struct replPropertyMetaDataBlob nmd;
+ struct ldb_val nmd_value;
+ struct ldb_dn *extended_dn = NULL;
+
+ /*
+ * The use of a time_t here seems odd, but as the NTTIME
+ * elements are actually declared as NTTIME_1sec in the IDL,
+ * getting a higher resolution timestamp is not required.
+ */
+ time_t t = time(NULL);
+ NTTIME now;
+ char *time_str;
+ int ret;
+ unsigned int i;
+ unsigned int functional_level;
+ uint32_t ni=0;
+ bool allow_add_guid = false;
+ bool remove_current_guid = false;
+ bool is_urgent = false;
+ bool is_schema_nc = false;
+ struct ldb_message_element *objectclass_el;
+ struct replmd_private *replmd_private =
+ talloc_get_type_abort(ldb_module_get_private(module), struct replmd_private);
+
+ /* check if there's a show relax control (used by provision to say 'I know what I'm doing') */
+ control = ldb_request_get_control(req, LDB_CONTROL_RELAX_OID);
+ if (control) {
+ allow_add_guid = true;
+ }
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_add\n");
+
+ guid_blob = ldb_msg_find_ldb_val(req->op.add.message, "objectGUID");
+ if (guid_blob != NULL) {
+ if (!allow_add_guid) {
+ ldb_set_errstring(ldb,
+ "replmd_add: it's not allowed to add an object with objectGUID!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ } else {
+ NTSTATUS status = GUID_from_data_blob(guid_blob,&guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_set_errstring(ldb,
+ "replmd_add: Unable to parse the 'objectGUID' as a GUID!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ /* we remove this attribute as it can be a string and
+ * will not be treated correctly and then we will re-add
+ * it later on in the good format */
+ remove_current_guid = true;
+ }
+ } else {
+ /* a new GUID */
+ guid = GUID_random();
+
+ guid_blob_stack = data_blob_const(guid_data, sizeof(guid_data));
+
+ /* This can't fail */
+ ndr_push_struct_into_fixed_blob(&guid_blob_stack, &guid,
+ (ndr_push_flags_fn_t)ndr_push_GUID);
+ guid_blob = &guid_blob_stack;
+ }
+
+ ac = replmd_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ functional_level = dsdb_functional_level(ldb);
+
+ /* Get a sequence number from the backend */
+ ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, &ac->seq_num);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+
+ /* we have to copy the message as the caller might have it as a const */
+ msg = ldb_msg_copy_shallow(ac, req->op.add.message);
+ if (msg == NULL) {
+ ldb_oom(ldb);
+ talloc_free(ac);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* generated times */
+ unix_to_nt_time(&now, t);
+ time_str = ldb_timestring(msg, t);
+ if (!time_str) {
+ ldb_oom(ldb);
+ talloc_free(ac);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (remove_current_guid) {
+ ldb_msg_remove_attr(msg,"objectGUID");
+ }
+
+ /*
+ * 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) {
+ ldb_oom(ldb);
+ talloc_free(ac);
+ return ret;
+ }
+
+ /* 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) {
+ ldb_oom(ldb);
+ talloc_free(ac);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ is_schema_nc = ldb_dn_compare_base(replmd_private->schema_dn, msg->dn) == 0;
+
+ for (i=0; i < msg->num_elements;) {
+ 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] == '@') {
+ i++;
+ continue;
+ }
+
+ sa = dsdb_attribute_by_lDAPDisplayName(ac->schema, e->name);
+ if (!sa) {
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ "replmd_add: attribute '%s' not defined in schema\n",
+ e->name);
+ talloc_free(ac);
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+
+ if ((sa->systemFlags & DS_FLAG_ATTR_NOT_REPLICATED) || (sa->systemFlags & DS_FLAG_ATTR_IS_CONSTRUCTED)) {
+ /* if the attribute is not replicated (0x00000001)
+ * or constructed (0x00000004) it has no metadata
+ */
+ i++;
+ continue;
+ }
+
+ if (sa->linkID != 0 && functional_level > DS_DOMAIN_FUNCTION_2000) {
+ if (extended_dn == NULL) {
+ ret = replmd_add_make_extended_dn(req,
+ guid_blob,
+ &extended_dn);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ }
+
+ /*
+ * Prepare the context for the backlinks and
+ * create metadata for the forward links. The
+ * backlinks are created in
+ * replmd_op_callback() after the successful
+ * ADD of the object.
+ */
+ ret = replmd_add_fix_la(module, msg->elements,
+ replmd_private, e,
+ ac, now,
+ extended_dn,
+ sa, req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ /* linked attributes are not stored in
+ replPropertyMetaData in FL above w2k */
+ i++;
+ continue;
+ }
+
+ m->attid = dsdb_attribute_get_attid(sa, is_schema_nc);
+ m->version = 1;
+ if (m->attid == DRSUAPI_ATTID_isDeleted) {
+ const struct ldb_val *rdn_val = ldb_dn_get_rdn_val(msg->dn);
+ const char* rdn;
+
+ if (rdn_val == NULL) {
+ ldb_oom(ldb);
+ talloc_free(ac);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ rdn = (const char*)rdn_val->data;
+ if (strcmp(rdn, "Deleted Objects") == 0) {
+ /*
+ * Set the originating_change_time to 29/12/9999 at 23:59:59
+ * as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
+ */
+ m->originating_change_time = DELETED_OBJECT_CONTAINER_CHANGE_TIME;
+ } else {
+ m->originating_change_time = now;
+ }
+ } else {
+ m->originating_change_time = now;
+ }
+ m->originating_invocation_id = ac->our_invocation_id;
+ m->originating_usn = ac->seq_num;
+ m->local_usn = ac->seq_num;
+ ni++;
+
+ if (!(e->flags & DSDB_FLAG_INTERNAL_FORCE_META_DATA)) {
+ i++;
+ continue;
+ }
+
+ e->flags &= ~DSDB_FLAG_INTERNAL_FORCE_META_DATA;
+
+ if (e->num_values != 0) {
+ i++;
+ continue;
+ }
+
+ ldb_msg_remove_element(msg, e);
+ }
+
+ /* fix meta data count */
+ nmd.ctr.ctr1.count = ni;
+
+ /*
+ * sort meta data array
+ */
+ ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &nmd.ctr.ctr1, msg->dn);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "%s: error during direct ADD: %s", __func__, ldb_errstring(ldb));
+ talloc_free(ac);
+ return ret;
+ }
+
+ /* generated NDR encoded values */
+ ndr_err = ndr_push_struct_blob(&nmd_value, msg,
+ &nmd,
+ (ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ ldb_oom(ldb);
+ talloc_free(ac);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * add the autogenerated values
+ */
+ ret = dsdb_msg_add_guid(msg, &guid, "objectGUID");
+ if (ret != LDB_SUCCESS) {
+ ldb_oom(ldb);
+ talloc_free(ac);
+ return ret;
+ }
+ ret = ldb_msg_add_string(msg, "whenChanged", time_str);
+ if (ret != LDB_SUCCESS) {
+ ldb_oom(ldb);
+ talloc_free(ac);
+ return ret;
+ }
+ ret = samdb_msg_add_uint64(ldb, msg, msg, "uSNCreated", ac->seq_num);
+ if (ret != LDB_SUCCESS) {
+ ldb_oom(ldb);
+ talloc_free(ac);
+ return ret;
+ }
+ ret = samdb_msg_add_uint64(ldb, msg, msg, "uSNChanged", ac->seq_num);
+ if (ret != LDB_SUCCESS) {
+ ldb_oom(ldb);
+ talloc_free(ac);
+ return ret;
+ }
+ ret = ldb_msg_add_value(msg, "replPropertyMetaData", &nmd_value, NULL);
+ if (ret != LDB_SUCCESS) {
+ ldb_oom(ldb);
+ talloc_free(ac);
+ return ret;
+ }
+
+ /*
+ * sort the attributes by attid before storing the object
+ */
+ replmd_ldb_message_sort(msg, ac->schema);
+
+ /*
+ * Assert that we do have an objectClass
+ */
+ objectclass_el = ldb_msg_find_element(msg, "objectClass");
+ if (objectclass_el == NULL) {
+ ldb_asprintf_errstring(ldb, __location__
+ ": objectClass missing on %s\n",
+ ldb_dn_get_linearized(msg->dn));
+ talloc_free(ac);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ is_urgent = replmd_check_urgent_objectclass(objectclass_el,
+ REPL_URGENT_ON_CREATE);
+
+ ac->is_urgent = is_urgent;
+ ret = ldb_build_add_req(&down_req, ldb, ac,
+ msg,
+ req->controls,
+ ac, replmd_op_callback,
+ req);
+
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+
+ /* current partition control is needed by "replmd_op_callback" */
+ if (ldb_request_get_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID) == NULL) {
+ ret = ldb_request_add_control(down_req,
+ DSDB_CONTROL_CURRENT_PARTITION_OID,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ }
+
+ if (functional_level == DS_DOMAIN_FUNCTION_2000) {
+ ret = ldb_request_add_control(down_req, DSDB_CONTROL_APPLY_LINKS, false, NULL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ }
+
+ /* mark the relax control done */
+ if (control) {
+ control->critical = 0;
+ }
+ /* go on with the call chain */
+ return ldb_next_request(module, down_req);
+}
+
+
+/*
+ * update the replPropertyMetaData for one element
+ */
+static int replmd_update_rpmd_element(struct ldb_context *ldb,
+ struct ldb_message *msg,
+ struct ldb_message_element *el,
+ struct ldb_message_element *old_el,
+ struct replPropertyMetaDataBlob *omd,
+ const struct dsdb_schema *schema,
+ uint64_t *seq_num,
+ const struct GUID *our_invocation_id,
+ NTTIME now,
+ bool is_schema_nc,
+ bool is_forced_rodc,
+ struct ldb_request *req)
+{
+ uint32_t i;
+ const struct dsdb_attribute *a;
+ struct replPropertyMetaData1 *md1;
+ bool may_skip = false;
+ uint32_t attid;
+
+ a = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (a == NULL) {
+ if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) {
+ /* allow this to make it possible for dbcheck
+ to remove bad attributes */
+ return LDB_SUCCESS;
+ }
+
+ DEBUG(0,(__location__ ": Unable to find attribute %s in schema\n",
+ el->name));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ attid = dsdb_attribute_get_attid(a, is_schema_nc);
+
+ if ((a->systemFlags & DS_FLAG_ATTR_NOT_REPLICATED) || (a->systemFlags & DS_FLAG_ATTR_IS_CONSTRUCTED)) {
+ return LDB_SUCCESS;
+ }
+
+ /*
+ * if the attribute's value haven't changed, and this isn't
+ * just a delete of everything then return LDB_SUCCESS Unless
+ * we have the provision control or if the attribute is
+ * interSiteTopologyGenerator as this page explain:
+ * http://support.microsoft.com/kb/224815 this attribute is
+ * periodicaly written by the DC responsible for the intersite
+ * generation in a given site
+ *
+ * Unchanged could be deleting or replacing an already-gone
+ * thing with an unconstrained delete/empty replace or a
+ * replace with the same value, but not an add with the same
+ * value because that could be about adding a duplicate (which
+ * is for someone else to error out on).
+ */
+ if (old_el != NULL && ldb_msg_element_equal_ordered(el, old_el)) {
+ if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_REPLACE) {
+ may_skip = true;
+ }
+ } else if (old_el == NULL && el->num_values == 0) {
+ if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_REPLACE) {
+ may_skip = true;
+ } else if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE) {
+ may_skip = true;
+ }
+ } else if (a->linkID != 0 && LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE &&
+ ldb_request_get_control(req, DSDB_CONTROL_REPLMD_VANISH_LINKS) != NULL) {
+ /*
+ * We intentionally skip the version bump when attempting to
+ * vanish links.
+ *
+ * The control is set by dbcheck and expunge-tombstones which
+ * both attempt to be non-replicating. Otherwise, making an
+ * alteration to the replication state would trigger a
+ * broadcast of all expunged objects.
+ */
+ may_skip = true;
+ }
+
+ if (el->flags & DSDB_FLAG_INTERNAL_FORCE_META_DATA) {
+ may_skip = false;
+ el->flags &= ~DSDB_FLAG_INTERNAL_FORCE_META_DATA;
+ }
+
+ if (may_skip) {
+ if (strcmp(el->name, "interSiteTopologyGenerator") != 0 &&
+ !ldb_request_get_control(req, LDB_CONTROL_PROVISION_OID)) {
+ /*
+ * allow this to make it possible for dbcheck
+ * to rebuild broken metadata
+ */
+ return LDB_SUCCESS;
+ }
+ }
+
+ for (i=0; i<omd->ctr.ctr1.count; i++) {
+ /*
+ * First check if we find it under the msDS-IntID,
+ * then check if we find it under the OID and
+ * prefixMap ID.
+ *
+ * This allows the administrator to simply re-write
+ * the attributes and so restore replication, which is
+ * likely what they will try to do.
+ */
+ if (attid == omd->ctr.ctr1.array[i].attid) {
+ break;
+ }
+
+ if (a->attributeID_id == omd->ctr.ctr1.array[i].attid) {
+ break;
+ }
+ }
+
+ if (a->linkID != 0 && dsdb_functional_level(ldb) > DS_DOMAIN_FUNCTION_2000) {
+ /* linked attributes are not stored in
+ replPropertyMetaData in FL above w2k, but we do
+ raise the seqnum for the object */
+ if (*seq_num == 0 &&
+ ldb_sequence_number(ldb, LDB_SEQ_NEXT, seq_num) != LDB_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ return LDB_SUCCESS;
+ }
+
+ if (i == omd->ctr.ctr1.count) {
+ /* we need to add a new one */
+ omd->ctr.ctr1.array = talloc_realloc(msg, omd->ctr.ctr1.array,
+ struct replPropertyMetaData1, omd->ctr.ctr1.count+1);
+ if (omd->ctr.ctr1.array == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ omd->ctr.ctr1.count++;
+ ZERO_STRUCT(omd->ctr.ctr1.array[i]);
+ }
+
+ /* Get a new sequence number from the backend. We only do this
+ * if we have a change that requires a new
+ * replPropertyMetaData element
+ */
+ if (*seq_num == 0) {
+ int ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, seq_num);
+ if (ret != LDB_SUCCESS) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ md1 = &omd->ctr.ctr1.array[i];
+ md1->version++;
+ md1->attid = attid;
+
+ if (md1->attid == DRSUAPI_ATTID_isDeleted) {
+ const struct ldb_val *rdn_val = ldb_dn_get_rdn_val(msg->dn);
+ const char* rdn;
+
+ if (rdn_val == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ rdn = (const char*)rdn_val->data;
+ if (strcmp(rdn, "Deleted Objects") == 0) {
+ /*
+ * Set the originating_change_time to 29/12/9999 at 23:59:59
+ * as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
+ */
+ md1->originating_change_time = DELETED_OBJECT_CONTAINER_CHANGE_TIME;
+ } else {
+ md1->originating_change_time = now;
+ }
+ } else {
+ md1->originating_change_time = now;
+ }
+ md1->originating_invocation_id = *our_invocation_id;
+ md1->originating_usn = *seq_num;
+ md1->local_usn = *seq_num;
+
+ if (is_forced_rodc) {
+ /* Force version to 0 to be overridden later via replication */
+ md1->version = 0;
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * Bump the replPropertyMetaData version on an attribute, and if it
+ * has changed (or forced by leaving rdn_old NULL), update the value
+ * in the entry.
+ *
+ * This is important, as calling a modify operation may not change the
+ * version number if the values appear unchanged, but a rename between
+ * parents bumps this value.
+ *
+ */
+static int replmd_update_rpmd_rdn_attr(struct ldb_context *ldb,
+ struct ldb_message *msg,
+ const struct ldb_val *rdn_new,
+ const struct ldb_val *rdn_old,
+ struct replPropertyMetaDataBlob *omd,
+ struct replmd_replicated_request *ar,
+ NTTIME now,
+ bool is_schema_nc,
+ bool is_forced_rodc)
+{
+ const char *rdn_name = ldb_dn_get_rdn_name(msg->dn);
+ const struct dsdb_attribute *rdn_attr =
+ dsdb_attribute_by_lDAPDisplayName(ar->schema, rdn_name);
+ const char *attr_name = rdn_attr != NULL ?
+ rdn_attr->lDAPDisplayName :
+ rdn_name;
+ struct ldb_message_element new_el = {
+ .flags = LDB_FLAG_MOD_REPLACE,
+ .name = attr_name,
+ .num_values = 1,
+ .values = discard_const_p(struct ldb_val, rdn_new)
+ };
+ struct ldb_message_element old_el = {
+ .flags = LDB_FLAG_MOD_REPLACE,
+ .name = attr_name,
+ .num_values = rdn_old ? 1 : 0,
+ .values = discard_const_p(struct ldb_val, rdn_old)
+ };
+
+ if (ldb_msg_element_equal_ordered(&new_el, &old_el) == false) {
+ int ret = ldb_msg_add(msg, &new_el, LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ldb_oom(ldb);
+ }
+ }
+
+ return replmd_update_rpmd_element(ldb, msg, &new_el, NULL,
+ omd, ar->schema, &ar->seq_num,
+ &ar->our_invocation_id,
+ now, is_schema_nc, is_forced_rodc,
+ ar->req);
+
+}
+
+static uint64_t find_max_local_usn(struct replPropertyMetaDataBlob omd)
+{
+ uint32_t count = omd.ctr.ctr1.count;
+ uint64_t max = 0;
+ uint32_t i;
+ for (i=0; i < count; i++) {
+ struct replPropertyMetaData1 m = omd.ctr.ctr1.array[i];
+ if (max < m.local_usn) {
+ max = m.local_usn;
+ }
+ }
+ return max;
+}
+
+/*
+ * update the replPropertyMetaData object each time we modify an
+ * object. This is needed for DRS replication, as the merge on the
+ * client is based on this object
+ */
+static int replmd_update_rpmd(struct ldb_module *module,
+ const struct dsdb_schema *schema,
+ struct ldb_request *req,
+ const char * const *rename_attrs,
+ struct ldb_message *msg, uint64_t *seq_num,
+ time_t t, bool is_schema_nc,
+ bool *is_urgent, bool *rodc)
+{
+ const struct ldb_val *omd_value;
+ enum ndr_err_code ndr_err;
+ struct replPropertyMetaDataBlob omd;
+ unsigned int i;
+ NTTIME now;
+ const struct GUID *our_invocation_id;
+ int ret;
+ const char * const *attrs = NULL;
+ const char * const attrs2[] = { "uSNChanged", "objectClass", "instanceType", NULL };
+ struct ldb_result *res;
+ struct ldb_context *ldb;
+ struct ldb_message_element *objectclass_el;
+ enum urgent_situation situation;
+ bool rmd_is_provided;
+ bool rmd_is_just_resorted = false;
+ const char *not_rename_attrs[4 + msg->num_elements];
+ bool is_forced_rodc = false;
+
+ if (rename_attrs) {
+ attrs = rename_attrs;
+ } else {
+ for (i = 0; i < msg->num_elements; i++) {
+ not_rename_attrs[i] = msg->elements[i].name;
+ }
+ not_rename_attrs[i] = "replPropertyMetaData";
+ not_rename_attrs[i+1] = "objectClass";
+ not_rename_attrs[i+2] = "instanceType";
+ not_rename_attrs[i+3] = NULL;
+ attrs = not_rename_attrs;
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ ret = samdb_rodc(ldb, rodc);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(4, (__location__ ": unable to tell if we are an RODC\n"));
+ *rodc = false;
+ }
+
+ if (*rodc &&
+ ldb_request_get_control(req, DSDB_CONTROL_FORCE_RODC_LOCAL_CHANGE)) {
+ is_forced_rodc = true;
+ }
+
+ our_invocation_id = samdb_ntds_invocation_id(ldb);
+ if (!our_invocation_id) {
+ /* this happens during an initial vampire while
+ updating the schema */
+ DEBUG(5,("No invocationID - skipping replPropertyMetaData update\n"));
+ return LDB_SUCCESS;
+ }
+
+ unix_to_nt_time(&now, t);
+
+ if (ldb_request_get_control(req, DSDB_CONTROL_CHANGEREPLMETADATA_OID)) {
+ rmd_is_provided = true;
+ if (ldb_request_get_control(req, DSDB_CONTROL_CHANGEREPLMETADATA_RESORT_OID)) {
+ rmd_is_just_resorted = true;
+ }
+ } else {
+ rmd_is_provided = false;
+ }
+
+ /* if isDeleted is present and is TRUE, then we consider we are deleting,
+ * otherwise we consider we are updating */
+ if (ldb_msg_check_string_attribute(msg, "isDeleted", "TRUE")) {
+ situation = REPL_URGENT_ON_DELETE;
+ } else if (rename_attrs) {
+ situation = REPL_URGENT_ON_CREATE | REPL_URGENT_ON_DELETE;
+ } else {
+ situation = REPL_URGENT_ON_UPDATE;
+ }
+
+ if (rmd_is_provided) {
+ /* In this case the change_replmetadata control was supplied */
+ /* We check that it's the only attribute that is provided
+ * (it's a rare case so it's better to keep the code simplier)
+ * We also check that the highest local_usn is bigger or the same as
+ * uSNChanged. */
+ uint64_t db_seq;
+ if( msg->num_elements != 1 ||
+ strncmp(msg->elements[0].name,
+ "replPropertyMetaData", 20) ) {
+ DEBUG(0,(__location__ ": changereplmetada control called without "\
+ "a specified replPropertyMetaData attribute or with others\n"));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (situation != REPL_URGENT_ON_UPDATE) {
+ DEBUG(0,(__location__ ": changereplmetada control can't be called when deleting an object\n"));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
+ if (!omd_value) {
+ DEBUG(0,(__location__ ": replPropertyMetaData was not specified for Object %s\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ndr_err = ndr_pull_struct_blob(omd_value, msg, &omd,
+ (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs2,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SHOW_EXTENDED_DN |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
+ DSDB_SEARCH_REVEAL_INTERNALS, req);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (rmd_is_just_resorted == false) {
+ *seq_num = find_max_local_usn(omd);
+
+ db_seq = ldb_msg_find_attr_as_uint64(res->msgs[0], "uSNChanged", 0);
+
+ /*
+ * The test here now allows for a new
+ * replPropertyMetaData with no change, if was
+ * just dbcheck re-sorting the values.
+ */
+ if (*seq_num <= db_seq) {
+ DEBUG(0,(__location__ ": changereplmetada control provided but max(local_usn)" \
+ " is less than uSNChanged (max = %lld uSNChanged = %lld)\n",
+ (long long)*seq_num, (long long)db_seq));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ } else {
+ /* search for the existing replPropertyMetaDataBlob. We need
+ * to use REVEAL and ask for DNs in storage format to support
+ * the check for values being the same in
+ * replmd_update_rpmd_element()
+ */
+ ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SHOW_EXTENDED_DN |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
+ DSDB_SEARCH_REVEAL_INTERNALS, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ omd_value = ldb_msg_find_ldb_val(res->msgs[0], "replPropertyMetaData");
+ if (!omd_value) {
+ DEBUG(0,(__location__ ": Object %s does not have a replPropertyMetaData attribute\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ndr_err = ndr_pull_struct_blob(omd_value, msg, &omd,
+ (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (omd.version != 1) {
+ DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s\n",
+ omd.version, ldb_dn_get_linearized(msg->dn)));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; i<msg->num_elements;) {
+ struct ldb_message_element *el = &msg->elements[i];
+ struct ldb_message_element *old_el;
+
+ old_el = ldb_msg_find_element(res->msgs[0], el->name);
+ ret = replmd_update_rpmd_element(ldb, msg, el, old_el,
+ &omd, schema, seq_num,
+ our_invocation_id,
+ now, is_schema_nc,
+ is_forced_rodc,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (!*is_urgent && (situation == REPL_URGENT_ON_UPDATE)) {
+ *is_urgent = replmd_check_urgent_attribute(el);
+ }
+
+ if (!(el->flags & DSDB_FLAG_INTERNAL_FORCE_META_DATA)) {
+ i++;
+ continue;
+ }
+
+ el->flags &= ~DSDB_FLAG_INTERNAL_FORCE_META_DATA;
+
+ if (el->num_values != 0) {
+ i++;
+ continue;
+ }
+
+ ldb_msg_remove_element(msg, el);
+ }
+ }
+
+ /*
+ * Assert that we have an objectClass attribute - this is major
+ * corruption if we don't have this!
+ */
+ objectclass_el = ldb_msg_find_element(res->msgs[0], "objectClass");
+ if (objectclass_el != NULL) {
+ /*
+ * Now check if this objectClass means we need to do urgent replication
+ */
+ if (!*is_urgent && replmd_check_urgent_objectclass(objectclass_el,
+ situation)) {
+ *is_urgent = true;
+ }
+ } else if (!ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) {
+ ldb_asprintf_errstring(ldb, __location__
+ ": objectClass missing on %s\n",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ /*
+ * replmd_update_rpmd_element has done an update if the
+ * seq_num is set
+ */
+ if (*seq_num != 0 || rmd_is_just_resorted == true) {
+ struct ldb_val *md_value;
+ struct ldb_message_element *el;
+
+ /*if we are RODC and this is a DRSR update then its ok*/
+ if (!ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)
+ && !ldb_request_get_control(req, DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA)
+ && !is_forced_rodc) {
+ unsigned instanceType;
+
+ if (*rodc) {
+ ldb_set_errstring(ldb, "RODC modify is forbidden!");
+ return LDB_ERR_REFERRAL;
+ }
+
+ instanceType = ldb_msg_find_attr_as_uint(res->msgs[0], "instanceType", INSTANCE_TYPE_WRITE);
+ if (!(instanceType & INSTANCE_TYPE_WRITE)) {
+ return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM,
+ "cannot change replicated attribute on partial replica");
+ }
+ }
+
+ md_value = talloc(msg, struct ldb_val);
+ if (md_value == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &omd.ctr.ctr1, msg->dn);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "%s: %s", __func__, ldb_errstring(ldb));
+ return ret;
+ }
+
+ ndr_err = ndr_push_struct_blob(md_value, msg, &omd,
+ (ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0,(__location__ ": Failed to marshall replPropertyMetaData for %s\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_msg_add_empty(msg, "replPropertyMetaData", LDB_FLAG_MOD_REPLACE, &el);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to add updated replPropertyMetaData %s\n",
+ ldb_dn_get_linearized(msg->dn)));
+ return ret;
+ }
+
+ el->num_values = 1;
+ el->values = md_value;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int parsed_dn_compare(struct parsed_dn *pdn1, struct parsed_dn *pdn2)
+{
+ int ret = ndr_guid_compare(&pdn1->guid, &pdn2->guid);
+ if (ret == 0) {
+ return data_blob_cmp(&pdn1->dsdb_dn->extra_part,
+ &pdn2->dsdb_dn->extra_part);
+ }
+ return ret;
+}
+
+/*
+ get a series of message element values as an array of DNs and GUIDs
+ the result is sorted by GUID
+ */
+static int get_parsed_dns(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *el, struct parsed_dn **pdn,
+ const char *ldap_oid, struct ldb_request *parent)
+{
+ unsigned int i;
+ bool values_are_sorted = true;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ if (el == NULL) {
+ *pdn = NULL;
+ return LDB_SUCCESS;
+ }
+
+ (*pdn) = talloc_array(mem_ctx, struct parsed_dn, el->num_values);
+ if (!*pdn) {
+ ldb_module_oom(module);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ for (i=0; i<el->num_values; i++) {
+ struct ldb_val *v = &el->values[i];
+ NTSTATUS status;
+ struct ldb_dn *dn;
+ struct parsed_dn *p;
+
+ p = &(*pdn)[i];
+
+ p->dsdb_dn = dsdb_dn_parse(*pdn, ldb, v, ldap_oid);
+ if (p->dsdb_dn == NULL) {
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+
+ dn = p->dsdb_dn->dn;
+
+ status = dsdb_get_extended_dn_guid(dn, &p->guid, "GUID");
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) ||
+ unlikely(GUID_all_zero(&p->guid))) {
+ /* we got a DN without a GUID - go find the GUID */
+ int ret = dsdb_module_guid_by_dn(module, dn, &p->guid, parent);
+ if (ret != LDB_SUCCESS) {
+ char *dn_str = NULL;
+ dn_str = ldb_dn_get_extended_linearized(mem_ctx,
+ (dn), 1);
+ ldb_asprintf_errstring(ldb,
+ "Unable to find GUID for DN %s\n",
+ dn_str);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT &&
+ LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE &&
+ ldb_attr_cmp(el->name, "member") == 0) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ return ret;
+ }
+ ret = dsdb_set_extended_dn_guid(dn, &p->guid, "GUID");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ } else if (!NT_STATUS_IS_OK(status)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (i > 0 && values_are_sorted) {
+ int cmp = parsed_dn_compare(p, &(*pdn)[i - 1]);
+ if (cmp < 0) {
+ values_are_sorted = false;
+ }
+ }
+ /* keep a pointer to the original ldb_val */
+ p->v = v;
+ }
+ if (! values_are_sorted) {
+ TYPESAFE_QSORT(*pdn, el->num_values, parsed_dn_compare);
+ }
+ return LDB_SUCCESS;
+}
+
+/*
+ * Get a series of trusted message element values. The result is sorted by
+ * GUID, even though the GUIDs might not be known. That works because we trust
+ * the database to give us the elements like that if the
+ * replmd_private->sorted_links flag is set.
+ *
+ * We also ensure that the links are in the Functional Level 2003
+ * linked attributes format.
+ */
+static int get_parsed_dns_trusted_fallback(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message_element *el,
+ struct parsed_dn **pdn,
+ const char *ldap_oid,
+ struct ldb_request *parent)
+{
+ int ret;
+ if (el == NULL) {
+ *pdn = NULL;
+ return LDB_SUCCESS;
+ }
+
+ if (!replmd_private->sorted_links) {
+ /* We need to sort the list. This is the slow old path we want
+ to avoid.
+ */
+ ret = get_parsed_dns(module, mem_ctx, el, pdn, ldap_oid,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ } else {
+ ret = get_parsed_dns_trusted(mem_ctx, el, pdn);
+ if (ret != LDB_SUCCESS) {
+ ldb_module_oom(module);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ /*
+ * This upgrades links to FL2003 style, and sorts the result
+ * if that was needed.
+ *
+ * TODO: Add a database feature that asserts we have no FL2000
+ * style links to avoid this check or add a feature that
+ * uses a similar check to find sorted/unsorted links
+ * for an on-the-fly upgrade.
+ */
+
+ ret = replmd_check_upgrade_links(ldb_module_get_ctx(module),
+ *pdn, el->num_values,
+ el,
+ ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ Return LDB_SUCCESS if a parsed_dn list contains no duplicate values,
+ otherwise an error code. For compatibility the error code differs depending
+ on whether or not the attribute is "member".
+
+ As always, the parsed_dn list is assumed to be sorted.
+ */
+static int check_parsed_dn_duplicates(struct ldb_module *module,
+ struct ldb_message_element *el,
+ struct parsed_dn *pdn)
+{
+ unsigned int i;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ for (i = 1; i < el->num_values; i++) {
+ struct parsed_dn *p = &pdn[i];
+ if (parsed_dn_compare(p, &pdn[i - 1]) == 0) {
+ ldb_asprintf_errstring(ldb,
+ "Linked attribute %s has "
+ "multiple identical values",
+ el->name);
+ if (ldb_attr_cmp(el->name, "member") == 0) {
+ return LDB_ERR_ENTRY_ALREADY_EXISTS;
+ } else {
+ return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+ }
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+/*
+ build a new extended DN, including all meta data fields
+
+ RMD_FLAGS = DSDB_RMD_FLAG_* bits
+ RMD_ADDTIME = originating_add_time
+ RMD_INVOCID = originating_invocation_id
+ RMD_CHANGETIME = originating_change_time
+ RMD_ORIGINATING_USN = originating_usn
+ RMD_LOCAL_USN = local_usn
+ RMD_VERSION = version
+ */
+static int replmd_build_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v,
+ struct dsdb_dn *dsdb_dn,
+ const struct GUID *invocation_id,
+ uint64_t local_usn, NTTIME nttime)
+{
+ return replmd_set_la_val(mem_ctx, v, dsdb_dn, NULL, invocation_id,
+ local_usn, local_usn, nttime,
+ RMD_VERSION_INITIAL, false);
+}
+
+static int replmd_update_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct dsdb_dn *dsdb_dn,
+ struct dsdb_dn *old_dsdb_dn, const struct GUID *invocation_id,
+ uint64_t seq_num, uint64_t local_usn, NTTIME nttime,
+ bool deleted);
+
+/*
+ check if any links need upgrading from w2k format
+ */
+static int replmd_check_upgrade_links(struct ldb_context *ldb,
+ struct parsed_dn *dns, uint32_t count,
+ struct ldb_message_element *el,
+ const char *ldap_oid)
+{
+ uint32_t i;
+ const struct GUID *invocation_id = NULL;
+ for (i=0; i<count; i++) {
+ NTSTATUS status;
+ uint32_t version;
+ int ret;
+ if (dns[i].dsdb_dn == NULL) {
+ ret = really_parse_trusted_dn(dns, ldb, &dns[i],
+ ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+ }
+
+ status = dsdb_get_extended_dn_uint32(dns[i].dsdb_dn->dn,
+ &version, "RMD_VERSION");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * We optimistically assume they are all the same; if
+ * the first one is fixed, they are all fixed.
+ *
+ * If the first one was *not* fixed and we find a
+ * later one that is, that is an occasion to shout
+ * with DEBUG(0).
+ */
+ if (i == 0) {
+ return LDB_SUCCESS;
+ }
+ DEBUG(0, ("Mixed w2k and fixed format "
+ "linked attributes\n"));
+ continue;
+ }
+
+ if (invocation_id == NULL) {
+ invocation_id = samdb_ntds_invocation_id(ldb);
+ if (invocation_id == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+
+ /* it's an old one that needs upgrading */
+ ret = replmd_update_la_val(el->values, dns[i].v,
+ dns[i].dsdb_dn, dns[i].dsdb_dn,
+ invocation_id, 1, 1, 0, false);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /*
+ * This sort() is critical for the operation of
+ * get_parsed_dns_trusted_fallback() because callers of this function
+ * expect a sorted list, and FL2000 style links are not
+ * sorted. In particular, as well as the upgrade case,
+ * get_parsed_dns_trusted_fallback() is called from
+ * replmd_delete_remove_link() even in FL2000 mode
+ *
+ * We do not normally pay the cost of the qsort() due to the
+ * early return in the RMD_VERSION found case.
+ */
+ TYPESAFE_QSORT(dns, count, parsed_dn_compare);
+ return LDB_SUCCESS;
+}
+
+/*
+ Sets the value for a linked attribute, including all meta data fields
+
+ see replmd_build_la_val for value names
+ */
+static int replmd_set_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct dsdb_dn *dsdb_dn,
+ struct dsdb_dn *old_dsdb_dn, const struct GUID *invocation_id,
+ uint64_t usn, uint64_t local_usn, NTTIME nttime,
+ uint32_t version, bool deleted)
+{
+ struct ldb_dn *dn = dsdb_dn->dn;
+ const char *tstring, *usn_string, *flags_string;
+ struct ldb_val tval;
+ struct ldb_val iid;
+ struct ldb_val usnv, local_usnv;
+ struct ldb_val vers, flagsv;
+ const struct ldb_val *old_addtime = NULL;
+ NTSTATUS status;
+ int ret;
+ const char *dnstring;
+ char *vstring;
+ uint32_t rmd_flags = deleted?DSDB_RMD_FLAG_DELETED:0;
+
+ tstring = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)nttime);
+ if (!tstring) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ tval = data_blob_string_const(tstring);
+
+ usn_string = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)usn);
+ if (!usn_string) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ usnv = data_blob_string_const(usn_string);
+
+ usn_string = talloc_asprintf(mem_ctx, "%llu", (unsigned long long)local_usn);
+ if (!usn_string) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ local_usnv = data_blob_string_const(usn_string);
+
+ status = GUID_to_ndr_blob(invocation_id, dn, &iid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ flags_string = talloc_asprintf(mem_ctx, "%u", rmd_flags);
+ if (!flags_string) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ flagsv = data_blob_string_const(flags_string);
+
+ ret = ldb_dn_set_extended_component(dn, "RMD_FLAGS", &flagsv);
+ if (ret != LDB_SUCCESS) return ret;
+
+ /* get the ADDTIME from the original */
+ if (old_dsdb_dn != NULL) {
+ old_addtime = ldb_dn_get_extended_component(old_dsdb_dn->dn,
+ "RMD_ADDTIME");
+ }
+ if (old_addtime == NULL) {
+ old_addtime = &tval;
+ }
+ if (dsdb_dn != old_dsdb_dn ||
+ ldb_dn_get_extended_component(dn, "RMD_ADDTIME") == NULL) {
+ ret = ldb_dn_set_extended_component(dn, "RMD_ADDTIME", old_addtime);
+ if (ret != LDB_SUCCESS) return ret;
+ }
+
+ /* use our invocation id */
+ ret = ldb_dn_set_extended_component(dn, "RMD_INVOCID", &iid);
+ if (ret != LDB_SUCCESS) return ret;
+
+ /* changetime is the current time */
+ ret = ldb_dn_set_extended_component(dn, "RMD_CHANGETIME", &tval);
+ if (ret != LDB_SUCCESS) return ret;
+
+ /* update the USN */
+ ret = ldb_dn_set_extended_component(dn, "RMD_ORIGINATING_USN", &usnv);
+ if (ret != LDB_SUCCESS) return ret;
+
+ ret = ldb_dn_set_extended_component(dn, "RMD_LOCAL_USN", &local_usnv);
+ if (ret != LDB_SUCCESS) return ret;
+
+ vstring = talloc_asprintf(mem_ctx, "%lu", (unsigned long)version);
+ vers = data_blob_string_const(vstring);
+ ret = ldb_dn_set_extended_component(dn, "RMD_VERSION", &vers);
+ if (ret != LDB_SUCCESS) return ret;
+
+ dnstring = dsdb_dn_get_extended_linearized(mem_ctx, dsdb_dn, 1);
+ if (dnstring == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *v = data_blob_string_const(dnstring);
+
+ return LDB_SUCCESS;
+}
+
+/**
+ * Updates the value for a linked attribute, including all meta data fields
+ */
+static int replmd_update_la_val(TALLOC_CTX *mem_ctx, struct ldb_val *v, struct dsdb_dn *dsdb_dn,
+ struct dsdb_dn *old_dsdb_dn, const struct GUID *invocation_id,
+ uint64_t usn, uint64_t local_usn, NTTIME nttime,
+ bool deleted)
+{
+ uint32_t old_version;
+ uint32_t version = RMD_VERSION_INITIAL;
+ NTSTATUS status;
+
+ /*
+ * We're updating the linked attribute locally, so increase the version
+ * by 1 so that other DCs will see the change when it gets replicated out
+ */
+ status = dsdb_get_extended_dn_uint32(old_dsdb_dn->dn, &old_version,
+ "RMD_VERSION");
+
+ if (NT_STATUS_IS_OK(status)) {
+ version = old_version + 1;
+ }
+
+ return replmd_set_la_val(mem_ctx, v, dsdb_dn, old_dsdb_dn, invocation_id,
+ usn, local_usn, nttime, version, deleted);
+}
+
+/*
+ handle adding a linked attribute
+ */
+static int replmd_modify_la_add(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ struct replmd_replicated_request *ac,
+ struct ldb_message *msg,
+ struct ldb_message_element *el,
+ struct ldb_message_element *old_el,
+ const struct dsdb_attribute *schema_attr,
+ time_t t,
+ struct ldb_dn *msg_dn,
+ struct ldb_request *parent)
+{
+ unsigned int i, j;
+ struct parsed_dn *dns, *old_dns;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg);
+ int ret;
+ struct ldb_val *new_values = NULL;
+ unsigned old_num_values = old_el ? old_el->num_values : 0;
+ unsigned num_values = 0;
+ unsigned max_num_values;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ NTTIME now;
+ unix_to_nt_time(&now, t);
+
+ /* get the DNs to be added, fully parsed.
+ *
+ * We need full parsing because they came off the wire and we don't
+ * trust them, besides which we need their details to know where to put
+ * them.
+ */
+ ret = get_parsed_dns(module, tmp_ctx, el, &dns,
+ schema_attr->syntax->ldap_oid, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* get the existing DNs, lazily parsed */
+ ret = get_parsed_dns_trusted_fallback(module, replmd_private,
+ tmp_ctx, old_el, &old_dns,
+ schema_attr->syntax->ldap_oid,
+ parent);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ max_num_values = old_num_values + el->num_values;
+ if (max_num_values < old_num_values) {
+ DEBUG(0, ("we seem to have overflow in replmd_modify_la_add. "
+ "old values: %u, new values: %u, sum: %u\n",
+ old_num_values, el->num_values, max_num_values));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ new_values = talloc_zero_array(tmp_ctx, struct ldb_val, max_num_values);
+
+ if (new_values == NULL) {
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * For each new value, find where it would go in the list. If there is
+ * a matching GUID there, we update the existing value; otherwise we
+ * put it in place.
+ */
+ j = 0;
+ for (i = 0; i < el->num_values; i++) {
+ struct parsed_dn *exact;
+ struct parsed_dn *next;
+ unsigned offset;
+ int err = parsed_dn_find(ldb, old_dns, old_num_values,
+ &dns[i].guid,
+ dns[i].dsdb_dn->dn,
+ dns[i].dsdb_dn->extra_part, 0,
+ &exact, &next,
+ schema_attr->syntax->ldap_oid,
+ true);
+ if (err != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return err;
+ }
+
+ if (ac->fix_link_sid) {
+ char *fixed_dnstring = NULL;
+ struct dom_sid tmp_sid = { 0, };
+ DATA_BLOB sid_blob = data_blob_null;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ int num;
+
+ if (exact == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ if (dns[i].dsdb_dn->dn_format != DSDB_NORMAL_DN) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ /*
+ * Only "<GUID=...><SID=...>" is allowed.
+ *
+ * We get the GUID to just to find the old
+ * value and the SID in order to add it
+ * to the found value.
+ */
+
+ num = ldb_dn_get_comp_num(dns[i].dsdb_dn->dn);
+ if (num != 0) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ num = ldb_dn_get_extended_comp_num(dns[i].dsdb_dn->dn);
+ if (num != 2) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ status = dsdb_get_extended_dn_sid(exact->dsdb_dn->dn,
+ &tmp_sid, "SID");
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /* this is what we expect */
+ } else if (NT_STATUS_IS_OK(status)) {
+ struct GUID_txt_buf guid_str;
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "i[%u] SID NOT MISSING... Attribute %s already "
+ "exists for target GUID %s, SID %s, DN: %s",
+ i, el->name,
+ GUID_buf_string(&exact->guid,
+ &guid_str),
+ dom_sid_string(tmp_ctx, &tmp_sid),
+ dsdb_dn_get_extended_linearized(tmp_ctx,
+ exact->dsdb_dn, 1));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+ } else {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ status = dsdb_get_extended_dn_sid(dns[i].dsdb_dn->dn,
+ &tmp_sid, "SID");
+ if (!NT_STATUS_IS_OK(status)) {
+ struct GUID_txt_buf guid_str;
+ ldb_asprintf_errstring(ldb,
+ "NO SID PROVIDED... Attribute %s already "
+ "exists for target GUID %s",
+ el->name,
+ GUID_buf_string(&exact->guid,
+ &guid_str));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+ }
+
+ ndr_err = ndr_push_struct_blob(&sid_blob, tmp_ctx, &tmp_sid,
+ (ndr_push_flags_fn_t)ndr_push_dom_sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ ret = ldb_dn_set_extended_component(exact->dsdb_dn->dn, "SID", &sid_blob);
+ data_blob_free(&sid_blob);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ fixed_dnstring = dsdb_dn_get_extended_linearized(
+ new_values, exact->dsdb_dn, 1);
+ if (fixed_dnstring == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ /*
+ * We just replace the existing value...
+ */
+ *exact->v = data_blob_string_const(fixed_dnstring);
+
+ continue;
+ }
+
+ if (exact != NULL) {
+ /*
+ * We are trying to add one that exists, which is only
+ * allowed if it was previously deleted.
+ *
+ * When we do undelete a link we change it in place.
+ * It will be copied across into the right spot in due
+ * course.
+ */
+ uint32_t rmd_flags;
+ rmd_flags = dsdb_dn_rmd_flags(exact->dsdb_dn->dn);
+
+ if (!(rmd_flags & DSDB_RMD_FLAG_DELETED)) {
+ struct GUID_txt_buf guid_str;
+ ldb_asprintf_errstring(ldb,
+ "Attribute %s already "
+ "exists for target GUID %s",
+ el->name,
+ GUID_buf_string(&exact->guid,
+ &guid_str));
+ talloc_free(tmp_ctx);
+ /* error codes for 'member' need to be
+ special cased */
+ if (ldb_attr_cmp(el->name, "member") == 0) {
+ return LDB_ERR_ENTRY_ALREADY_EXISTS;
+ } else {
+ return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+ }
+ }
+
+ ret = replmd_update_la_val(new_values, exact->v,
+ dns[i].dsdb_dn,
+ exact->dsdb_dn,
+ &ac->our_invocation_id,
+ ac->seq_num, ac->seq_num,
+ now, false);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = replmd_add_backlink(module, replmd_private,
+ ac->schema,
+ msg_dn,
+ &dns[i].guid,
+ true,
+ schema_attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ continue;
+ }
+ /*
+ * Here we don't have an exact match.
+ *
+ * If next is NULL, this one goes beyond the end of the
+ * existing list, so we need to add all of those ones first.
+ *
+ * If next is not NULL, we need to add all the ones before
+ * next.
+ */
+ if (next == NULL) {
+ offset = old_num_values;
+ } else {
+ /* next should have been parsed, but let's make sure */
+ if (next->dsdb_dn == NULL) {
+ ret = really_parse_trusted_dn(tmp_ctx, ldb, next,
+ schema_attr->syntax->ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ offset = MIN(next - old_dns, old_num_values);
+ }
+
+ /* put all the old ones before next on the list */
+ for (; j < offset; j++) {
+ new_values[num_values] = *old_dns[j].v;
+ num_values++;
+ }
+
+ ret = replmd_add_backlink(module, replmd_private,
+ ac->schema, msg_dn,
+ &dns[i].guid,
+ true, schema_attr,
+ parent);
+ /* Make the new linked attribute ldb_val. */
+ ret = replmd_build_la_val(new_values, &new_values[num_values],
+ dns[i].dsdb_dn, &ac->our_invocation_id,
+ ac->seq_num, now);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ num_values++;
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+ /* copy the rest of the old ones (if any) */
+ for (; j < old_num_values; j++) {
+ new_values[num_values] = *old_dns[j].v;
+ num_values++;
+ }
+
+ talloc_steal(msg->elements, new_values);
+ if (old_el != NULL) {
+ talloc_steal(msg->elements, old_el->values);
+ }
+ el->values = new_values;
+ el->num_values = num_values;
+
+ talloc_free(tmp_ctx);
+
+ /* we now tell the backend to replace all existing values
+ with the one we have constructed */
+ el->flags = LDB_FLAG_MOD_REPLACE;
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ handle deleting all active linked attributes
+ */
+static int replmd_modify_la_delete(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ struct replmd_replicated_request *ac,
+ struct ldb_message *msg,
+ struct ldb_message_element *el,
+ struct ldb_message_element *old_el,
+ const struct dsdb_attribute *schema_attr,
+ time_t t,
+ struct ldb_dn *msg_dn,
+ struct ldb_request *parent)
+{
+ unsigned int i;
+ struct parsed_dn *dns, *old_dns;
+ TALLOC_CTX *tmp_ctx = NULL;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_control *vanish_links_ctrl = NULL;
+ bool vanish_links = false;
+ unsigned int num_to_delete = el->num_values;
+ uint32_t rmd_flags;
+ NTTIME now;
+
+ unix_to_nt_time(&now, t);
+
+ if (old_el == NULL || old_el->num_values == 0) {
+ /* there is nothing to delete... */
+ if (num_to_delete == 0) {
+ /* and we're deleting nothing, so that's OK */
+ return LDB_SUCCESS;
+ }
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+
+ tmp_ctx = talloc_new(msg);
+ if (tmp_ctx == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = get_parsed_dns(module, tmp_ctx, el, &dns,
+ schema_attr->syntax->ldap_oid, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = get_parsed_dns_trusted_fallback(module, replmd_private,
+ tmp_ctx, old_el, &old_dns,
+ schema_attr->syntax->ldap_oid,
+ parent);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ vanish_links_ctrl = ldb_request_get_control(parent, DSDB_CONTROL_REPLMD_VANISH_LINKS);
+ if (vanish_links_ctrl) {
+ vanish_links = true;
+ vanish_links_ctrl->critical = false;
+ }
+
+ /* we empty out el->values here to avoid damage if we return early. */
+ el->num_values = 0;
+ el->values = NULL;
+
+ /*
+ * If vanish links is set, we are actually removing members of
+ * old_el->values; otherwise we are just marking them deleted.
+ *
+ * There is a special case when no values are given: we remove them
+ * all. When we have the vanish_links control we just have to remove
+ * the backlinks and change our element to replace the existing values
+ * with the empty list.
+ */
+
+ if (num_to_delete == 0) {
+ for (i = 0; i < old_el->num_values; i++) {
+ struct parsed_dn *p = &old_dns[i];
+ if (p->dsdb_dn == NULL) {
+ ret = really_parse_trusted_dn(tmp_ctx, ldb, p,
+ schema_attr->syntax->ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ ret = replmd_add_backlink(module, replmd_private,
+ ac->schema, msg_dn, &p->guid,
+ false, schema_attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ if (vanish_links) {
+ continue;
+ }
+
+ rmd_flags = dsdb_dn_rmd_flags(p->dsdb_dn->dn);
+ if (rmd_flags & DSDB_RMD_FLAG_DELETED) {
+ continue;
+ }
+
+ ret = replmd_update_la_val(old_el->values, p->v,
+ p->dsdb_dn, p->dsdb_dn,
+ &ac->our_invocation_id,
+ ac->seq_num, ac->seq_num,
+ now, true);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ if (vanish_links) {
+ el->flags = LDB_FLAG_MOD_REPLACE;
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+ }
+
+
+ for (i = 0; i < num_to_delete; i++) {
+ struct parsed_dn *p = &dns[i];
+ struct parsed_dn *exact = NULL;
+ struct parsed_dn *next = NULL;
+ ret = parsed_dn_find(ldb, old_dns, old_el->num_values,
+ &p->guid,
+ NULL,
+ p->dsdb_dn->extra_part, 0,
+ &exact, &next,
+ schema_attr->syntax->ldap_oid,
+ true);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ if (exact == NULL) {
+ struct GUID_txt_buf buf;
+ ldb_asprintf_errstring(ldb, "Attribute %s doesn't "
+ "exist for target GUID %s",
+ el->name,
+ GUID_buf_string(&p->guid, &buf));
+ if (ldb_attr_cmp(el->name, "member") == 0) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ } else {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+ }
+
+ if (vanish_links) {
+ if (CHECK_DEBUGLVL(5)) {
+ rmd_flags = dsdb_dn_rmd_flags(exact->dsdb_dn->dn);
+ if ((rmd_flags & DSDB_RMD_FLAG_DELETED)) {
+ struct GUID_txt_buf buf;
+ const char *guid_str = \
+ GUID_buf_string(&p->guid, &buf);
+ DEBUG(5, ("Deleting deleted linked "
+ "attribute %s to %s, because "
+ "vanish_links control is set\n",
+ el->name, guid_str));
+ }
+ }
+
+ /* remove the backlink */
+ ret = replmd_add_backlink(module,
+ replmd_private,
+ ac->schema,
+ msg_dn,
+ &p->guid,
+ false, schema_attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* We flag the deletion and tidy it up later. */
+ exact->v = NULL;
+ continue;
+ }
+
+ rmd_flags = dsdb_dn_rmd_flags(exact->dsdb_dn->dn);
+
+ if (rmd_flags & DSDB_RMD_FLAG_DELETED) {
+ struct GUID_txt_buf buf;
+ const char *guid_str = GUID_buf_string(&p->guid, &buf);
+ ldb_asprintf_errstring(ldb, "Attribute %s already "
+ "deleted for target GUID %s",
+ el->name, guid_str);
+ if (ldb_attr_cmp(el->name, "member") == 0) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ } else {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+ }
+
+ ret = replmd_update_la_val(old_el->values, exact->v,
+ exact->dsdb_dn, exact->dsdb_dn,
+ &ac->our_invocation_id,
+ ac->seq_num, ac->seq_num,
+ now, true);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ ret = replmd_add_backlink(module, replmd_private,
+ ac->schema, msg_dn,
+ &p->guid,
+ false, schema_attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ if (vanish_links) {
+ unsigned j = 0;
+ struct ldb_val *tmp_vals = NULL;
+
+ tmp_vals = talloc_array(tmp_ctx, struct ldb_val,
+ old_el->num_values);
+ if (tmp_vals == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+ for (i = 0; i < old_el->num_values; i++) {
+ if (old_dns[i].v == NULL) {
+ continue;
+ }
+ tmp_vals[j] = *old_dns[i].v;
+ j++;
+ }
+ for (i = 0; i < j; i++) {
+ old_el->values[i] = tmp_vals[i];
+ }
+ old_el->num_values = j;
+ }
+
+ el->values = talloc_steal(msg->elements, old_el->values);
+ el->num_values = old_el->num_values;
+
+ talloc_free(tmp_ctx);
+
+ /* we now tell the backend to replace all existing values
+ with the one we have constructed */
+ el->flags = LDB_FLAG_MOD_REPLACE;
+
+ return LDB_SUCCESS;
+}
+
+/*
+ handle replacing a linked attribute
+ */
+static int replmd_modify_la_replace(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ struct replmd_replicated_request *ac,
+ struct ldb_message *msg,
+ struct ldb_message_element *el,
+ struct ldb_message_element *old_el,
+ const struct dsdb_attribute *schema_attr,
+ time_t t,
+ struct ldb_dn *msg_dn,
+ struct ldb_request *parent)
+{
+ unsigned int i, old_i, new_i;
+ struct parsed_dn *dns, *old_dns;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg);
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_val *new_values = NULL;
+ const char *ldap_oid = schema_attr->syntax->ldap_oid;
+ unsigned int old_num_values;
+ unsigned int repl_num_values;
+ unsigned int max_num_values;
+ NTTIME now;
+
+ unix_to_nt_time(&now, t);
+
+ /*
+ * The replace operation is unlike the replace and delete cases in that
+ * we need to look at every existing link to see whether it is being
+ * retained or deleted. In other words, we can't avoid parsing the GUIDs.
+ *
+ * As we are trying to combine two sorted lists, the algorithm we use
+ * is akin to the merge phase of a merge sort. We interleave the two
+ * lists, doing different things depending on which side the current
+ * item came from.
+ *
+ * There are three main cases, with some sub-cases.
+ *
+ * - a DN is in the old list but not the new one. It needs to be
+ * marked as deleted (but left in the list).
+ * - maybe it is already deleted, and we have less to do.
+ *
+ * - a DN is in both lists. The old data gets replaced by the new,
+ * and the list doesn't grow. The old link may have been marked as
+ * deleted, in which case we undelete it.
+ *
+ * - a DN is in the new list only. We add it in the right place.
+ */
+
+ old_num_values = old_el ? old_el->num_values : 0;
+ repl_num_values = el->num_values;
+ max_num_values = old_num_values + repl_num_values;
+
+ if (max_num_values == 0) {
+ /* There is nothing to do! */
+ return LDB_SUCCESS;
+ }
+
+ /*
+ * At the successful end of these functions el->values is
+ * overwritten with new_values. However get_parsed_dns()
+ * points p->v at the supplied el and it effectively gets used
+ * as a working area by replmd_build_la_val(). So we must
+ * duplicate it because our caller only called
+ * ldb_msg_copy_shallow().
+ */
+
+ el->values = talloc_memdup(tmp_ctx,
+ el->values,
+ sizeof(el->values[0]) * el->num_values);
+ if (el->values == NULL) {
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = get_parsed_dns(module, tmp_ctx, el, &dns, ldap_oid, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = check_parsed_dn_duplicates(module, el, dns);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = get_parsed_dns(module, tmp_ctx, old_el, &old_dns,
+ ldap_oid, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = replmd_check_upgrade_links(ldb, old_dns, old_num_values,
+ old_el, ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ new_values = talloc_array(tmp_ctx, struct ldb_val, max_num_values);
+ if (new_values == NULL) {
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ old_i = 0;
+ new_i = 0;
+ for (i = 0; i < max_num_values; i++) {
+ int cmp;
+ struct parsed_dn *old_p, *new_p;
+ if (old_i < old_num_values && new_i < repl_num_values) {
+ old_p = &old_dns[old_i];
+ new_p = &dns[new_i];
+ cmp = parsed_dn_compare(old_p, new_p);
+ } else if (old_i < old_num_values) {
+ /* the new list is empty, read the old list */
+ old_p = &old_dns[old_i];
+ new_p = NULL;
+ cmp = -1;
+ } else if (new_i < repl_num_values) {
+ /* the old list is empty, read new list */
+ old_p = NULL;
+ new_p = &dns[new_i];
+ cmp = 1;
+ } else {
+ break;
+ }
+
+ if (cmp < 0) {
+ /*
+ * An old ones that come before the next replacement
+ * (if any). We mark it as deleted and add it to the
+ * final list.
+ */
+ uint32_t rmd_flags = dsdb_dn_rmd_flags(old_p->dsdb_dn->dn);
+ if ((rmd_flags & DSDB_RMD_FLAG_DELETED) == 0) {
+ ret = replmd_update_la_val(new_values, old_p->v,
+ old_p->dsdb_dn,
+ old_p->dsdb_dn,
+ &ac->our_invocation_id,
+ ac->seq_num, ac->seq_num,
+ now, true);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = replmd_add_backlink(module, replmd_private,
+ ac->schema,
+ msg_dn,
+ &old_p->guid, false,
+ schema_attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+ new_values[i] = *old_p->v;
+ old_i++;
+ } else if (cmp == 0) {
+ /*
+ * We are overwriting one. If it was previously
+ * deleted, we need to add a backlink.
+ *
+ * Note that if any RMD_FLAGs in an extended new DN
+ * will be ignored.
+ */
+ uint32_t rmd_flags;
+
+ ret = replmd_update_la_val(new_values, old_p->v,
+ new_p->dsdb_dn,
+ old_p->dsdb_dn,
+ &ac->our_invocation_id,
+ ac->seq_num, ac->seq_num,
+ now, false);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ rmd_flags = dsdb_dn_rmd_flags(old_p->dsdb_dn->dn);
+ if ((rmd_flags & DSDB_RMD_FLAG_DELETED) != 0) {
+ ret = replmd_add_backlink(module, replmd_private,
+ ac->schema,
+ msg_dn,
+ &new_p->guid, true,
+ schema_attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ new_values[i] = *old_p->v;
+ old_i++;
+ new_i++;
+ } else {
+ /*
+ * Replacements that don't match an existing one. We
+ * just add them to the final list.
+ */
+ ret = replmd_build_la_val(new_values,
+ new_p->v,
+ new_p->dsdb_dn,
+ &ac->our_invocation_id,
+ ac->seq_num, now);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ ret = replmd_add_backlink(module, replmd_private,
+ ac->schema,
+ msg_dn,
+ &new_p->guid, true,
+ schema_attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ new_values[i] = *new_p->v;
+ new_i++;
+ }
+ }
+ if (old_el != NULL) {
+ talloc_steal(msg->elements, old_el->values);
+ }
+ el->values = talloc_steal(msg->elements, new_values);
+ el->num_values = i;
+ talloc_free(tmp_ctx);
+
+ el->flags = LDB_FLAG_MOD_REPLACE;
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ handle linked attributes in modify requests
+ */
+static int replmd_modify_handle_linked_attribs(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ struct replmd_replicated_request *ac,
+ struct ldb_message *msg,
+ time_t t,
+ struct ldb_request *parent)
+{
+ struct ldb_result *res;
+ unsigned int i;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_message *old_msg;
+
+ if (dsdb_functional_level(ldb) == DS_DOMAIN_FUNCTION_2000) {
+ /*
+ * Nothing special is required for modifying or vanishing links
+ * in fl2000 since they are just strings in a multi-valued
+ * attribute.
+ */
+ struct ldb_control *ctrl = ldb_request_get_control(parent,
+ DSDB_CONTROL_REPLMD_VANISH_LINKS);
+ if (ctrl) {
+ ctrl->critical = false;
+ }
+ return LDB_SUCCESS;
+ }
+
+ /*
+ * TODO:
+ *
+ * We should restrict this to the intersection of the list of
+ * linked attributes in the schema and the list of attributes
+ * being modified.
+ *
+ * This will help performance a little, as otherwise we have
+ * to allocate the entire object value-by-value.
+ */
+ ret = dsdb_module_search_dn(module, msg, &res, msg->dn, NULL,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_REVEAL_INTERNALS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ old_msg = res->msgs[0];
+
+ for (i=0; i<msg->num_elements; i++) {
+ struct ldb_message_element *el = &msg->elements[i];
+ struct ldb_message_element *old_el, *new_el;
+ unsigned int mod_type = LDB_FLAG_MOD_TYPE(el->flags);
+ const struct dsdb_attribute *schema_attr
+ = dsdb_attribute_by_lDAPDisplayName(ac->schema, el->name);
+ if (!schema_attr) {
+ ldb_asprintf_errstring(ldb,
+ "%s: attribute %s is not a valid attribute in schema",
+ __FUNCTION__, el->name);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ if (schema_attr->linkID == 0) {
+ continue;
+ }
+ if ((schema_attr->linkID & 1) == 1) {
+ struct ldb_control *ctrl;
+
+ ctrl = ldb_request_get_control(parent,
+ DSDB_CONTROL_REPLMD_VANISH_LINKS);
+ if (ctrl != NULL) {
+ ctrl->critical = false;
+ continue;
+ }
+ ctrl = ldb_request_get_control(parent,
+ DSDB_CONTROL_DBCHECK);
+ if (ctrl != NULL) {
+ continue;
+ }
+
+ /* Odd is for the target. Illegal to modify */
+ ldb_asprintf_errstring(ldb,
+ "attribute %s must not be modified directly, it is a linked attribute", el->name);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ old_el = ldb_msg_find_element(old_msg, el->name);
+ switch (mod_type) {
+ case LDB_FLAG_MOD_REPLACE:
+ ret = replmd_modify_la_replace(module, replmd_private,
+ ac, msg, el, old_el,
+ schema_attr, t,
+ old_msg->dn,
+ parent);
+ break;
+ case LDB_FLAG_MOD_DELETE:
+ ret = replmd_modify_la_delete(module, replmd_private,
+ ac, msg, el, old_el,
+ schema_attr, t,
+ old_msg->dn,
+ parent);
+ break;
+ case LDB_FLAG_MOD_ADD:
+ ret = replmd_modify_la_add(module, replmd_private,
+ ac, msg, el, old_el,
+ schema_attr, t,
+ old_msg->dn,
+ parent);
+ break;
+ default:
+ ldb_asprintf_errstring(ldb,
+ "invalid flags 0x%x for %s linked attribute",
+ el->flags, el->name);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = dsdb_check_single_valued_link(schema_attr, el);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "Attribute %s is single valued but more than one value has been supplied",
+ el->name);
+ /* Return codes as found on Windows 2012r2 */
+ if (mod_type == LDB_FLAG_MOD_REPLACE) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ } else {
+ return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+ }
+ } else {
+ el->flags |= LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK;
+ }
+
+ if (old_el) {
+ ldb_msg_remove_attr(old_msg, el->name);
+ }
+ ldb_msg_add_empty(old_msg, el->name, 0, &new_el);
+ new_el->num_values = el->num_values;
+ new_el->values = talloc_steal(msg->elements, el->values);
+
+ /* TODO: this relises a bit too heavily on the exact
+ behaviour of ldb_msg_find_element and
+ ldb_msg_remove_element */
+ old_el = ldb_msg_find_element(msg, el->name);
+ if (old_el != el) {
+ ldb_msg_remove_element(msg, old_el);
+ i--;
+ }
+ }
+
+ talloc_free(res);
+ return ret;
+}
+
+
+static int send_rodc_referral(struct ldb_request *req,
+ struct ldb_context *ldb,
+ struct ldb_dn *dn)
+{
+ char *referral = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+ struct ldb_dn *fsmo_role_dn = NULL;
+ struct ldb_dn *role_owner_dn = NULL;
+ const char *domain = NULL;
+ WERROR werr;
+
+ lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+
+ werr = dsdb_get_fsmo_role_info(req, ldb, DREPL_PDC_MASTER,
+ &fsmo_role_dn, &role_owner_dn);
+
+ if (W_ERROR_IS_OK(werr)) {
+ struct ldb_dn *server_dn = ldb_dn_copy(req, role_owner_dn);
+ if (server_dn != NULL) {
+ ldb_dn_remove_child_components(server_dn, 1);
+ domain = samdb_dn_to_dnshostname(ldb, req,
+ server_dn);
+ }
+ }
+
+ if (domain == NULL) {
+ domain = lpcfg_dnsdomain(lp_ctx);
+ }
+
+ referral = talloc_asprintf(req, "ldap://%s/%s",
+ domain,
+ ldb_dn_get_linearized(dn));
+ if (referral == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return ldb_module_send_referral(req, referral);
+}
+
+
+static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct replmd_replicated_request *ac;
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ time_t t = time(NULL);
+ int ret;
+ bool is_urgent = false, rodc = false;
+ bool is_schema_nc = false;
+ unsigned int functional_level;
+ const struct ldb_message_element *guid_el = NULL;
+ struct ldb_control *sd_propagation_control;
+ struct ldb_control *fix_links_control = NULL;
+ struct ldb_control *fix_dn_name_control = NULL;
+ struct ldb_control *fix_dn_sid_control = NULL;
+ struct replmd_private *replmd_private =
+ talloc_get_type(ldb_module_get_private(module), struct replmd_private);
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ sd_propagation_control = ldb_request_get_control(req,
+ DSDB_CONTROL_SEC_DESC_PROPAGATION_OID);
+ if (sd_propagation_control != NULL) {
+ if (req->op.mod.message->num_elements != 1) {
+ return ldb_module_operr(module);
+ }
+ ret = strcmp(req->op.mod.message->elements[0].name,
+ "nTSecurityDescriptor");
+ if (ret != 0) {
+ return ldb_module_operr(module);
+ }
+
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ fix_links_control = ldb_request_get_control(req,
+ DSDB_CONTROL_DBCHECK_FIX_DUPLICATE_LINKS);
+ if (fix_links_control != NULL) {
+ struct dsdb_schema *schema = NULL;
+ const struct dsdb_attribute *sa = NULL;
+
+ if (req->op.mod.message->num_elements != 1) {
+ return ldb_module_operr(module);
+ }
+
+ if (LDB_FLAG_MOD_TYPE(req->op.mod.message->elements[0].flags) != LDB_FLAG_MOD_REPLACE) {
+ return ldb_module_operr(module);
+ }
+
+ schema = dsdb_get_schema(ldb, req);
+ if (schema == NULL) {
+ return ldb_module_operr(module);
+ }
+
+ sa = dsdb_attribute_by_lDAPDisplayName(schema,
+ req->op.mod.message->elements[0].name);
+ if (sa == NULL) {
+ return ldb_module_operr(module);
+ }
+
+ if (sa->linkID == 0) {
+ return ldb_module_operr(module);
+ }
+
+ fix_links_control->critical = false;
+ return ldb_next_request(module, req);
+ }
+
+ fix_dn_name_control = ldb_request_get_control(req,
+ DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME);
+ if (fix_dn_name_control != NULL) {
+ struct dsdb_schema *schema = NULL;
+ const struct dsdb_attribute *sa = NULL;
+
+ if (req->op.mod.message->num_elements != 2) {
+ return ldb_module_operr(module);
+ }
+
+ if (LDB_FLAG_MOD_TYPE(req->op.mod.message->elements[0].flags) != LDB_FLAG_MOD_DELETE) {
+ return ldb_module_operr(module);
+ }
+
+ if (LDB_FLAG_MOD_TYPE(req->op.mod.message->elements[1].flags) != LDB_FLAG_MOD_ADD) {
+ return ldb_module_operr(module);
+ }
+
+ if (req->op.mod.message->elements[0].num_values != 1) {
+ return ldb_module_operr(module);
+ }
+
+ if (req->op.mod.message->elements[1].num_values != 1) {
+ return ldb_module_operr(module);
+ }
+
+ schema = dsdb_get_schema(ldb, req);
+ if (schema == NULL) {
+ return ldb_module_operr(module);
+ }
+
+ if (ldb_attr_cmp(req->op.mod.message->elements[0].name,
+ req->op.mod.message->elements[1].name) != 0) {
+ return ldb_module_operr(module);
+ }
+
+ sa = dsdb_attribute_by_lDAPDisplayName(schema,
+ req->op.mod.message->elements[0].name);
+ if (sa == NULL) {
+ return ldb_module_operr(module);
+ }
+
+ if (sa->dn_format == DSDB_INVALID_DN) {
+ return ldb_module_operr(module);
+ }
+
+ if (sa->linkID != 0) {
+ return ldb_module_operr(module);
+ }
+
+ /*
+ * If we are run from dbcheck and we are not updating
+ * a link (as these would need to be sorted and so
+ * can't go via such a simple update, then do not
+ * trigger replicated updates and a new USN from this
+ * change, it wasn't a real change, just a new
+ * (correct) string DN
+ */
+
+ fix_dn_name_control->critical = false;
+ return ldb_next_request(module, req);
+ }
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_modify\n");
+
+ guid_el = ldb_msg_find_element(req->op.mod.message, "objectGUID");
+ if (guid_el != NULL) {
+ ldb_set_errstring(ldb,
+ "replmd_modify: it's not allowed to change the objectGUID!");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ ac = replmd_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ functional_level = dsdb_functional_level(ldb);
+
+ /* we have to copy the message as the caller might have it as a const */
+ msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
+ if (msg == NULL) {
+ ldb_oom(ldb);
+ talloc_free(ac);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ fix_dn_sid_control = ldb_request_get_control(req,
+ DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID);
+ if (fix_dn_sid_control != NULL) {
+ const struct dsdb_attribute *sa = NULL;
+
+ if (msg->num_elements != 1) {
+ talloc_free(ac);
+ return ldb_module_operr(module);
+ }
+
+ if (LDB_FLAG_MOD_TYPE(msg->elements[0].flags) != LDB_FLAG_MOD_ADD) {
+ talloc_free(ac);
+ return ldb_module_operr(module);
+ }
+
+ if (msg->elements[0].num_values != 1) {
+ talloc_free(ac);
+ return ldb_module_operr(module);
+ }
+
+ sa = dsdb_attribute_by_lDAPDisplayName(ac->schema,
+ msg->elements[0].name);
+ if (sa == NULL) {
+ talloc_free(ac);
+ return ldb_module_operr(module);
+ }
+
+ if (sa->dn_format != DSDB_NORMAL_DN) {
+ talloc_free(ac);
+ return ldb_module_operr(module);
+ }
+
+ fix_dn_sid_control->critical = false;
+ ac->fix_link_sid = true;
+
+ goto handle_linked_attribs;
+ }
+
+ ldb_msg_remove_attr(msg, "whenChanged");
+ ldb_msg_remove_attr(msg, "uSNChanged");
+
+ is_schema_nc = ldb_dn_compare_base(replmd_private->schema_dn, msg->dn) == 0;
+
+ ret = replmd_update_rpmd(module, ac->schema, req, NULL,
+ msg, &ac->seq_num, t, is_schema_nc,
+ &is_urgent, &rodc);
+ if (rodc && (ret == LDB_ERR_REFERRAL)) {
+ ret = send_rodc_referral(req, ldb, msg->dn);
+ talloc_free(ac);
+ return ret;
+
+ }
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+
+ handle_linked_attribs:
+ ret = replmd_modify_handle_linked_attribs(module, replmd_private,
+ ac, msg, t, req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+
+ /* TODO:
+ * - replace the old object with the newly constructed one
+ */
+
+ ac->is_urgent = is_urgent;
+
+ ret = ldb_build_mod_req(&down_req, ldb, ac,
+ msg,
+ req->controls,
+ ac, replmd_op_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+
+ /* current partition control is needed by "replmd_op_callback" */
+ if (ldb_request_get_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID) == NULL) {
+ ret = ldb_request_add_control(down_req,
+ DSDB_CONTROL_CURRENT_PARTITION_OID,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ }
+
+ /* If we are in functional level 2000, then
+ * replmd_modify_handle_linked_attribs will have done
+ * nothing */
+ if (functional_level == DS_DOMAIN_FUNCTION_2000) {
+ ret = ldb_request_add_control(down_req, DSDB_CONTROL_APPLY_LINKS, false, NULL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ }
+
+ talloc_steal(down_req, msg);
+
+ /* we only change whenChanged and uSNChanged if the seq_num
+ has changed */
+ if (ac->seq_num != 0) {
+ ret = add_time_element(msg, "whenChanged", t);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ ldb_operr(ldb);
+ return ret;
+ }
+
+ ret = add_uint64_element(ldb, msg, "uSNChanged", ac->seq_num);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ ldb_operr(ldb);
+ return ret;
+ }
+ }
+
+ /* go on with the call chain */
+ return ldb_next_request(module, down_req);
+}
+
+static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *ares);
+
+/*
+ handle a rename request
+
+ On a rename we need to do an extra ldb_modify which sets the
+ whenChanged and uSNChanged attributes. We do this in a callback after the success.
+ */
+static int replmd_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct ldb_control *fix_dn_name_control = NULL;
+ struct replmd_replicated_request *ac;
+ int ret;
+ struct ldb_request *down_req;
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.rename.olddn)) {
+ return ldb_next_request(module, req);
+ }
+
+ fix_dn_name_control = ldb_request_get_control(req,
+ DSDB_CONTROL_DBCHECK_FIX_LINK_DN_NAME);
+ if (fix_dn_name_control != NULL) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_rename\n");
+
+ ac = replmd_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ ret = ldb_build_rename_req(&down_req, ldb, ac,
+ ac->req->op.rename.olddn,
+ ac->req->op.rename.newdn,
+ ac->req->controls,
+ ac, replmd_rename_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+
+ /* go on with the call chain */
+ return ldb_next_request(module, down_req);
+}
+
+/* After the rename is compleated, update the whenchanged etc */
+static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct ldb_request *down_req;
+ struct ldb_message *msg;
+ const struct dsdb_attribute *rdn_attr;
+ const char *rdn_name;
+ const struct ldb_val *rdn_val;
+ const char *attrs[5] = { NULL, };
+ time_t t = time(NULL);
+ int ret;
+ bool is_urgent = false, rodc = false;
+ bool is_schema_nc;
+ struct replmd_replicated_request *ac =
+ talloc_get_type(req->context, struct replmd_replicated_request);
+ struct replmd_private *replmd_private =
+ talloc_get_type(ldb_module_get_private(ac->module),
+ struct replmd_private);
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ ldb_set_errstring(ldb,
+ "invalid reply type in repl_meta_data rename callback");
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* TODO:
+ * - replace the old object with the newly constructed one
+ */
+
+ msg = ldb_msg_new(ac);
+ if (msg == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ msg->dn = ac->req->op.rename.newdn;
+
+ is_schema_nc = ldb_dn_compare_base(replmd_private->schema_dn, msg->dn) == 0;
+
+ rdn_name = ldb_dn_get_rdn_name(msg->dn);
+ if (rdn_name == NULL) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ ldb_operr(ldb));
+ }
+
+ /* normalize the rdn attribute name */
+ rdn_attr = dsdb_attribute_by_lDAPDisplayName(ac->schema, rdn_name);
+ if (rdn_attr == NULL) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ ldb_operr(ldb));
+ }
+ rdn_name = rdn_attr->lDAPDisplayName;
+
+ rdn_val = ldb_dn_get_rdn_val(msg->dn);
+ if (rdn_val == NULL) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ ldb_operr(ldb));
+ }
+
+ if (ldb_msg_append_value(msg, rdn_name, rdn_val, LDB_FLAG_MOD_REPLACE) != 0) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ ldb_oom(ldb));
+ }
+ if (ldb_msg_append_value(msg, "name", rdn_val, LDB_FLAG_MOD_REPLACE) != 0) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ ldb_oom(ldb));
+ }
+
+ /*
+ * here we let replmd_update_rpmd() only search for
+ * the existing "replPropertyMetaData" and rdn_name attributes.
+ *
+ * We do not want the existing "name" attribute as
+ * the "name" attribute needs to get the version
+ * updated on rename even if the rdn value hasn't changed.
+ *
+ * This is the diff of the meta data, for a moved user
+ * on a w2k8r2 server:
+ *
+ * # record 1
+ * -dn: CN=sdf df,CN=Users,DC=bla,DC=base
+ * +dn: CN=sdf df,OU=TestOU,DC=bla,DC=base
+ * replPropertyMetaData: NDR: struct replPropertyMetaDataBlob
+ * version : 0x00000001 (1)
+ * reserved : 0x00000000 (0)
+ * @@ -66,11 +66,11 @@ replPropertyMetaData: NDR: struct re
+ * local_usn : 0x00000000000037a5 (14245)
+ * array: struct replPropertyMetaData1
+ * attid : DRSUAPI_ATTID_name (0x90001)
+ * - version : 0x00000001 (1)
+ * - originating_change_time : Wed Feb 9 17:20:49 2011 CET
+ * + version : 0x00000002 (2)
+ * + originating_change_time : Wed Apr 6 15:21:01 2011 CEST
+ * originating_invocation_id: 0d36ca05-5507-4e62-aca3-354bab0d39e1
+ * - originating_usn : 0x00000000000037a5 (14245)
+ * - local_usn : 0x00000000000037a5 (14245)
+ * + originating_usn : 0x0000000000003834 (14388)
+ * + local_usn : 0x0000000000003834 (14388)
+ * array: struct replPropertyMetaData1
+ * attid : DRSUAPI_ATTID_userAccountControl (0x90008)
+ * version : 0x00000004 (4)
+ */
+ attrs[0] = "replPropertyMetaData";
+ attrs[1] = "objectClass";
+ attrs[2] = "instanceType";
+ attrs[3] = rdn_name;
+ attrs[4] = NULL;
+
+ ret = replmd_update_rpmd(ac->module, ac->schema, req, attrs,
+ msg, &ac->seq_num, t,
+ is_schema_nc, &is_urgent, &rodc);
+ if (rodc && (ret == LDB_ERR_REFERRAL)) {
+ ret = send_rodc_referral(req, ldb, ac->req->op.rename.olddn);
+ talloc_free(ares);
+ return ldb_module_done(req, NULL, NULL, ret);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ if (ac->seq_num == 0) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL,
+ ldb_error(ldb, ret,
+ "internal error seq_num == 0"));
+ }
+ ac->is_urgent = is_urgent;
+
+ ret = ldb_build_mod_req(&down_req, ldb, ac,
+ msg,
+ req->controls,
+ ac, replmd_op_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+
+ /* current partition control is needed by "replmd_op_callback" */
+ if (ldb_request_get_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID) == NULL) {
+ ret = ldb_request_add_control(down_req,
+ DSDB_CONTROL_CURRENT_PARTITION_OID,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ }
+
+ talloc_steal(down_req, msg);
+
+ ret = add_time_element(msg, "whenChanged", t);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ ldb_operr(ldb);
+ return ret;
+ }
+
+ ret = add_uint64_element(ldb, msg, "uSNChanged", ac->seq_num);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ ldb_operr(ldb);
+ return ret;
+ }
+
+ /* go on with the call chain - do the modify after the rename */
+ return ldb_next_request(ac->module, down_req);
+}
+
+/*
+ * remove links from objects that point at this object when an object
+ * is deleted. We remove it from the NEXT module per MS-DRSR 5.160
+ * RemoveObj which states that link removal due to the object being
+ * deleted is NOT an originating update - they just go away!
+ *
+ */
+static int replmd_delete_remove_link(struct ldb_module *module,
+ const struct dsdb_schema *schema,
+ struct replmd_private *replmd_private,
+ struct ldb_dn *dn,
+ struct GUID *guid,
+ struct ldb_message_element *el,
+ const struct dsdb_attribute *sa,
+ struct ldb_request *parent,
+ bool *caller_should_vanish)
+{
+ unsigned int i;
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ for (i=0; i<el->num_values; i++) {
+ struct dsdb_dn *dsdb_dn;
+ int ret;
+ struct ldb_message *msg;
+ const struct dsdb_attribute *target_attr;
+ struct ldb_message_element *el2;
+ const char *dn_str;
+ struct ldb_val dn_val;
+ uint32_t dsdb_flags = 0;
+ const char *attrs[] = { NULL, NULL };
+ struct ldb_result *link_res;
+ struct ldb_message *link_msg;
+ struct ldb_message_element *link_el;
+ struct parsed_dn *link_dns;
+ struct parsed_dn *p = NULL, *unused = NULL;
+
+ if (dsdb_dn_is_deleted_val(&el->values[i])) {
+ continue;
+ }
+
+ dsdb_dn = dsdb_dn_parse(tmp_ctx, ldb, &el->values[i], sa->syntax->ldap_oid);
+ if (!dsdb_dn) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* remove the link */
+ msg = ldb_msg_new(tmp_ctx);
+ if (!msg) {
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ msg->dn = dsdb_dn->dn;
+
+ target_attr = dsdb_attribute_by_linkID(schema, sa->linkID ^ 1);
+ if (target_attr == NULL) {
+ continue;
+ }
+ attrs[0] = target_attr->lDAPDisplayName;
+
+ ret = ldb_msg_add_empty(msg, target_attr->lDAPDisplayName,
+ LDB_FLAG_MOD_DELETE, &el2);
+ if (ret != LDB_SUCCESS) {
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &link_res,
+ msg->dn, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_EXTENDED_DN |
+ DSDB_SEARCH_SHOW_RECYCLED,
+ parent);
+
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ DBG_WARNING("Failed to find forward link object %s "
+ "to remove backlink %s on %s",
+ ldb_dn_get_linearized(msg->dn),
+ sa->lDAPDisplayName,
+ ldb_dn_get_linearized(dn));
+ *caller_should_vanish = true;
+ continue;
+ }
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ link_msg = link_res->msgs[0];
+ link_el = ldb_msg_find_element(link_msg,
+ target_attr->lDAPDisplayName);
+ if (link_el == NULL) {
+ DBG_WARNING("Failed to find forward link on %s "
+ "as %s to remove backlink %s on %s",
+ ldb_dn_get_linearized(msg->dn),
+ target_attr->lDAPDisplayName,
+ sa->lDAPDisplayName,
+ ldb_dn_get_linearized(dn));
+ *caller_should_vanish = true;
+ continue;
+ }
+
+ /*
+ * This call 'upgrades' the links in link_dns, but we
+ * do not commit the result back into the database, so
+ * this is safe to call in FL2000 or on databases that
+ * have been run at that level in the past.
+ */
+ ret = get_parsed_dns_trusted_fallback(module, replmd_private,
+ tmp_ctx,
+ link_el, &link_dns,
+ target_attr->syntax->ldap_oid,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = parsed_dn_find(ldb, link_dns, link_el->num_values,
+ guid, dn,
+ data_blob_null, 0,
+ &p, &unused,
+ target_attr->syntax->ldap_oid, false);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (p == NULL) {
+ DBG_WARNING("Failed to find forward link on %s "
+ "as %s to remove backlink %s on %s",
+ ldb_dn_get_linearized(msg->dn),
+ target_attr->lDAPDisplayName,
+ sa->lDAPDisplayName,
+ ldb_dn_get_linearized(dn));
+ *caller_should_vanish = true;
+ continue;
+ }
+
+ /*
+ * If we find a backlink to ourself, we will delete
+ * the forward link before we get to process that
+ * properly, so just let the caller process this via
+ * the forward link.
+ *
+ * We do this once we are sure we have the forward
+ * link (to ourself) in case something is very wrong
+ * and they are out of sync.
+ */
+ if (ldb_dn_compare(dsdb_dn->dn, dn) == 0) {
+ continue;
+ }
+
+ /* This needs to get the Binary DN, by first searching */
+ dn_str = dsdb_dn_get_linearized(tmp_ctx,
+ p->dsdb_dn);
+
+ dn_val = data_blob_string_const(dn_str);
+ el2->values = &dn_val;
+ el2->num_values = 1;
+
+ /*
+ * Ensure that we tell the modification to vanish any linked
+ * attributes (not simply mark them as isDeleted = TRUE)
+ */
+ dsdb_flags |= DSDB_REPLMD_VANISH_LINKS;
+
+ ret = dsdb_module_modify(module, msg, dsdb_flags|DSDB_FLAG_OWN_MODULE, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+
+/*
+ handle update of replication meta data for deletion of objects
+
+ This also handles the mapping of delete to a rename operation
+ to allow deletes to be replicated.
+
+ It also handles the incoming deleted objects, to ensure they are
+ fully deleted here. In that case re_delete is true, and we do not
+ use this as a signal to change the deleted state, just reinforce it.
+
+ */
+static int replmd_delete_internals(struct ldb_module *module, struct ldb_request *req, bool re_delete)
+{
+ int ret = LDB_ERR_OTHER;
+ bool retb, disallow_move_on_delete;
+ struct ldb_dn *old_dn = NULL, *new_dn = NULL;
+ const char *rdn_name;
+ const struct ldb_val *rdn_value, *new_rdn_value;
+ struct GUID guid;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const struct dsdb_schema *schema;
+ struct ldb_message *msg, *old_msg;
+ struct ldb_message_element *el;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_result *res, *parent_res;
+ static const char * const preserved_attrs[] = {
+ /*
+ * This list MUST be kept in case-insensitive sorted order,
+ * as we use it in a binary search with ldb_attr_cmp().
+ *
+ * We get this hard-coded list from
+ * MS-ADTS section 3.1.1.5.5.1.1 "Tombstone Requirements".
+ */
+ "attributeID",
+ "attributeSyntax",
+ "distinguishedName",
+ "dNReferenceUpdate",
+ "dNSHostName",
+ "flatName",
+ "governsID",
+ "groupType",
+ "instanceType",
+ "isDeleted",
+ "isRecycled",
+ "lastKnownParent",
+ "lDAPDisplayName",
+ "legacyExchangeDN",
+ "mS-DS-CreatorSID",
+ "msDS-LastKnownRDN",
+ "msDS-PortLDAP",
+ "mSMQOwnerID",
+ "name",
+ "nCName",
+ "nTSecurityDescriptor",
+ "objectClass",
+ "objectGUID",
+ "objectSid",
+ "oMSyntax",
+ "proxiedObjectName",
+ "replPropertyMetaData",
+ "sAMAccountName",
+ "securityIdentifier",
+ "sIDHistory",
+ "subClassOf",
+ "systemFlags",
+ "trustAttributes",
+ "trustDirection",
+ "trustPartner",
+ "trustType",
+ "userAccountControl",
+ "uSNChanged",
+ "uSNCreated",
+ "whenChanged",
+ "whenCreated",
+ /*
+ * DO NOT JUST APPEND TO THIS LIST.
+ *
+ * In case you missed the note at the top, this list is kept
+ * in case-insensitive sorted order. In the unlikely event you
+ * need to add an attrbute, please add it in the RIGHT PLACE.
+ */
+ };
+ static const char * const all_attrs[] = {
+ DSDB_SECRET_ATTRIBUTES,
+ "*",
+ NULL
+ };
+ static const struct ldb_val true_val = {
+ .data = discard_const_p(uint8_t, "TRUE"),
+ .length = 4
+ };
+
+ unsigned int i;
+ uint32_t dsdb_flags = 0;
+ struct replmd_private *replmd_private;
+ enum deletion_state deletion_state, next_deletion_state;
+
+ if (ldb_dn_is_special(req->op.del.dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /*
+ * We have to allow dbcheck to remove an object that
+ * is beyond repair, and to do so totally. This could
+ * mean we we can get a partial object from the other
+ * DC, causing havoc, so dbcheck suggests
+ * re-replication first. dbcheck sets both DBCHECK
+ * and RELAX in this situation.
+ */
+ if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)
+ && ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) {
+ /* really, really remove it */
+ return ldb_next_request(module, req);
+ }
+
+ tmp_ctx = talloc_new(ldb);
+ if (!tmp_ctx) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ schema = dsdb_get_schema(ldb, tmp_ctx);
+ if (!schema) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ old_dn = ldb_dn_copy(tmp_ctx, req->op.del.dn);
+
+ /* we need the complete msg off disk, so we can work out which
+ attributes need to be removed */
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res, old_dn, all_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_REVEAL_INTERNALS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "repmd_delete: Failed to %s %s, because we failed to find it: %s",
+ re_delete ? "re-delete" : "delete",
+ ldb_dn_get_linearized(old_dn),
+ ldb_errstring(ldb_module_get_ctx(module)));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ old_msg = res->msgs[0];
+
+ replmd_deletion_state(module, old_msg,
+ &deletion_state,
+ &next_deletion_state);
+
+ /* This supports us noticing an incoming isDeleted and acting on it */
+ if (re_delete) {
+ SMB_ASSERT(deletion_state > OBJECT_NOT_DELETED);
+ next_deletion_state = deletion_state;
+ }
+
+ if (next_deletion_state == OBJECT_REMOVED) {
+ /*
+ * We have to prevent objects being deleted, even if
+ * the administrator really wants them gone, as
+ * without the tombstone, we can get a partial object
+ * from the other DC, causing havoc.
+ *
+ * The only other valid case is when the 180 day
+ * timeout has expired, when relax is specified.
+ */
+ if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) {
+ /* it is already deleted - really remove it this time */
+ talloc_free(tmp_ctx);
+ return ldb_next_request(module, req);
+ }
+
+ ldb_asprintf_errstring(ldb, "Refusing to delete tombstone object %s. "
+ "This check is to prevent corruption of the replicated state.",
+ ldb_dn_get_linearized(old_msg->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ rdn_name = ldb_dn_get_rdn_name(old_dn);
+ rdn_value = ldb_dn_get_rdn_val(old_dn);
+ if ((rdn_name == NULL) || (rdn_value == NULL)) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ msg = ldb_msg_new(tmp_ctx);
+ if (msg == NULL) {
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ msg->dn = old_dn;
+
+ /* consider the SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE flag */
+ disallow_move_on_delete =
+ (ldb_msg_find_attr_as_int(old_msg, "systemFlags", 0)
+ & SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE);
+
+ /* work out where we will be renaming this object to */
+ if (!disallow_move_on_delete) {
+ struct ldb_dn *deleted_objects_dn;
+ ret = dsdb_get_deleted_objects_dn(ldb, tmp_ctx, old_dn,
+ &deleted_objects_dn);
+
+ /*
+ * We should not move objects if we can't find the
+ * deleted objects DN. Not moving (or otherwise
+ * harming) the Deleted Objects DN itself is handled
+ * in the caller.
+ */
+ if (re_delete && (ret != LDB_SUCCESS)) {
+ new_dn = ldb_dn_get_parent(tmp_ctx, old_dn);
+ if (new_dn == NULL) {
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ } else if (ret != LDB_SUCCESS) {
+ /* this is probably an attempted delete on a partition
+ * that doesn't allow delete operations, such as the
+ * schema partition */
+ ldb_asprintf_errstring(ldb, "No Deleted Objects container for DN %s",
+ ldb_dn_get_linearized(old_dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ } else {
+ new_dn = deleted_objects_dn;
+ }
+ } else {
+ new_dn = ldb_dn_get_parent(tmp_ctx, old_dn);
+ if (new_dn == NULL) {
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ /* get the objects GUID from the search we just did */
+ guid = samdb_result_guid(old_msg, "objectGUID");
+
+ if (deletion_state == OBJECT_NOT_DELETED) {
+ struct ldb_message_element *is_deleted_el;
+
+ ret = replmd_make_deleted_child_dn(tmp_ctx,
+ ldb,
+ new_dn,
+ rdn_name, rdn_value,
+ guid);
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = ldb_msg_add_value(msg, "isDeleted", &true_val,
+ &is_deleted_el);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, __location__
+ ": Failed to add isDeleted string to the msg");
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ is_deleted_el->flags = LDB_FLAG_MOD_REPLACE;
+ } else {
+ /*
+ * No matter what has happened with other renames etc, try again to
+ * get this to be under the deleted DN. See MS-DRSR 5.160 RemoveObj
+ */
+
+ struct ldb_dn *rdn = ldb_dn_copy(tmp_ctx, old_dn);
+ retb = ldb_dn_remove_base_components(rdn, ldb_dn_get_comp_num(rdn) - 1);
+ if (!retb) {
+ ldb_asprintf_errstring(ldb, __location__
+ ": Unable to add a prepare rdn of %s",
+ ldb_dn_get_linearized(rdn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ SMB_ASSERT(ldb_dn_get_comp_num(rdn) == 1);
+
+ retb = ldb_dn_add_child(new_dn, rdn);
+ if (!retb) {
+ ldb_asprintf_errstring(ldb, __location__
+ ": Unable to add rdn %s to base dn: %s",
+ ldb_dn_get_linearized(rdn),
+ ldb_dn_get_linearized(new_dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ /*
+ now we need to modify the object in the following ways:
+
+ - add isDeleted=TRUE
+ - update rDN and name, with new rDN
+ - remove linked attributes
+ - remove objectCategory and sAMAccountType
+ - remove attribs not on the preserved list
+ - preserved if in above list, or is rDN
+ - remove all linked attribs from this object
+ - remove all links from other objects to this object
+ (note we use the backlinks to do this, so we won't find one-way
+ links that still point to this object, or deactivated two-way
+ links, i.e. 'member' after the user has been removed from the
+ group)
+ - add lastKnownParent
+ - update replPropertyMetaData?
+
+ see MS-ADTS "Tombstone Requirements" section 3.1.1.5.5.1.1
+ */
+
+ if (deletion_state == OBJECT_NOT_DELETED) {
+ struct ldb_dn *parent_dn = ldb_dn_get_parent(tmp_ctx, old_dn);
+ char *parent_dn_str = NULL;
+ struct ldb_message_element *p_el;
+
+ /* we need the storage form of the parent GUID */
+ ret = dsdb_module_search_dn(module, tmp_ctx, &parent_res,
+ parent_dn, NULL,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
+ DSDB_SEARCH_REVEAL_INTERNALS|
+ DSDB_SEARCH_SHOW_RECYCLED, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "repmd_delete: Failed to %s %s, "
+ "because we failed to find it's parent (%s): %s",
+ re_delete ? "re-delete" : "delete",
+ ldb_dn_get_linearized(old_dn),
+ ldb_dn_get_linearized(parent_dn),
+ ldb_errstring(ldb_module_get_ctx(module)));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /*
+ * Now we can use the DB version,
+ * it will have the extended DN info in it
+ */
+ parent_dn = parent_res->msgs[0]->dn;
+ parent_dn_str = ldb_dn_get_extended_linearized(tmp_ctx,
+ parent_dn,
+ 1);
+ if (parent_dn_str == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+
+ ret = ldb_msg_add_steal_string(msg, "lastKnownParent",
+ parent_dn_str);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, __location__
+ ": Failed to add lastKnownParent "
+ "string when deleting %s",
+ ldb_dn_get_linearized(old_dn));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ p_el = ldb_msg_find_element(msg,
+ "lastKnownParent");
+ if (p_el == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_operr(module);
+ }
+ p_el->flags = LDB_FLAG_MOD_REPLACE;
+
+ if (next_deletion_state == OBJECT_DELETED) {
+ ret = ldb_msg_add_value(msg, "msDS-LastKnownRDN", rdn_value, NULL);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, __location__
+ ": Failed to add msDS-LastKnownRDN "
+ "string when deleting %s",
+ ldb_dn_get_linearized(old_dn));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ p_el = ldb_msg_find_element(msg,
+ "msDS-LastKnownRDN");
+ if (p_el == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_operr(module);
+ }
+ p_el->flags = LDB_FLAG_MOD_ADD;
+ }
+ }
+
+ switch (next_deletion_state) {
+
+ case OBJECT_RECYCLED:
+ case OBJECT_TOMBSTONE:
+
+ /*
+ * MS-ADTS 3.1.1.5.5.1.1 Tombstone Requirements
+ * describes what must be removed from a tombstone
+ * object
+ *
+ * MS-ADTS 3.1.1.5.5.1.3 Recycled-Object Requirements
+ * describes what must be removed from a recycled
+ * object
+ *
+ */
+
+ /*
+ * we also mark it as recycled, meaning this object can't be
+ * recovered (we are stripping its attributes).
+ * This is done only if we have this schema object of course ...
+ * This behavior is identical to the one of Windows 2008R2 which
+ * always set the isRecycled attribute, even if the recycle-bin is
+ * not activated and what ever the forest level is.
+ */
+ if (dsdb_attribute_by_lDAPDisplayName(schema, "isRecycled") != NULL) {
+ struct ldb_message_element *is_recycled_el;
+
+ ret = ldb_msg_add_value(msg, "isRecycled", &true_val,
+ &is_recycled_el);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to add isRecycled string to the msg\n"));
+ ldb_module_oom(module);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ is_recycled_el->flags = LDB_FLAG_MOD_REPLACE;
+ }
+
+ replmd_private = talloc_get_type(ldb_module_get_private(module),
+ struct replmd_private);
+ /* work out which of the old attributes we will be removing */
+ for (i=0; i<old_msg->num_elements; i++) {
+ const struct dsdb_attribute *sa;
+ el = &old_msg->elements[i];
+ sa = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ if (!sa) {
+ const char *old_dn_str
+ = ldb_dn_get_linearized(old_dn);
+
+ ldb_asprintf_errstring(ldb,
+ __location__
+ ": Attribute %s "
+ "not found in schema "
+ "when deleting %s. "
+ "Existing record is invalid",
+ el->name,
+ old_dn_str);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (ldb_attr_cmp(el->name, rdn_name) == 0) {
+ /* don't remove the rDN */
+ continue;
+ }
+
+ if (sa->linkID & 1) {
+ bool caller_should_vanish = false;
+ /*
+ * we have a backlink in this object
+ * that needs to be removed. We're not
+ * allowed to remove it directly
+ * however, so we instead setup a
+ * modify to delete the corresponding
+ * forward link
+ */
+ ret = replmd_delete_remove_link(module, schema,
+ replmd_private,
+ old_dn, &guid,
+ el, sa, req,
+ &caller_should_vanish);
+ if (ret != LDB_SUCCESS) {
+ const char *old_dn_str
+ = ldb_dn_get_linearized(old_dn);
+ ldb_asprintf_errstring(ldb,
+ __location__
+ ": Failed to remove backlink of "
+ "%s when deleting %s: %s",
+ el->name,
+ old_dn_str,
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (caller_should_vanish == false) {
+ /*
+ * now we continue, which means we
+ * won't remove this backlink
+ * directly
+ */
+ continue;
+ }
+
+ /*
+ * Otherwise vanish the link, we are
+ * out of sync and the controlling
+ * object does not have the source
+ * link any more
+ */
+
+ dsdb_flags |= DSDB_REPLMD_VANISH_LINKS;
+
+ } else if (sa->linkID == 0) {
+ const char * const *attr = NULL;
+ if (sa->searchFlags & SEARCH_FLAG_PRESERVEONDELETE) {
+ continue;
+ }
+ BINARY_ARRAY_SEARCH_V(preserved_attrs,
+ ARRAY_SIZE(preserved_attrs),
+ el->name,
+ ldb_attr_cmp,
+ attr);
+ /*
+ * If we are preserving, do not do the
+ * ldb_msg_add_empty() below, continue
+ * to the next element
+ */
+ if (attr != NULL) {
+ continue;
+ }
+ } else {
+ /*
+ * Ensure that we tell the modification to vanish any linked
+ * attributes (not simply mark them as isDeleted = TRUE)
+ */
+ dsdb_flags |= DSDB_REPLMD_VANISH_LINKS;
+ }
+ ret = ldb_msg_add_empty(msg, el->name, LDB_FLAG_MOD_DELETE, &el);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ ldb_module_oom(module);
+ return ret;
+ }
+ }
+
+ break;
+
+ case OBJECT_DELETED:
+ /*
+ * MS-ADTS 3.1.1.5.5.1.2 Deleted-Object Requirements
+ * describes what must be removed from a deleted
+ * object
+ */
+
+ ret = ldb_msg_add_empty(msg, "objectCategory", LDB_FLAG_MOD_REPLACE, NULL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ ldb_module_oom(module);
+ return ret;
+ }
+
+ ret = ldb_msg_add_empty(msg, "sAMAccountType", LDB_FLAG_MOD_REPLACE, NULL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ ldb_module_oom(module);
+ return ret;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ if (deletion_state == OBJECT_NOT_DELETED) {
+ const struct dsdb_attribute *sa;
+
+ /* work out what the new rdn value is, for updating the
+ rDN and name fields */
+ new_rdn_value = ldb_dn_get_rdn_val(new_dn);
+ if (new_rdn_value == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ sa = dsdb_attribute_by_lDAPDisplayName(schema, rdn_name);
+ if (!sa) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_msg_add_value(msg, sa->lDAPDisplayName, new_rdn_value,
+ &el);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ el->flags = LDB_FLAG_MOD_REPLACE;
+
+ el = ldb_msg_find_element(old_msg, "name");
+ if (el) {
+ ret = ldb_msg_add_value(msg, "name", new_rdn_value, &el);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ el->flags = LDB_FLAG_MOD_REPLACE;
+ }
+ }
+
+ /*
+ * TODO: Per MS-DRSR 5.160 RemoveObj we should remove links directly, not as an originating update!
+ *
+ */
+
+ /*
+ * No matter what has happned with other renames, try again to
+ * get this to be under the deleted DN.
+ */
+ if (strcmp(ldb_dn_get_linearized(old_dn), ldb_dn_get_linearized(new_dn)) != 0) {
+ /* now rename onto the new DN */
+ ret = dsdb_module_rename(module, old_dn, new_dn, DSDB_FLAG_NEXT_MODULE, req);
+ if (ret != LDB_SUCCESS){
+ DEBUG(0,(__location__ ": Failed to rename object from '%s' to '%s' - %s\n",
+ ldb_dn_get_linearized(old_dn),
+ ldb_dn_get_linearized(new_dn),
+ ldb_errstring(ldb)));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ msg->dn = new_dn;
+ }
+
+ ret = dsdb_module_modify(module, msg, dsdb_flags|DSDB_FLAG_OWN_MODULE, req);
+ if (ret != LDB_SUCCESS) {
+ char *s = NULL;
+ /*
+ * This should not fail, so be quite verbose in the
+ * error handling if it fails
+ */
+ if (strcmp(ldb_dn_get_linearized(old_dn),
+ ldb_dn_get_linearized(new_dn)) != 0) {
+ DBG_NOTICE("Failure to handle '%s' of object %s "
+ "after successful rename to %s. "
+ "Error during tombstone modificaton was: %s\n",
+ re_delete ? "re-delete" : "delete",
+ ldb_dn_get_linearized(new_dn),
+ ldb_dn_get_linearized(old_dn),
+ ldb_errstring(ldb));
+ } else {
+ DBG_NOTICE("Failure to handle '%s' of object %s. "
+ "Error during tombstone modificaton was: %s\n",
+ re_delete ? "re-delete" : "delete",
+ ldb_dn_get_linearized(new_dn),
+ ldb_errstring(ldb));
+ }
+ s = ldb_ldif_message_redacted_string(ldb_module_get_ctx(module),
+ tmp_ctx,
+ LDB_CHANGETYPE_MODIFY,
+ msg);
+
+ DBG_INFO("Failed tombstone modify%s was:\n%s\n",
+ (dsdb_flags & DSDB_REPLMD_VANISH_LINKS) ?
+ " with VANISH_LINKS" : "",
+ s);
+ ldb_asprintf_errstring(ldb,
+ "replmd_delete: Failed to modify"
+ " object %s in '%s' - %s",
+ ldb_dn_get_linearized(old_dn),
+ re_delete ? "re-delete" : "delete",
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ talloc_free(tmp_ctx);
+
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
+}
+
+static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ return replmd_delete_internals(module, req, false);
+}
+
+
+static int replmd_replicated_request_error(struct replmd_replicated_request *ar, int ret)
+{
+ return ret;
+}
+
+static int replmd_replicated_request_werror(struct replmd_replicated_request *ar, WERROR status)
+{
+ int ret = LDB_ERR_OTHER;
+ /* TODO: do some error mapping */
+
+ /* Let the caller know the full WERROR */
+ ar->objs->error = status;
+
+ return ret;
+}
+
+
+static struct replPropertyMetaData1 *
+replmd_replPropertyMetaData1_find_attid(struct replPropertyMetaDataBlob *md_blob,
+ enum drsuapi_DsAttributeId attid)
+{
+ uint32_t i;
+ struct replPropertyMetaDataCtr1 *rpmd_ctr = &md_blob->ctr.ctr1;
+
+ for (i = 0; i < rpmd_ctr->count; i++) {
+ if (rpmd_ctr->array[i].attid == attid) {
+ return &rpmd_ctr->array[i];
+ }
+ }
+ return NULL;
+}
+
+
+/*
+ return true if an update is newer than an existing entry
+ see section 5.11 of MS-ADTS
+*/
+static bool replmd_update_is_newer(const struct GUID *current_invocation_id,
+ const struct GUID *update_invocation_id,
+ uint32_t current_version,
+ uint32_t update_version,
+ NTTIME current_change_time,
+ NTTIME update_change_time)
+{
+ if (update_version != current_version) {
+ return update_version > current_version;
+ }
+ if (update_change_time != current_change_time) {
+ return update_change_time > current_change_time;
+ }
+ return GUID_compare(update_invocation_id, current_invocation_id) > 0;
+}
+
+static bool replmd_replPropertyMetaData1_is_newer(struct replPropertyMetaData1 *cur_m,
+ struct replPropertyMetaData1 *new_m)
+{
+ return replmd_update_is_newer(&cur_m->originating_invocation_id,
+ &new_m->originating_invocation_id,
+ cur_m->version,
+ new_m->version,
+ cur_m->originating_change_time,
+ new_m->originating_change_time);
+}
+
+static bool replmd_replPropertyMetaData1_new_should_be_taken(uint32_t dsdb_repl_flags,
+ struct replPropertyMetaData1 *cur_m,
+ struct replPropertyMetaData1 *new_m)
+{
+ bool cmp;
+
+ /*
+ * If the new replPropertyMetaData entry for this attribute is
+ * not provided (this happens in the case where we look for
+ * ATTID_name, but the name was not changed), then the local
+ * state is clearly still current, as the remote
+ * server didn't send it due to being older the high watermark
+ * USN we sent.
+ */
+ if (new_m == NULL) {
+ return false;
+ }
+
+ if (dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING) {
+ /*
+ * if we compare equal then do an
+ * update. This is used when a client
+ * asks for a FULL_SYNC, and can be
+ * used to recover a corrupt
+ * replica.
+ *
+ * This call is a bit tricky, what we
+ * are doing it turning the 'is_newer'
+ * call into a 'not is older' by
+ * swapping cur_m and new_m, and negating the
+ * outcome.
+ */
+ cmp = !replmd_replPropertyMetaData1_is_newer(new_m,
+ cur_m);
+ } else {
+ cmp = replmd_replPropertyMetaData1_is_newer(cur_m,
+ new_m);
+ }
+ return cmp;
+}
+
+
+/*
+ form a DN for a deleted (DEL:) or conflict (CNF:) DN
+ */
+static int replmd_make_prefix_child_dn(TALLOC_CTX *tmp_ctx,
+ struct ldb_context *ldb,
+ struct ldb_dn *dn,
+ const char *four_char_prefix,
+ const char *rdn_name,
+ const struct ldb_val *rdn_value,
+ struct GUID guid)
+{
+ struct ldb_val deleted_child_rdn_val;
+ struct GUID_txt_buf guid_str;
+ int ret;
+ bool retb;
+
+ GUID_buf_string(&guid, &guid_str);
+
+ retb = ldb_dn_add_child_fmt(dn, "X=Y");
+ if (!retb) {
+ ldb_asprintf_errstring(ldb, __location__
+ ": Unable to add a formatted child to dn: %s",
+ ldb_dn_get_linearized(dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * TODO: Per MS-ADTS 3.1.1.5.5 Delete Operation
+ * we should truncate this value to ensure the RDN is not more than 255 chars.
+ *
+ * However we MS-ADTS 3.1.1.5.1.2 Naming Constraints indicates that:
+ *
+ * "Naming constraints are not enforced for replicated
+ * updates." so this is safe and we don't have to work out not
+ * splitting a UTF8 char right now.
+ */
+ deleted_child_rdn_val = ldb_val_dup(tmp_ctx, rdn_value);
+
+ /*
+ * sizeof(guid_str.buf) will always be longer than
+ * strlen(guid_str.buf) but we allocate using this and
+ * waste the trailing bytes to avoid scaring folks
+ * with memcpy() using strlen() below
+ */
+
+ deleted_child_rdn_val.data
+ = talloc_realloc(tmp_ctx, deleted_child_rdn_val.data,
+ uint8_t,
+ rdn_value->length + 5
+ + sizeof(guid_str.buf));
+ if (!deleted_child_rdn_val.data) {
+ ldb_asprintf_errstring(ldb, __location__
+ ": Unable to add a formatted child to dn: %s",
+ ldb_dn_get_linearized(dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ deleted_child_rdn_val.length =
+ rdn_value->length + 5
+ + strlen(guid_str.buf);
+
+ SMB_ASSERT(deleted_child_rdn_val.length <
+ talloc_get_size(deleted_child_rdn_val.data));
+
+ /*
+ * talloc won't allocate more than 256MB so we can't
+ * overflow but just to be sure
+ */
+ if (deleted_child_rdn_val.length < rdn_value->length) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ deleted_child_rdn_val.data[rdn_value->length] = 0x0a;
+ memcpy(&deleted_child_rdn_val.data[rdn_value->length + 1],
+ four_char_prefix, 4);
+ memcpy(&deleted_child_rdn_val.data[rdn_value->length + 5],
+ guid_str.buf,
+ sizeof(guid_str.buf));
+
+ /* Now set the value into the RDN, without parsing it */
+ ret = ldb_dn_set_component(
+ dn,
+ 0,
+ rdn_name,
+ deleted_child_rdn_val);
+
+ return ret;
+}
+
+
+/*
+ form a conflict DN
+ */
+static struct ldb_dn *replmd_conflict_dn(TALLOC_CTX *mem_ctx,
+ struct ldb_context *ldb,
+ struct ldb_dn *dn,
+ struct GUID *guid)
+{
+ const struct ldb_val *rdn_val;
+ const char *rdn_name;
+ struct ldb_dn *new_dn;
+ int ret;
+
+ rdn_val = ldb_dn_get_rdn_val(dn);
+ rdn_name = ldb_dn_get_rdn_name(dn);
+ if (!rdn_val || !rdn_name) {
+ return NULL;
+ }
+
+ new_dn = ldb_dn_get_parent(mem_ctx, dn);
+ if (!new_dn) {
+ return NULL;
+ }
+
+ ret = replmd_make_prefix_child_dn(mem_ctx,
+ ldb, new_dn,
+ "CNF:",
+ rdn_name,
+ rdn_val,
+ *guid);
+ if (ret != LDB_SUCCESS) {
+ return NULL;
+ }
+ return new_dn;
+}
+
+/*
+ form a deleted DN
+ */
+static int replmd_make_deleted_child_dn(TALLOC_CTX *tmp_ctx,
+ struct ldb_context *ldb,
+ struct ldb_dn *dn,
+ const char *rdn_name,
+ const struct ldb_val *rdn_value,
+ struct GUID guid)
+{
+ return replmd_make_prefix_child_dn(tmp_ctx,
+ ldb, dn,
+ "DEL:",
+ rdn_name,
+ rdn_value,
+ guid);
+}
+
+
+/*
+ perform a modify operation which sets the rDN and name attributes to
+ their current values. This has the effect of changing these
+ attributes to have been last updated by the current DC. This is
+ needed to ensure that renames performed as part of conflict
+ resolution are propagated to other DCs
+ */
+static int replmd_name_modify(struct replmd_replicated_request *ar,
+ struct ldb_request *req, struct ldb_dn *dn)
+{
+ struct ldb_message *msg;
+ const char *rdn_name;
+ const struct ldb_val *rdn_val;
+ const struct dsdb_attribute *rdn_attr;
+ int ret;
+
+ msg = ldb_msg_new(req);
+ if (msg == NULL) {
+ goto failed;
+ }
+ msg->dn = dn;
+
+ rdn_name = ldb_dn_get_rdn_name(dn);
+ if (rdn_name == NULL) {
+ goto failed;
+ }
+
+ /* normalize the rdn attribute name */
+ rdn_attr = dsdb_attribute_by_lDAPDisplayName(ar->schema, rdn_name);
+ if (rdn_attr == NULL) {
+ goto failed;
+ }
+ rdn_name = rdn_attr->lDAPDisplayName;
+
+ rdn_val = ldb_dn_get_rdn_val(dn);
+ if (rdn_val == NULL) {
+ goto failed;
+ }
+
+ if (ldb_msg_append_value(msg, rdn_name, rdn_val, LDB_FLAG_MOD_REPLACE) != 0) {
+ goto failed;
+ }
+ if (ldb_msg_append_value(msg, "name", rdn_val, LDB_FLAG_MOD_REPLACE) != 0) {
+ goto failed;
+ }
+
+ /*
+ * We have to mark this as a replicated update otherwise
+ * schema_data may reject a rename in the schema partition
+ */
+
+ ret = dsdb_module_modify(ar->module, msg,
+ DSDB_FLAG_OWN_MODULE|DSDB_FLAG_REPLICATED_UPDATE,
+ req);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to modify rDN/name of DN being DRS renamed '%s' - %s",
+ ldb_dn_get_linearized(dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module))));
+ return ret;
+ }
+
+ talloc_free(msg);
+
+ return LDB_SUCCESS;
+
+failed:
+ talloc_free(msg);
+ DEBUG(0,(__location__ ": Failed to setup modify rDN/name of DN being DRS renamed '%s'",
+ ldb_dn_get_linearized(dn)));
+ return LDB_ERR_OPERATIONS_ERROR;
+}
+
+
+/*
+ callback for conflict DN handling where we have renamed the incoming
+ record. After renaming it, we need to ensure the change of name and
+ rDN for the incoming record is seen as an originating update by this DC.
+
+ This also handles updating lastKnownParent for entries sent to lostAndFound
+ */
+static int replmd_op_name_modify_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct replmd_replicated_request *ar =
+ talloc_get_type_abort(req->context, struct replmd_replicated_request);
+ struct ldb_dn *conflict_dn = NULL;
+ int ret;
+
+ if (ares->error != LDB_SUCCESS) {
+ /* call the normal callback for everything except success */
+ return replmd_op_callback(req, ares);
+ }
+
+ switch (req->operation) {
+ case LDB_ADD:
+ conflict_dn = req->op.add.message->dn;
+ break;
+ case LDB_MODIFY:
+ conflict_dn = req->op.mod.message->dn;
+ break;
+ default:
+ smb_panic("replmd_op_name_modify_callback called in unknown circumstances");
+ }
+
+ /* perform a modify of the rDN and name of the record */
+ ret = replmd_name_modify(ar, req, conflict_dn);
+ if (ret != LDB_SUCCESS) {
+ ares->error = ret;
+ return replmd_op_callback(req, ares);
+ }
+
+ if (ar->objs->objects[ar->index_current].last_known_parent) {
+ struct ldb_message *msg = ldb_msg_new(req);
+ if (msg == NULL) {
+ ldb_module_oom(ar->module);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ msg->dn = req->op.add.message->dn;
+
+ ret = ldb_msg_add_steal_string(msg, "lastKnownParent",
+ ldb_dn_get_extended_linearized(msg, ar->objs->objects[ar->index_current].last_known_parent, 1));
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to add lastKnownParent string to the msg\n"));
+ ldb_module_oom(ar->module);
+ return ret;
+ }
+ msg->elements[0].flags = LDB_FLAG_MOD_REPLACE;
+
+ ret = dsdb_module_modify(ar->module, msg, DSDB_FLAG_OWN_MODULE, req);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to modify lastKnownParent of lostAndFound DN '%s' - %s",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module))));
+ return ret;
+ }
+ TALLOC_FREE(msg);
+ }
+
+ return replmd_op_callback(req, ares);
+}
+
+
+
+/*
+ * A helper for replmd_op_possible_conflict_callback() and
+ * replmd_replicated_handle_rename()
+ */
+static int incoming_dn_should_be_renamed(TALLOC_CTX *mem_ctx,
+ struct replmd_replicated_request *ar,
+ struct ldb_dn *conflict_dn,
+ struct ldb_result **res,
+ bool *rename_incoming_record)
+{
+ int ret;
+ bool rodc;
+ enum ndr_err_code ndr_err;
+ const struct ldb_val *omd_value = NULL;
+ struct replPropertyMetaDataBlob omd, *rmd = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(ar->module);
+ const char *attrs[] = { "replPropertyMetaData", "objectGUID", NULL };
+ struct replPropertyMetaData1 *omd_name = NULL;
+ struct replPropertyMetaData1 *rmd_name = NULL;
+ struct ldb_message *msg = NULL;
+
+ ret = samdb_rodc(ldb, &rodc);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(
+ ldb,
+ "Failed to determine if we are an RODC when attempting "
+ "to form conflict DN: %s",
+ ldb_errstring(ldb));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (rodc) {
+ /*
+ * We are on an RODC, or were a GC for this
+ * partition, so we have to fail this until
+ * someone who owns the partition sorts it
+ * out
+ */
+ ldb_asprintf_errstring(
+ ldb,
+ "Conflict adding object '%s' from incoming replication "
+ "but we are read only for the partition. \n"
+ " - We must fail the operation until a master for this "
+ "partition resolves the conflict",
+ ldb_dn_get_linearized(conflict_dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * first we need the replPropertyMetaData attribute from the
+ * old record
+ */
+ ret = dsdb_module_search_dn(ar->module, mem_ctx, res, conflict_dn,
+ attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_DELETED |
+ DSDB_SEARCH_SHOW_RECYCLED, ar->req);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR(__location__
+ ": Unable to find object for conflicting record '%s'\n",
+ ldb_dn_get_linearized(conflict_dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ msg = (*res)->msgs[0];
+ omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
+ if (omd_value == NULL) {
+ DBG_ERR(__location__
+ ": Unable to find replPropertyMetaData for conflicting "
+ "record '%s'\n",
+ ldb_dn_get_linearized(conflict_dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ndr_err = ndr_pull_struct_blob(
+ omd_value, msg, &omd,
+ (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR(__location__
+ ": Failed to parse old replPropertyMetaData for %s\n",
+ ldb_dn_get_linearized(conflict_dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ rmd = ar->objs->objects[ar->index_current].meta_data;
+
+ /*
+ * we decide which is newer based on the RPMD on the name
+ * attribute. See [MS-DRSR] ResolveNameConflict.
+ *
+ * We expect omd_name to be present, as this is from a local
+ * search, but while rmd_name should have been given to us by
+ * the remote server, if it is missing we just prefer the
+ * local name in
+ * replmd_replPropertyMetaData1_new_should_be_taken()
+ */
+ rmd_name = replmd_replPropertyMetaData1_find_attid(rmd,
+ DRSUAPI_ATTID_name);
+ omd_name = replmd_replPropertyMetaData1_find_attid(&omd,
+ DRSUAPI_ATTID_name);
+ if (!omd_name) {
+ DBG_ERR(__location__
+ ": Failed to find name attribute in "
+ "local LDB replPropertyMetaData for %s\n",
+ ldb_dn_get_linearized(conflict_dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * Should we preserve the current record, and so rename the
+ * incoming record to be a conflict?
+ */
+ *rename_incoming_record =
+ !replmd_replPropertyMetaData1_new_should_be_taken(
+ (ar->objs->dsdb_repl_flags &
+ DSDB_REPL_FLAG_PRIORITISE_INCOMING),
+ omd_name, rmd_name);
+
+ return LDB_SUCCESS;
+}
+
+
+/*
+ callback for replmd_replicated_apply_add()
+ This copes with the creation of conflict records in the case where
+ the DN exists, but with a different objectGUID
+ */
+static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct ldb_reply *ares, int (*callback)(struct ldb_request *req, struct ldb_reply *ares))
+{
+ struct ldb_dn *conflict_dn;
+ struct replmd_replicated_request *ar =
+ talloc_get_type_abort(req->context, struct replmd_replicated_request);
+ struct ldb_result *res;
+ int ret;
+ bool rename_incoming_record;
+ struct ldb_message *msg;
+ struct ldb_request *down_req = NULL;
+
+ /* call the normal callback for success */
+ if (ares->error == LDB_SUCCESS) {
+ return callback(req, ares);
+ }
+
+ /*
+ * we have a conflict, and need to decide if we will keep the
+ * new record or the old record
+ */
+
+ msg = ar->objs->objects[ar->index_current].msg;
+ conflict_dn = msg->dn;
+
+ /* For failures other than conflicts, fail the whole operation here */
+ if (ares->error != LDB_ERR_ENTRY_ALREADY_EXISTS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module), "Failed to locally apply remote add of %s: %s",
+ ldb_dn_get_linearized(conflict_dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module)));
+
+ return ldb_module_done(ar->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+
+ ret = incoming_dn_should_be_renamed(req, ar, conflict_dn, &res,
+ &rename_incoming_record);
+ if (ret != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ if (rename_incoming_record) {
+ struct GUID guid;
+ struct ldb_dn *new_dn;
+
+ guid = samdb_result_guid(msg, "objectGUID");
+ if (GUID_all_zero(&guid)) {
+ DEBUG(0,(__location__ ": Failed to find objectGUID for conflicting incoming record %s\n",
+ ldb_dn_get_linearized(conflict_dn)));
+ goto failed;
+ }
+ new_dn = replmd_conflict_dn(req,
+ ldb_module_get_ctx(ar->module),
+ conflict_dn, &guid);
+ if (new_dn == NULL) {
+ DEBUG(0,(__location__ ": Failed to form conflict DN for %s\n",
+ ldb_dn_get_linearized(conflict_dn)));
+ goto failed;
+ }
+
+ DEBUG(2,(__location__ ": Resolving conflict record via incoming rename '%s' -> '%s'\n",
+ ldb_dn_get_linearized(conflict_dn), ldb_dn_get_linearized(new_dn)));
+
+ /* re-submit the request, but with the new DN */
+ callback = replmd_op_name_modify_callback;
+ msg->dn = new_dn;
+ } else {
+ /* we are renaming the existing record */
+ struct GUID guid;
+ struct ldb_dn *new_dn;
+
+ guid = samdb_result_guid(res->msgs[0], "objectGUID");
+ if (GUID_all_zero(&guid)) {
+ DEBUG(0,(__location__ ": Failed to find objectGUID for existing conflict record %s\n",
+ ldb_dn_get_linearized(conflict_dn)));
+ goto failed;
+ }
+
+ new_dn = replmd_conflict_dn(req,
+ ldb_module_get_ctx(ar->module),
+ conflict_dn, &guid);
+ if (new_dn == NULL) {
+ DEBUG(0,(__location__ ": Failed to form conflict DN for %s\n",
+ ldb_dn_get_linearized(conflict_dn)));
+ goto failed;
+ }
+
+ DEBUG(2,(__location__ ": Resolving conflict record via existing-record rename '%s' -> '%s'\n",
+ ldb_dn_get_linearized(conflict_dn), ldb_dn_get_linearized(new_dn)));
+
+ ret = dsdb_module_rename(ar->module, conflict_dn, new_dn,
+ DSDB_FLAG_OWN_MODULE, req);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to rename conflict dn '%s' to '%s' - %s\n",
+ ldb_dn_get_linearized(conflict_dn),
+ ldb_dn_get_linearized(new_dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module))));
+ goto failed;
+ }
+
+ /*
+ * now we need to ensure that the rename is seen as an
+ * originating update. We do that with a modify.
+ */
+ ret = replmd_name_modify(ar, req, new_dn);
+ if (ret != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ DEBUG(2,(__location__ ": With conflicting record renamed, re-apply replicated creation of '%s'\n",
+ ldb_dn_get_linearized(req->op.add.message->dn)));
+ }
+
+ ret = ldb_build_add_req(&down_req,
+ ldb_module_get_ctx(ar->module),
+ req,
+ msg,
+ ar->controls,
+ ar,
+ callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ goto failed;
+ }
+ LDB_REQ_SET_LOCATION(down_req);
+
+ /* current partition control needed by "repmd_op_callback" */
+ ret = ldb_request_add_control(down_req,
+ DSDB_CONTROL_CURRENT_PARTITION_OID,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ if (ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PARTIAL_REPLICA) {
+ /* this tells the partition module to make it a
+ partial replica if creating an NC */
+ ret = ldb_request_add_control(down_req,
+ DSDB_CONTROL_PARTIAL_REPLICA,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+ }
+
+ /*
+ * Finally we re-run the add, otherwise the new record won't
+ * exist, as we are here because of that exact failure!
+ */
+ return ldb_next_request(ar->module, down_req);
+failed:
+
+ /* on failure make the caller get the error. This means
+ * replication will stop with an error, but there is not much
+ * else we can do.
+ */
+ if (ret == LDB_SUCCESS) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ }
+ return ldb_module_done(ar->req, NULL, NULL,
+ ret);
+}
+
+/*
+ callback for replmd_replicated_apply_add()
+ This copes with the creation of conflict records in the case where
+ the DN exists, but with a different objectGUID
+ */
+static int replmd_op_add_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct replmd_replicated_request *ar =
+ talloc_get_type_abort(req->context, struct replmd_replicated_request);
+
+ if (ar->objs->objects[ar->index_current].last_known_parent) {
+ /* This is like a conflict DN, where we put the object in LostAndFound
+ see MS-DRSR 4.1.10.6.10 FindBestParentObject */
+ return replmd_op_possible_conflict_callback(req, ares, replmd_op_name_modify_callback);
+ }
+
+ return replmd_op_possible_conflict_callback(req, ares, replmd_op_callback);
+}
+
+/*
+ this is called when a new object comes in over DRS
+ */
+static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
+{
+ struct ldb_context *ldb;
+ struct ldb_request *change_req;
+ enum ndr_err_code ndr_err;
+ struct ldb_message *msg;
+ struct replPropertyMetaDataBlob *md;
+ struct ldb_val md_value;
+ unsigned int i;
+ int ret;
+ bool remote_isDeleted = false;
+ bool is_schema_nc;
+ NTTIME now;
+ time_t t = time(NULL);
+ const struct ldb_val *rdn_val;
+ struct replmd_private *replmd_private =
+ talloc_get_type(ldb_module_get_private(ar->module),
+ struct replmd_private);
+ unix_to_nt_time(&now, t);
+
+ ldb = ldb_module_get_ctx(ar->module);
+ msg = ar->objs->objects[ar->index_current].msg;
+ md = ar->objs->objects[ar->index_current].meta_data;
+ is_schema_nc = ldb_dn_compare_base(replmd_private->schema_dn, msg->dn) == 0;
+
+ ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, &ar->seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ ret = dsdb_msg_add_guid(msg,
+ &ar->objs->objects[ar->index_current].object_guid,
+ "objectGUID");
+ 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(ldb, msg, msg, "uSNCreated", ar->seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ ret = samdb_msg_add_uint64(ldb, msg, msg, "uSNChanged", ar->seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ /* remove any message elements that have zero values */
+ for (i=0; i<msg->num_elements; i++) {
+ struct ldb_message_element *el = &msg->elements[i];
+
+ if (el->num_values == 0) {
+ if (ldb_attr_cmp(msg->elements[i].name, "objectClass") == 0) {
+ ldb_asprintf_errstring(ldb, __location__
+ ": empty objectClass sent on %s, aborting replication\n",
+ ldb_dn_get_linearized(msg->dn));
+ return replmd_replicated_request_error(ar, LDB_ERR_OBJECT_CLASS_VIOLATION);
+ }
+
+ DEBUG(4,(__location__ ": Removing attribute %s with num_values==0\n",
+ el->name));
+ ldb_msg_remove_element(msg, &msg->elements[i]);
+ i--;
+ continue;
+ }
+ }
+
+ if (DEBUGLVL(8)) {
+ struct GUID_txt_buf guid_txt;
+
+ char *s = ldb_ldif_message_redacted_string(ldb, ar,
+ LDB_CHANGETYPE_ADD,
+ msg);
+ DEBUG(8, ("DRS replication add message of %s:\n%s\n",
+ GUID_buf_string(&ar->objs->objects[ar->index_current].object_guid, &guid_txt),
+ s));
+ talloc_free(s);
+ } else if (DEBUGLVL(4)) {
+ struct GUID_txt_buf guid_txt;
+ DEBUG(4, ("DRS replication add DN of %s is %s\n",
+ GUID_buf_string(&ar->objs->objects[ar->index_current].object_guid, &guid_txt),
+ ldb_dn_get_linearized(msg->dn)));
+ }
+ remote_isDeleted = ldb_msg_find_attr_as_bool(msg,
+ "isDeleted", false);
+
+ /*
+ * the meta data array is already sorted by the caller, except
+ * for the RDN, which needs to be added.
+ */
+
+
+ rdn_val = ldb_dn_get_rdn_val(msg->dn);
+ ret = replmd_update_rpmd_rdn_attr(ldb, msg, rdn_val, NULL,
+ md, ar, now, is_schema_nc,
+ false);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "%s: error during DRS repl ADD: %s", __func__, ldb_errstring(ldb));
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &md->ctr.ctr1, msg->dn);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "%s: error during DRS repl ADD: %s", __func__, ldb_errstring(ldb));
+ return replmd_replicated_request_error(ar, ret);
+ }
+
+ for (i=0; i < md->ctr.ctr1.count; i++) {
+ md->ctr.ctr1.array[i].local_usn = ar->seq_num;
+ }
+ ndr_err = ndr_push_struct_blob(&md_value, msg, 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);
+
+ if (!remote_isDeleted) {
+ /*
+ * Ensure any local ACL inheritence is applied from
+ * the parent object.
+ *
+ * This is needed because descriptor is above
+ * repl_meta_data in the module stack, so this will
+ * not be trigered 'naturally' by the flow of
+ * operations.
+ */
+ ret = dsdb_module_schedule_sd_propagation(ar->module,
+ ar->objs->partition_dn,
+ ar->objs->objects[ar->index_current].object_guid,
+ ar->objs->objects[ar->index_current].parent_guid ?
+ *ar->objs->objects[ar->index_current].parent_guid :
+ GUID_zero(),
+ true);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+ }
+
+ ar->isDeleted = remote_isDeleted;
+
+ ret = ldb_build_add_req(&change_req,
+ ldb,
+ ar,
+ msg,
+ ar->controls,
+ ar,
+ replmd_op_add_callback,
+ ar->req);
+ LDB_REQ_SET_LOCATION(change_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ /* current partition control needed by "repmd_op_callback" */
+ ret = ldb_request_add_control(change_req,
+ DSDB_CONTROL_CURRENT_PARTITION_OID,
+ false, NULL);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ if (ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PARTIAL_REPLICA) {
+ /* this tells the partition module to make it a
+ partial replica if creating an NC */
+ ret = ldb_request_add_control(change_req,
+ DSDB_CONTROL_PARTIAL_REPLICA,
+ false, NULL);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+ }
+
+ return ldb_next_request(ar->module, change_req);
+}
+
+static int replmd_replicated_apply_search_for_parent_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct replmd_replicated_request *ar = talloc_get_type(req->context,
+ struct replmd_replicated_request);
+ int ret;
+
+ if (!ares) {
+ return ldb_module_done(ar->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /*
+ * The error NO_SUCH_OBJECT is not expected, unless the search
+ * base is the partition DN, and that case doesn't happen here
+ * because then we wouldn't get a parent_guid_value in any
+ * case.
+ */
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ar->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ {
+ struct ldb_message *parent_msg = ares->message;
+ struct ldb_message *msg = ar->objs->objects[ar->index_current].msg;
+ struct ldb_dn *parent_dn = NULL;
+ int comp_num;
+
+ if (!ldb_msg_check_string_attribute(msg, "isDeleted", "TRUE")
+ && ldb_msg_check_string_attribute(parent_msg, "isDeleted", "TRUE")) {
+ /* Per MS-DRSR 4.1.10.6.10
+ * FindBestParentObject we need to move this
+ * new object under a deleted object to
+ * lost-and-found */
+ struct ldb_dn *nc_root;
+
+ ret = dsdb_find_nc_root(ldb_module_get_ctx(ar->module), msg, msg->dn, &nc_root);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ "No suitable NC root found for %s. "
+ "We need to move this object because parent object %s "
+ "is deleted, but this object is not.",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_dn_get_linearized(parent_msg->dn));
+ return ldb_module_done(ar->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ } else if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ "Unable to find NC root for %s: %s. "
+ "We need to move this object because parent object %s "
+ "is deleted, but this object is not.",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module)),
+ ldb_dn_get_linearized(parent_msg->dn));
+ return ldb_module_done(ar->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ ret = dsdb_wellknown_dn(ldb_module_get_ctx(ar->module), msg,
+ nc_root,
+ DS_GUID_LOSTANDFOUND_CONTAINER,
+ &parent_dn);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ "Unable to find LostAndFound Container for %s "
+ "in partition %s: %s. "
+ "We need to move this object because parent object %s "
+ "is deleted, but this object is not.",
+ ldb_dn_get_linearized(msg->dn), ldb_dn_get_linearized(nc_root),
+ ldb_errstring(ldb_module_get_ctx(ar->module)),
+ ldb_dn_get_linearized(parent_msg->dn));
+ return ldb_module_done(ar->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+ }
+ ar->objs->objects[ar->index_current].last_known_parent
+ = talloc_steal(ar->objs->objects[ar->index_current].msg, parent_msg->dn);
+
+ } else {
+ parent_dn
+ = talloc_steal(ar->objs->objects[ar->index_current].msg, parent_msg->dn);
+
+ }
+ ar->objs->objects[ar->index_current].local_parent_dn = parent_dn;
+
+ comp_num = ldb_dn_get_comp_num(msg->dn);
+ if (comp_num > 1) {
+ if (!ldb_dn_remove_base_components(msg->dn, comp_num - 1)) {
+ talloc_free(ares);
+ return ldb_module_done(ar->req, NULL, NULL, ldb_module_operr(ar->module));
+ }
+ }
+ if (!ldb_dn_add_base(msg->dn, parent_dn)) {
+ talloc_free(ares);
+ return ldb_module_done(ar->req, NULL, NULL, ldb_module_operr(ar->module));
+ }
+ break;
+ }
+ case LDB_REPLY_REFERRAL:
+ /* we ignore referrals */
+ break;
+
+ case LDB_REPLY_DONE:
+
+ if (ar->objs->objects[ar->index_current].local_parent_dn == NULL) {
+ struct GUID_txt_buf str_buf;
+ if (ar->search_msg != NULL) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ "No parent with GUID %s found for object locally known as %s",
+ GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid, &str_buf),
+ ldb_dn_get_linearized(ar->search_msg->dn));
+ } else {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ "No parent with GUID %s found for object remotely known as %s",
+ GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid, &str_buf),
+ ldb_dn_get_linearized(ar->objs->objects[ar->index_current].msg->dn));
+ }
+
+ /*
+ * This error code is really important, as it
+ * is the flag back to the callers to retry
+ * this with DRSUAPI_DRS_GET_ANC, and so get
+ * the parent objects before the child
+ * objects
+ */
+ return ldb_module_done(ar->req, NULL, NULL,
+ replmd_replicated_request_werror(ar, WERR_DS_DRA_MISSING_PARENT));
+ }
+
+ if (ar->search_msg != NULL) {
+ ret = replmd_replicated_apply_merge(ar);
+ } else {
+ ret = replmd_replicated_apply_add(ar);
+ }
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ar->req, NULL, NULL, ret);
+ }
+ }
+
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+/*
+ * Look for the parent object, so we put the new object in the right
+ * place This is akin to NameObject in MS-DRSR - this routine and the
+ * callbacks find the right parent name, and correct name for this
+ * object
+ */
+
+static int replmd_replicated_apply_search_for_parent(struct replmd_replicated_request *ar)
+{
+ struct ldb_context *ldb;
+ int ret;
+ char *tmp_str;
+ char *filter;
+ struct ldb_request *search_req;
+ static const char *attrs[] = {"isDeleted", NULL};
+ struct GUID_txt_buf guid_str_buf;
+
+ ldb = ldb_module_get_ctx(ar->module);
+
+ if (ar->objs->objects[ar->index_current].parent_guid == NULL) {
+ if (ar->search_msg != NULL) {
+ return replmd_replicated_apply_merge(ar);
+ } else {
+ return replmd_replicated_apply_add(ar);
+ }
+ }
+
+ tmp_str = GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid,
+ &guid_str_buf);
+
+ filter = talloc_asprintf(ar, "(objectGUID=%s)", tmp_str);
+ if (!filter) return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
+
+ ret = ldb_build_search_req(&search_req,
+ ldb,
+ ar,
+ ar->objs->partition_dn,
+ LDB_SCOPE_SUBTREE,
+ filter,
+ attrs,
+ NULL,
+ ar,
+ replmd_replicated_apply_search_for_parent_callback,
+ ar->req);
+ LDB_REQ_SET_LOCATION(search_req);
+
+ ret = dsdb_request_add_controls(search_req,
+ DSDB_SEARCH_SHOW_RECYCLED|
+ DSDB_SEARCH_SHOW_DELETED|
+ DSDB_SEARCH_SHOW_EXTENDED_DN);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ar->module, search_req);
+}
+
+/*
+ handle renames that come in over DRS replication
+ */
+static int replmd_replicated_handle_rename(struct replmd_replicated_request *ar,
+ struct ldb_message *msg,
+ struct ldb_request *parent,
+ bool *renamed_to_conflict)
+{
+ int ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg);
+ struct ldb_result *res;
+ struct ldb_dn *conflict_dn;
+ bool rename_incoming_record;
+ struct ldb_dn *new_dn;
+ struct GUID guid;
+
+ DEBUG(4,("replmd_replicated_request rename %s => %s\n",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ ldb_dn_get_linearized(msg->dn)));
+
+
+ ret = dsdb_module_rename(ar->module, ar->search_msg->dn, msg->dn,
+ DSDB_FLAG_NEXT_MODULE, ar->req);
+ if (ret == LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (ret != LDB_ERR_ENTRY_ALREADY_EXISTS) {
+ talloc_free(tmp_ctx);
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module), "Failed to locally apply remote rename from %s to %s: %s",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module)));
+ return ret;
+ }
+
+ conflict_dn = msg->dn;
+
+
+ ret = incoming_dn_should_be_renamed(tmp_ctx, ar, conflict_dn, &res,
+ &rename_incoming_record);
+ if (ret != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ if (rename_incoming_record) {
+
+ new_dn = replmd_conflict_dn(msg,
+ ldb_module_get_ctx(ar->module),
+ msg->dn,
+ &ar->objs->objects[ar->index_current].object_guid);
+ if (new_dn == NULL) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ "Failed to form conflict DN for %s\n",
+ ldb_dn_get_linearized(msg->dn));
+
+ talloc_free(tmp_ctx);
+ return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
+ }
+
+ ret = dsdb_module_rename(ar->module, ar->search_msg->dn, new_dn,
+ DSDB_FLAG_NEXT_MODULE, ar->req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+ "Failed to rename incoming conflicting dn '%s' (was '%s') to '%s' - %s\n",
+ ldb_dn_get_linearized(conflict_dn),
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ ldb_dn_get_linearized(new_dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module)));
+ talloc_free(tmp_ctx);
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_DB_ERROR);
+ }
+
+ msg->dn = new_dn;
+ *renamed_to_conflict = true;
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ /* we are renaming the existing record */
+
+ guid = samdb_result_guid(res->msgs[0], "objectGUID");
+ if (GUID_all_zero(&guid)) {
+ DEBUG(0,(__location__ ": Failed to find objectGUID for existing conflict record %s\n",
+ ldb_dn_get_linearized(conflict_dn)));
+ goto failed;
+ }
+
+ new_dn = replmd_conflict_dn(tmp_ctx,
+ ldb_module_get_ctx(ar->module),
+ conflict_dn, &guid);
+ if (new_dn == NULL) {
+ DEBUG(0,(__location__ ": Failed to form conflict DN for %s\n",
+ ldb_dn_get_linearized(conflict_dn)));
+ goto failed;
+ }
+
+ DEBUG(2,(__location__ ": Resolving conflict record via existing-record rename '%s' -> '%s'\n",
+ ldb_dn_get_linearized(conflict_dn), ldb_dn_get_linearized(new_dn)));
+
+ ret = dsdb_module_rename(ar->module, conflict_dn, new_dn,
+ DSDB_FLAG_OWN_MODULE, ar->req);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to rename conflict dn '%s' to '%s' - %s\n",
+ ldb_dn_get_linearized(conflict_dn),
+ ldb_dn_get_linearized(new_dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module))));
+ goto failed;
+ }
+
+ /*
+ * now we need to ensure that the rename is seen as an
+ * originating update. We do that with a modify.
+ */
+ ret = replmd_name_modify(ar, ar->req, new_dn);
+ if (ret != LDB_SUCCESS) {
+ goto failed;
+ }
+
+ DEBUG(2,(__location__ ": With conflicting record renamed, re-apply replicated rename '%s' -> '%s'\n",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ ldb_dn_get_linearized(msg->dn)));
+
+ /*
+ * With the other record out of the way, do the rename we had
+ * at the top again
+ */
+ ret = dsdb_module_rename(ar->module, ar->search_msg->dn, msg->dn,
+ DSDB_FLAG_NEXT_MODULE, ar->req);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": After conflict resolution, failed to rename dn '%s' to '%s' - %s\n",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb_module_get_ctx(ar->module))));
+ goto failed;
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+failed:
+ /*
+ * On failure make the caller get the error
+ * This means replication will stop with an error,
+ * but there is not much else we can do. In the
+ * LDB_ERR_ENTRY_ALREADY_EXISTS case this is exactly what is
+ * needed.
+ */
+ if (ret == LDB_SUCCESS) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+
+static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
+{
+ struct ldb_context *ldb;
+ struct ldb_request *change_req;
+ 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;
+ struct GUID remote_parent_guid;
+ unsigned int i;
+ uint32_t j,ni=0;
+ unsigned int removed_attrs = 0;
+ int ret;
+ int (*callback)(struct ldb_request *req, struct ldb_reply *ares) = replmd_op_callback;
+ bool isDeleted = false;
+ bool local_isDeleted = false;
+ bool remote_isDeleted = false;
+ bool take_remote_isDeleted = false;
+ bool sd_updated = false;
+ bool renamed = false;
+ bool renamed_to_conflict = false;
+ bool is_schema_nc = false;
+ NTSTATUS nt_status;
+ const struct ldb_val *old_rdn, *new_rdn;
+ struct replmd_private *replmd_private =
+ talloc_get_type(ldb_module_get_private(ar->module),
+ struct replmd_private);
+ NTTIME now;
+ time_t t = time(NULL);
+ unix_to_nt_time(&now, t);
+
+ ldb = ldb_module_get_ctx(ar->module);
+ msg = ar->objs->objects[ar->index_current].msg;
+
+ is_schema_nc = ldb_dn_compare_base(replmd_private->schema_dn, msg->dn) == 0;
+
+ rmd = ar->objs->objects[ar->index_current].meta_data;
+ ZERO_STRUCT(omd);
+ omd.version = 1;
+
+ /* find existing meta data */
+ omd_value = ldb_msg_find_ldb_val(ar->search_msg, "replPropertyMetaData");
+ if (omd_value) {
+ ndr_err = ndr_pull_struct_blob(omd_value, ar, &omd,
+ (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ 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);
+ }
+ }
+
+ if (DEBUGLVL(8)) {
+ struct GUID_txt_buf guid_txt;
+
+ char *s = ldb_ldif_message_redacted_string(ldb, ar,
+ LDB_CHANGETYPE_MODIFY, msg);
+ DEBUG(8, ("Initial DRS replication modify message of %s is:\n%s\n"
+ "%s\n"
+ "%s\n",
+ GUID_buf_string(&ar->objs->objects[ar->index_current].object_guid, &guid_txt),
+ s,
+ ndr_print_struct_string(s,
+ (ndr_print_fn_t)ndr_print_replPropertyMetaDataBlob,
+ "existing replPropertyMetaData",
+ &omd),
+ ndr_print_struct_string(s,
+ (ndr_print_fn_t)ndr_print_replPropertyMetaDataBlob,
+ "incoming replPropertyMetaData",
+ rmd)));
+ talloc_free(s);
+ } else if (DEBUGLVL(4)) {
+ struct GUID_txt_buf guid_txt;
+
+ DEBUG(4, ("Initial DRS replication modify DN of %s is: %s\n",
+ GUID_buf_string(&ar->objs->objects[ar->index_current].object_guid,
+ &guid_txt),
+ ldb_dn_get_linearized(msg->dn)));
+ }
+
+ local_isDeleted = ldb_msg_find_attr_as_bool(ar->search_msg,
+ "isDeleted", false);
+ remote_isDeleted = ldb_msg_find_attr_as_bool(msg,
+ "isDeleted", false);
+
+ /*
+ * Fill in the remote_parent_guid with the GUID or an all-zero
+ * GUID.
+ */
+ if (ar->objs->objects[ar->index_current].parent_guid != NULL) {
+ remote_parent_guid = *ar->objs->objects[ar->index_current].parent_guid;
+ } else {
+ remote_parent_guid = GUID_zero();
+ }
+
+ /*
+ * To ensure we follow a complex rename chain around, we have
+ * to confirm that the DN is the same (mostly to confirm the
+ * RDN) and the parentGUID is the same.
+ *
+ * This ensures we keep things under the correct parent, which
+ * replmd_replicated_handle_rename() will do.
+ */
+
+ if (strcmp(ldb_dn_get_linearized(msg->dn), ldb_dn_get_linearized(ar->search_msg->dn)) == 0
+ && GUID_equal(&remote_parent_guid, &ar->local_parent_guid)) {
+ ret = LDB_SUCCESS;
+ } else {
+ /*
+ * handle renames, even just by case that come in over
+ * DRS. Changes in the parent DN don't hit us here,
+ * because the search for a parent will clean up those
+ * components.
+ *
+ * We also have already filtered out the case where
+ * the peer has an older name to what we have (see
+ * replmd_replicated_apply_search_callback())
+ */
+ ret = replmd_replicated_handle_rename(ar, msg, ar->req, &renamed_to_conflict);
+
+ /*
+ * This looks strange, but we must set this after any
+ * rename, otherwise the SD propegation will not
+ * happen (which might matter if we have a new parent)
+ *
+ * The additional case of calling
+ * replmd_op_name_modify_callback (below) is
+ * controlled by renamed_to_conflict.
+ */
+ renamed = true;
+ }
+
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_FATAL,
+ "replmd_replicated_request rename %s => %s failed - %s\n",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb));
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_DB_ERROR);
+ }
+
+ if (renamed_to_conflict == true) {
+ /*
+ * Set the callback to one that will fix up the name
+ * metadata on the new conflict DN
+ */
+ callback = replmd_op_name_modify_callback;
+ }
+
+ 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,
+ struct replPropertyMetaData1,
+ nmd.ctr.ctr1.count);
+ if (!nmd.ctr.ctr1.array) return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
+
+ /* 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++;
+ }
+
+ ar->seq_num = 0;
+ /* now merge in the new meta data */
+ for (i=0; i < rmd->ctr.ctr1.count; i++) {
+ bool found = false;
+
+ for (j=0; j < ni; j++) {
+ bool cmp;
+
+ if (rmd->ctr.ctr1.array[i].attid != nmd.ctr.ctr1.array[j].attid) {
+ continue;
+ }
+
+ cmp = replmd_replPropertyMetaData1_new_should_be_taken(
+ ar->objs->dsdb_repl_flags,
+ &nmd.ctr.ctr1.array[j],
+ &rmd->ctr.ctr1.array[i]);
+ if (cmp) {
+ /* replace the entry */
+ nmd.ctr.ctr1.array[j] = rmd->ctr.ctr1.array[i];
+ if (ar->seq_num == 0) {
+ ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, &ar->seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+ }
+ nmd.ctr.ctr1.array[j].local_usn = ar->seq_num;
+ switch (nmd.ctr.ctr1.array[j].attid) {
+ case DRSUAPI_ATTID_ntSecurityDescriptor:
+ sd_updated = true;
+ break;
+ case DRSUAPI_ATTID_isDeleted:
+ take_remote_isDeleted = true;
+ break;
+ default:
+ break;
+ }
+ found = true;
+ break;
+ }
+
+ if (rmd->ctr.ctr1.array[i].attid != DRSUAPI_ATTID_instanceType) {
+ DEBUG(3,("Discarding older DRS attribute update to %s on %s from %s\n",
+ msg->elements[i-removed_attrs].name,
+ ldb_dn_get_linearized(msg->dn),
+ GUID_string(ar, &rmd->ctr.ctr1.array[i].originating_invocation_id)));
+ }
+
+ /* 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];
+ if (ar->seq_num == 0) {
+ ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, &ar->seq_num);
+ if (ret != LDB_SUCCESS) {
+ return replmd_replicated_request_error(ar, ret);
+ }
+ }
+ nmd.ctr.ctr1.array[ni].local_usn = ar->seq_num;
+ switch (nmd.ctr.ctr1.array[ni].attid) {
+ case DRSUAPI_ATTID_ntSecurityDescriptor:
+ sd_updated = true;
+ break;
+ case DRSUAPI_ATTID_isDeleted:
+ take_remote_isDeleted = true;
+ break;
+ default:
+ break;
+ }
+ ni++;
+ }
+
+ /*
+ * finally correct the size of the meta_data array
+ */
+ nmd.ctr.ctr1.count = ni;
+
+ new_rdn = ldb_dn_get_rdn_val(msg->dn);
+ old_rdn = ldb_dn_get_rdn_val(ar->search_msg->dn);
+
+ if (renamed) {
+ ret = replmd_update_rpmd_rdn_attr(ldb, msg, new_rdn, old_rdn,
+ &nmd, ar, now, is_schema_nc,
+ false);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "%s: error during DRS repl merge: %s", __func__, ldb_errstring(ldb));
+ return replmd_replicated_request_error(ar, ret);
+ }
+ }
+ /*
+ * sort the new meta data array
+ */
+ ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &nmd.ctr.ctr1, msg->dn);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "%s: error during DRS repl merge: %s", __func__, ldb_errstring(ldb));
+ return ret;
+ }
+
+ /*
+ * Work out if this object is deleted, so we can prune any extra attributes. See MS-DRSR 4.1.10.6.9
+ * UpdateObject.
+ *
+ * This also controls SD propagation below
+ */
+ if (take_remote_isDeleted) {
+ isDeleted = remote_isDeleted;
+ } else {
+ isDeleted = local_isDeleted;
+ }
+
+ ar->isDeleted = isDeleted;
+
+ /*
+ * check if some replicated attributes left, otherwise skip the ldb_modify() call
+ */
+ if (msg->num_elements == 0) {
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_replicated_apply_merge[%u]: skip replace\n",
+ ar->index_current);
+
+ return replmd_replicated_apply_isDeleted(ar);
+ }
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_replicated_apply_merge[%u]: replace %u attributes\n",
+ ar->index_current, msg->num_elements);
+
+ if (renamed) {
+ /*
+ * This is an new name for this object, so we must
+ * inherit from the parent
+ *
+ * This is needed because descriptor is above
+ * repl_meta_data in the module stack, so this will
+ * not be trigered 'naturally' by the flow of
+ * operations.
+ */
+ ret = dsdb_module_schedule_sd_propagation(ar->module,
+ ar->objs->partition_dn,
+ ar->objs->objects[ar->index_current].object_guid,
+ ar->objs->objects[ar->index_current].parent_guid ?
+ *ar->objs->objects[ar->index_current].parent_guid :
+ GUID_zero(),
+ true);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+ }
+
+ if (sd_updated && !isDeleted) {
+ /*
+ * This is an existing object, so there is no need to
+ * inherit from the parent, but we must inherit any
+ * incoming changes to our child objects.
+ *
+ * This is needed because descriptor is above
+ * repl_meta_data in the module stack, so this will
+ * not be trigered 'naturally' by the flow of
+ * operations.
+ */
+ ret = dsdb_module_schedule_sd_propagation(ar->module,
+ ar->objs->partition_dn,
+ ar->objs->objects[ar->index_current].object_guid,
+ ar->objs->objects[ar->index_current].parent_guid ?
+ *ar->objs->objects[ar->index_current].parent_guid :
+ GUID_zero(),
+ false);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+ }
+
+ /* create the meta data value */
+ ndr_err = ndr_push_struct_blob(&nmd_value, msg, &nmd,
+ (ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ nt_status = ndr_map_error2ntstatus(ndr_err);
+ return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+ }
+
+ /*
+ * when we know 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(ldb, msg, msg, "uSNChanged", ar->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;
+ if (ldb_attr_cmp(msg->elements[i].name, "objectClass") == 0) {
+ if (msg->elements[i].num_values == 0) {
+ ldb_asprintf_errstring(ldb, __location__
+ ": objectClass removed on %s, aborting replication\n",
+ ldb_dn_get_linearized(msg->dn));
+ return replmd_replicated_request_error(ar, LDB_ERR_OBJECT_CLASS_VIOLATION);
+ }
+ }
+ }
+
+ if (DEBUGLVL(8)) {
+ struct GUID_txt_buf guid_txt;
+
+ char *s = ldb_ldif_message_redacted_string(ldb, ar,
+ LDB_CHANGETYPE_MODIFY,
+ msg);
+ DEBUG(8, ("Final DRS replication modify message of %s:\n%s\n",
+ GUID_buf_string(&ar->objs->objects[ar->index_current].object_guid,
+ &guid_txt),
+ s));
+ talloc_free(s);
+ } else if (DEBUGLVL(4)) {
+ struct GUID_txt_buf guid_txt;
+
+ DEBUG(4, ("Final DRS replication modify DN of %s is %s\n",
+ GUID_buf_string(&ar->objs->objects[ar->index_current].object_guid,
+ &guid_txt),
+ ldb_dn_get_linearized(msg->dn)));
+ }
+
+ ret = ldb_build_mod_req(&change_req,
+ ldb,
+ ar,
+ msg,
+ ar->controls,
+ ar,
+ callback,
+ ar->req);
+ LDB_REQ_SET_LOCATION(change_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ /* current partition control needed by "repmd_op_callback" */
+ ret = ldb_request_add_control(change_req,
+ DSDB_CONTROL_CURRENT_PARTITION_OID,
+ false, NULL);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ return ldb_next_request(ar->module, change_req);
+}
+
+static int replmd_replicated_apply_search_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct replmd_replicated_request *ar = talloc_get_type(req->context,
+ struct replmd_replicated_request);
+ int ret;
+
+ if (!ares) {
+ return ldb_module_done(ar->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS &&
+ ares->error != LDB_ERR_NO_SUCH_OBJECT) {
+ return ldb_module_done(ar->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ ar->search_msg = talloc_steal(ar, ares->message);
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* we ignore referrals */
+ break;
+
+ case LDB_REPLY_DONE:
+ {
+ struct replPropertyMetaData1 *md_remote;
+ struct replPropertyMetaData1 *md_local;
+
+ struct replPropertyMetaDataBlob omd;
+ const struct ldb_val *omd_value;
+ struct replPropertyMetaDataBlob *rmd;
+ struct ldb_message *msg;
+ int instanceType;
+ ar->objs->objects[ar->index_current].local_parent_dn = NULL;
+ ar->objs->objects[ar->index_current].last_known_parent = NULL;
+
+ /*
+ * This is the ADD case, find the appropriate parent,
+ * as this object doesn't exist locally:
+ */
+ if (ar->search_msg == NULL) {
+ ret = replmd_replicated_apply_search_for_parent(ar);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ar->req, NULL, NULL, ret);
+ }
+ talloc_free(ares);
+ return LDB_SUCCESS;
+ }
+
+ /*
+ * Otherwise, in the MERGE case, work out if we are
+ * attempting a rename, and if so find the parent the
+ * newly renamed object wants to belong under (which
+ * may not be the parent in it's attached string DN
+ */
+ rmd = ar->objs->objects[ar->index_current].meta_data;
+ ZERO_STRUCT(omd);
+ omd.version = 1;
+
+ /* find existing meta data */
+ omd_value = ldb_msg_find_ldb_val(ar->search_msg, "replPropertyMetaData");
+ if (omd_value) {
+ enum ndr_err_code ndr_err;
+ ndr_err = ndr_pull_struct_blob(omd_value, ar, &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);
+ }
+ }
+
+ ar->local_parent_guid = samdb_result_guid(ar->search_msg, "parentGUID");
+
+ instanceType = ldb_msg_find_attr_as_int(ar->search_msg, "instanceType", 0);
+ if (((instanceType & INSTANCE_TYPE_IS_NC_HEAD) == 0)
+ && GUID_all_zero(&ar->local_parent_guid)) {
+ DEBUG(0, ("Refusing to replicate new version of %s "
+ "as local object has an all-zero parentGUID attribute, "
+ "despite not being an NC root\n",
+ ldb_dn_get_linearized(ar->search_msg->dn)));
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
+ }
+
+ /*
+ * now we need to check for double renames. We could have a
+ * local rename pending which our replication partner hasn't
+ * received yet. We choose which one wins by looking at the
+ * attribute stamps on the two objects, the newer one wins.
+ *
+ * This also simply applies the correct algorithms for
+ * determining if a change was made to name at all, or
+ * if the object has just been renamed under the same
+ * parent.
+ */
+ md_remote = replmd_replPropertyMetaData1_find_attid(rmd, DRSUAPI_ATTID_name);
+ md_local = replmd_replPropertyMetaData1_find_attid(&omd, DRSUAPI_ATTID_name);
+ if (!md_local) {
+ DEBUG(0,(__location__ ": Failed to find name attribute in local LDB replPropertyMetaData for %s\n",
+ ldb_dn_get_linearized(ar->search_msg->dn)));
+ return replmd_replicated_request_werror(ar, WERR_DS_DRA_DB_ERROR);
+ }
+
+ /*
+ * if there is no name attribute given then we have to assume the
+ * object we've received has the older name
+ */
+ if (replmd_replPropertyMetaData1_new_should_be_taken(
+ ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING,
+ md_local, md_remote)) {
+ struct GUID_txt_buf p_guid_local;
+ struct GUID_txt_buf p_guid_remote;
+ msg = ar->objs->objects[ar->index_current].msg;
+
+ /* Merge on the existing object, with rename */
+
+ DEBUG(4,(__location__ ": Looking for new parent for object %s currently under %s "
+ "as incoming object changing to %s under %s\n",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ GUID_buf_string(&ar->local_parent_guid, &p_guid_local),
+ ldb_dn_get_linearized(msg->dn),
+ GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid,
+ &p_guid_remote)));
+ ret = replmd_replicated_apply_search_for_parent(ar);
+ } else {
+ struct GUID_txt_buf p_guid_local;
+ struct GUID_txt_buf p_guid_remote;
+ msg = ar->objs->objects[ar->index_current].msg;
+
+ /*
+ * Merge on the existing object, force no
+ * rename (code below just to explain why in
+ * the DEBUG() logs)
+ */
+
+ if (strcmp(ldb_dn_get_linearized(ar->search_msg->dn),
+ ldb_dn_get_linearized(msg->dn)) == 0) {
+ if (ar->objs->objects[ar->index_current].parent_guid != NULL &&
+ GUID_equal(&ar->local_parent_guid,
+ ar->objs->objects[ar->index_current].parent_guid)
+ == false) {
+ DEBUG(4,(__location__ ": Keeping object %s at under %s "
+ "despite incoming object changing parent to %s\n",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ GUID_buf_string(&ar->local_parent_guid, &p_guid_local),
+ GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid,
+ &p_guid_remote)));
+ }
+ } else {
+ DEBUG(4,(__location__ ": Keeping object %s at under %s "
+ " and rejecting older rename to %s under %s\n",
+ ldb_dn_get_linearized(ar->search_msg->dn),
+ GUID_buf_string(&ar->local_parent_guid, &p_guid_local),
+ ldb_dn_get_linearized(msg->dn),
+ GUID_buf_string(ar->objs->objects[ar->index_current].parent_guid,
+ &p_guid_remote)));
+ }
+ /*
+ * This assignment ensures that the strcmp()
+ * and GUID_equal() calls in
+ * replmd_replicated_apply_merge() avoids the
+ * rename call
+ */
+ ar->objs->objects[ar->index_current].parent_guid =
+ &ar->local_parent_guid;
+
+ msg->dn = ar->search_msg->dn;
+ ret = replmd_replicated_apply_merge(ar);
+ }
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ar->req, NULL, NULL, ret);
+ }
+ }
+ }
+
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+/**
+ * Returns true if we can group together processing this link attribute,
+ * i.e. it has the same source-object and attribute ID as other links
+ * already in the group
+ */
+static bool la_entry_matches_group(struct la_entry *la_entry,
+ struct la_group *la_group)
+{
+ struct la_entry *prev = la_group->la_entries;
+
+ return (la_entry->la->attid == prev->la->attid &&
+ GUID_equal(&la_entry->la->identifier->guid,
+ &prev->la->identifier->guid));
+}
+
+/**
+ * Creates a new la_entry to store replication info for a single
+ * linked attribute.
+ */
+static struct la_entry *
+create_la_entry(struct replmd_private *replmd_private,
+ struct drsuapi_DsReplicaLinkedAttribute *la,
+ uint32_t dsdb_repl_flags)
+{
+ struct la_entry *la_entry;
+
+ if (replmd_private->la_ctx == NULL) {
+ replmd_private->la_ctx = talloc_new(replmd_private);
+ }
+ la_entry = talloc(replmd_private->la_ctx, struct la_entry);
+ if (la_entry == NULL) {
+ return NULL;
+ }
+ la_entry->la = talloc(la_entry,
+ struct drsuapi_DsReplicaLinkedAttribute);
+ if (la_entry->la == NULL) {
+ talloc_free(la_entry);
+ return NULL;
+ }
+ *la_entry->la = *la;
+ la_entry->dsdb_repl_flags = dsdb_repl_flags;
+
+ /*
+ * we need to steal the non-scalars so they stay
+ * around until the end of the transaction
+ */
+ talloc_steal(la_entry->la, la_entry->la->identifier);
+ talloc_steal(la_entry->la, la_entry->la->value.blob);
+
+ return la_entry;
+}
+
+/**
+ * Stores the linked attributes received in the replication chunk - these get
+ * applied at the end of the transaction. We also check that each linked
+ * attribute is valid, i.e. source and target objects are known.
+ */
+static int replmd_store_linked_attributes(struct replmd_replicated_request *ar)
+{
+ int ret = LDB_SUCCESS;
+ uint32_t i;
+ struct ldb_module *module = ar->module;
+ struct replmd_private *replmd_private =
+ talloc_get_type(ldb_module_get_private(module), struct replmd_private);
+ struct la_group *la_group = NULL;
+ struct ldb_context *ldb;
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct ldb_message *src_msg = NULL;
+ const struct dsdb_attribute *attr = NULL;
+
+ ldb = ldb_module_get_ctx(module);
+
+ DEBUG(4,("linked_attributes_count=%u\n", ar->objs->linked_attributes_count));
+
+ /* save away the linked attributes for the end of the transaction */
+ for (i = 0; i < ar->objs->linked_attributes_count; i++) {
+ struct la_entry *la_entry;
+ bool new_srcobj;
+
+ /* create an entry to store the received link attribute info */
+ la_entry = create_la_entry(replmd_private,
+ &ar->objs->linked_attributes[i],
+ ar->objs->dsdb_repl_flags);
+ if (la_entry == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * check if we're still dealing with the same source object
+ * as the last link
+ */
+ new_srcobj = (la_group == NULL ||
+ !la_entry_matches_group(la_entry, la_group));
+
+ if (new_srcobj) {
+
+ /* get a new mem_ctx to lookup the source object */
+ TALLOC_FREE(tmp_ctx);
+ tmp_ctx = talloc_new(ar);
+ if (tmp_ctx == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* verify the link source exists */
+ ret = replmd_get_la_entry_source(module, la_entry,
+ tmp_ctx, &attr,
+ &src_msg);
+
+ /*
+ * When we fail to find the source object, the error
+ * code we pass back here is really important. It flags
+ * back to the callers to retry this request with
+ * DRSUAPI_DRS_GET_ANC. This case should never happen
+ * if we're replicating from a Samba DC, but it is
+ * needed to talk to a Windows DC
+ */
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ WERROR err = WERR_DS_DRA_MISSING_PARENT;
+ ret = replmd_replicated_request_werror(ar,
+ err);
+ break;
+ }
+ }
+
+ ret = replmd_verify_link_target(ar, tmp_ctx, la_entry,
+ src_msg->dn, attr);
+ if (ret != LDB_SUCCESS) {
+ break;
+ }
+
+ /* group the links together by source-object for efficiency */
+ if (new_srcobj) {
+ la_group = talloc_zero(replmd_private->la_ctx,
+ struct la_group);
+ if (la_group == NULL) {
+ ldb_oom(ldb);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ DLIST_ADD(replmd_private->la_list, la_group);
+ }
+ DLIST_ADD(la_group->la_entries, la_entry);
+ replmd_private->total_links++;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int replmd_replicated_uptodate_vector(struct replmd_replicated_request *ar);
+
+static int replmd_replicated_apply_next(struct replmd_replicated_request *ar)
+{
+ struct ldb_context *ldb;
+ int ret;
+ char *tmp_str;
+ char *filter;
+ struct ldb_request *search_req;
+ static const char *attrs[] = { "repsFrom", "replUpToDateVector",
+ "parentGUID", "instanceType",
+ "replPropertyMetaData", "nTSecurityDescriptor",
+ "isDeleted", NULL };
+ struct GUID_txt_buf guid_str_buf;
+
+ if (ar->index_current >= ar->objs->num_objects) {
+
+ /*
+ * Now that we've applied all the objects, check the new linked
+ * attributes and store them (we apply them in .prepare_commit)
+ */
+ ret = replmd_store_linked_attributes(ar);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* done applying objects, move on to the next stage */
+ return replmd_replicated_uptodate_vector(ar);
+ }
+
+ ldb = ldb_module_get_ctx(ar->module);
+ ar->search_msg = NULL;
+ ar->isDeleted = false;
+
+ tmp_str = GUID_buf_string(&ar->objs->objects[ar->index_current].object_guid,
+ &guid_str_buf);
+
+ filter = talloc_asprintf(ar, "(objectGUID=%s)", tmp_str);
+ if (!filter) return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
+
+ ret = ldb_build_search_req(&search_req,
+ ldb,
+ ar,
+ ar->objs->partition_dn,
+ LDB_SCOPE_SUBTREE,
+ filter,
+ attrs,
+ NULL,
+ ar,
+ replmd_replicated_apply_search_callback,
+ ar->req);
+ LDB_REQ_SET_LOCATION(search_req);
+
+ /*
+ * We set DSDB_SEARCH_SHOW_EXTENDED_DN to get the GUID on the
+ * DN. This in turn helps our operational module find the
+ * record by GUID, not DN lookup which is more error prone if
+ * DN indexing changes. We prefer to keep chasing GUIDs
+ * around if possible, even within a transaction.
+ *
+ * The aim here is to keep replication moving and allow a
+ * reindex later.
+ */
+ ret = dsdb_request_add_controls(search_req, DSDB_SEARCH_SHOW_RECYCLED
+ |DSDB_SEARCH_SHOW_EXTENDED_DN);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ar->module, search_req);
+}
+
+/*
+ * Returns true if we need to do extra processing to handle deleted object
+ * changes received via replication
+ */
+static bool replmd_should_apply_isDeleted(struct replmd_replicated_request *ar,
+ struct ldb_message *msg)
+{
+ struct ldb_dn *deleted_objects_dn;
+ int ret;
+
+ if (!ar->isDeleted) {
+
+ /* not a deleted object, so don't set isDeleted */
+ return false;
+ }
+
+ ret = dsdb_get_deleted_objects_dn(ldb_module_get_ctx(ar->module),
+ msg, msg->dn,
+ &deleted_objects_dn);
+
+ /*
+ * if the Deleted Object container lookup failed, then just apply
+ * isDeleted (note that it doesn't exist for the Schema partition)
+ */
+ if (ret != LDB_SUCCESS) {
+ return true;
+ }
+
+ /*
+ * the Deleted Objects container has isDeleted set but is not entirely
+ * a deleted object, so DON'T re-apply isDeleted to it
+ */
+ if (ldb_dn_compare(msg->dn, deleted_objects_dn) == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * This is essentially a wrapper for replmd_replicated_apply_next()
+ *
+ * This is needed to ensure that both codepaths call this handler.
+ */
+static int replmd_replicated_apply_isDeleted(struct replmd_replicated_request *ar)
+{
+ struct ldb_message *msg = ar->objs->objects[ar->index_current].msg;
+ int ret;
+ bool apply_isDeleted;
+ struct ldb_request *del_req = NULL;
+ struct ldb_result *res = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ apply_isDeleted = replmd_should_apply_isDeleted(ar, msg);
+
+ if (!apply_isDeleted) {
+
+ /* nothing to do */
+ ar->index_current++;
+ return replmd_replicated_apply_next(ar);
+ }
+
+ /*
+ * Do a delete here again, so that if there is
+ * anything local that conflicts with this
+ * object being deleted, it is removed. This
+ * includes links. See MS-DRSR 4.1.10.6.9
+ * UpdateObject.
+ *
+ * If the object is already deleted, and there
+ * is no more work required, it doesn't do
+ * anything.
+ */
+
+ /* This has been updated to point to the DN we eventually did the modify on */
+
+ tmp_ctx = talloc_new(ar);
+ if (!tmp_ctx) {
+ ret = ldb_oom(ldb_module_get_ctx(ar->module));
+ return ret;
+ }
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ ret = ldb_oom(ldb_module_get_ctx(ar->module));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* Build a delete request, which hopefully will artually turn into nothing */
+ ret = ldb_build_del_req(&del_req, ldb_module_get_ctx(ar->module), tmp_ctx,
+ msg->dn,
+ NULL,
+ res,
+ ldb_modify_default_callback,
+ ar->req);
+ LDB_REQ_SET_LOCATION(del_req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /*
+ * This is the guts of the call, call back
+ * into our delete code, but setting the
+ * re_delete flag so we delete anything that
+ * shouldn't be there on a deleted or recycled
+ * object
+ */
+ ret = replmd_delete_internals(ar->module, del_req, true);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(del_req->handle, LDB_WAIT_ALL);
+ }
+
+ talloc_free(tmp_ctx);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ar->index_current++;
+ return replmd_replicated_apply_next(ar);
+}
+
+static int replmd_replicated_uptodate_modify_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct replmd_replicated_request *ar = talloc_get_type(req->context,
+ struct replmd_replicated_request);
+ ldb = ldb_module_get_ctx(ar->module);
+
+ if (!ares) {
+ return ldb_module_done(ar->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ar->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ ldb_asprintf_errstring(ldb, "Invalid LDB reply type %d", ares->type);
+ return ldb_module_done(ar->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ talloc_free(ares);
+
+ return ldb_module_done(ar->req, NULL, NULL, LDB_SUCCESS);
+}
+
+static int replmd_replicated_uptodate_modify(struct replmd_replicated_request *ar)
+{
+ struct ldb_context *ldb;
+ struct ldb_request *change_req;
+ 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;
+ struct ldb_message_element *orf_el = NULL;
+ struct repsFromToBlob nrf;
+ struct ldb_val *nrf_value = NULL;
+ struct ldb_message_element *nrf_el = NULL;
+ unsigned int i;
+ uint32_t j,ni=0;
+ bool found = false;
+ time_t t = time(NULL);
+ NTTIME now;
+ int ret;
+ uint32_t instanceType;
+
+ ldb = ldb_module_get_ctx(ar->module);
+ ruv = ar->objs->uptodateness_vector;
+ ZERO_STRUCT(ouv);
+ ouv.version = 2;
+ ZERO_STRUCT(nuv);
+ nuv.version = 2;
+
+ unix_to_nt_time(&now, t);
+
+ if (ar->search_msg == NULL) {
+ /* this happens for a REPL_OBJ call where we are
+ creating the target object by replicating it. The
+ subdomain join code does this for the partition DN
+ */
+ DEBUG(4,(__location__ ": Skipping UDV and repsFrom update as no target DN\n"));
+ return ldb_module_done(ar->req, NULL, NULL, LDB_SUCCESS);
+ }
+
+ instanceType = ldb_msg_find_attr_as_uint(ar->search_msg, "instanceType", 0);
+ if (! (instanceType & INSTANCE_TYPE_IS_NC_HEAD)) {
+ DEBUG(4,(__location__ ": Skipping UDV and repsFrom update as not NC root: %s\n",
+ ldb_dn_get_linearized(ar->search_msg->dn)));
+ return ldb_module_done(ar->req, NULL, NULL, LDB_SUCCESS);
+ }
+
+ /*
+ * first create the new replUpToDateVector
+ */
+ ouv_value = ldb_msg_find_ldb_val(ar->search_msg, "replUpToDateVector");
+ if (ouv_value) {
+ ndr_err = ndr_pull_struct_blob(ouv_value, ar, &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 = ouv.ctr.ctr2.count;
+ if (ruv) nuv.ctr.ctr2.count += ruv->count;
+ nuv.ctr.ctr2.cursors = talloc_array(ar,
+ struct drsuapi_DsReplicaCursor2,
+ nuv.ctr.ctr2.count);
+ if (!nuv.ctr.ctr2.cursors) return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
+
+ /* 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++;
+ }
+
+ /* merge in the source_dsa vector is available */
+ for (i=0; (ruv && i < ruv->count); i++) {
+ found = false;
+
+ if (GUID_equal(&ruv->cursors[i].source_dsa_invocation_id,
+ &ar->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;
+
+ if (ruv->cursors[i].highest_usn > nuv.ctr.ctr2.cursors[j].highest_usn) {
+ nuv.ctr.ctr2.cursors[j] = ruv->cursors[i];
+ }
+ break;
+ }
+
+ if (found) continue;
+
+ /* if it's not there yet, add it */
+ nuv.ctr.ctr2.cursors[ni] = ruv->cursors[i];
+ ni++;
+ }
+
+ /*
+ * finally correct the size of the cursors array
+ */
+ nuv.ctr.ctr2.count = ni;
+
+ /*
+ * sort the cursors
+ */
+ TYPESAFE_QSORT(nuv.ctr.ctr2.cursors, nuv.ctr.ctr2.count, drsuapi_DsReplicaCursor2_compare);
+
+ /*
+ * create the change ldb_message
+ */
+ msg = ldb_msg_new(ar);
+ if (!msg) return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
+ msg->dn = ar->search_msg->dn;
+
+ ndr_err = ndr_push_struct_blob(&nuv_value, msg, &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;
+ nrf.ctr.ctr1.last_attempt = now;
+ nrf.ctr.ctr1.last_success = now;
+ nrf.ctr.ctr1.result_last_attempt = WERR_OK;
+
+ /*
+ * 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->search_msg, "repsFrom");
+ if (orf_el) {
+ for (i=0; i < orf_el->num_values; i++) {
+ struct repsFromToBlob *trf;
+
+ trf = talloc(ar, struct repsFromToBlob);
+ if (!trf) return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
+
+ ndr_err = ndr_pull_struct_blob(&orf_el->values[i], trf, 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,
+ &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;
+
+ if (CHECK_DEBUGLVL(4)) {
+ char *s = ldb_ldif_message_redacted_string(ldb, ar,
+ LDB_CHANGETYPE_MODIFY,
+ msg);
+ DEBUG(4, ("DRS replication uptodate modify message:\n%s\n", s));
+ talloc_free(s);
+ }
+
+ /* prepare the ldb_modify() request */
+ ret = ldb_build_mod_req(&change_req,
+ ldb,
+ ar,
+ msg,
+ ar->controls,
+ ar,
+ replmd_replicated_uptodate_modify_callback,
+ ar->req);
+ LDB_REQ_SET_LOCATION(change_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ return ldb_next_request(ar->module, change_req);
+}
+
+static int replmd_replicated_uptodate_search_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct replmd_replicated_request *ar = talloc_get_type(req->context,
+ struct replmd_replicated_request);
+ int ret;
+
+ if (!ares) {
+ return ldb_module_done(ar->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS &&
+ ares->error != LDB_ERR_NO_SUCH_OBJECT) {
+ return ldb_module_done(ar->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ ar->search_msg = talloc_steal(ar, ares->message);
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* we ignore referrals */
+ break;
+
+ case LDB_REPLY_DONE:
+ ret = replmd_replicated_uptodate_modify(ar);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ar->req, NULL, NULL, ret);
+ }
+ }
+
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+
+static int replmd_replicated_uptodate_vector(struct replmd_replicated_request *ar)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ar->module);
+ struct replmd_private *replmd_private =
+ talloc_get_type_abort(ldb_module_get_private(ar->module),
+ struct replmd_private);
+ int ret;
+ static const char *attrs[] = {
+ "replUpToDateVector",
+ "repsFrom",
+ "instanceType",
+ NULL
+ };
+ struct ldb_request *search_req;
+
+ ar->search_msg = NULL;
+
+ /*
+ * Let the caller know that we did an originating updates
+ */
+ ar->objs->originating_updates = replmd_private->originating_updates;
+
+ ret = ldb_build_search_req(&search_req,
+ ldb,
+ ar,
+ ar->objs->partition_dn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs,
+ NULL,
+ ar,
+ replmd_replicated_uptodate_search_callback,
+ ar->req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+
+ return ldb_next_request(ar->module, search_req);
+}
+
+
+
+static int replmd_extended_replicated_objects(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct dsdb_extended_replicated_objects *objs;
+ struct replmd_replicated_request *ar;
+ struct ldb_control **ctrls;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ldb_debug(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(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(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_ctx_init(module, req);
+ if (!ar)
+ return LDB_ERR_OPERATIONS_ERROR;
+
+ /* Set the flags to have the replmd_op_callback run over the full set of objects */
+ ar->apply_mode = true;
+ ar->objs = objs;
+ ar->schema = dsdb_get_schema(ldb, ar);
+ if (!ar->schema) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL, "replmd_ctx_init: no loaded schema found\n");
+ talloc_free(ar);
+ DEBUG(0,(__location__ ": %s\n", ldb_errstring(ldb)));
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ ctrls = req->controls;
+
+ if (req->controls) {
+ req->controls = talloc_memdup(ar, req->controls,
+ talloc_get_size(req->controls));
+ if (!req->controls) return replmd_replicated_request_werror(ar, WERR_NOT_ENOUGH_MEMORY);
+ }
+
+ ret = ldb_request_add_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID, false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* If this change contained linked attributes in the body
+ * (rather than in the links section) we need to update
+ * backlinks in linked_attributes */
+ ret = ldb_request_add_control(req, DSDB_CONTROL_APPLY_LINKS, false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ar->controls = req->controls;
+ req->controls = ctrls;
+
+ return replmd_replicated_apply_next(ar);
+}
+
+/**
+ * Checks how to handle an missing target - either we need to fail the
+ * replication and retry with GET_TGT, ignore the link and continue, or try to
+ * add a partial link to an unknown target.
+ */
+static int replmd_allow_missing_target(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *target_dn,
+ struct ldb_dn *source_dn,
+ bool is_obj_commit,
+ struct GUID *guid,
+ uint32_t dsdb_repl_flags,
+ bool *ignore_link,
+ const char * missing_str)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ bool is_in_same_nc;
+
+ /*
+ * we may not be able to resolve link targets properly when
+ * dealing with subsets of objects, e.g. the source is a
+ * critical object and the target isn't
+ *
+ * TODO:
+ * When we implement Trusted Domains we need to consider
+ * whether they get treated as an incomplete replica here or not
+ */
+ if (dsdb_repl_flags & DSDB_REPL_FLAG_OBJECT_SUBSET) {
+
+ /*
+ * Ignore the link. We don't increase the highwater-mark in
+ * the object subset cases, so subsequent replications should
+ * resolve any missing links
+ */
+ DEBUG(2, ("%s target %s linked from %s\n", missing_str,
+ ldb_dn_get_linearized(target_dn),
+ ldb_dn_get_linearized(source_dn)));
+ *ignore_link = true;
+ return LDB_SUCCESS;
+ }
+
+ is_in_same_nc = dsdb_objects_have_same_nc(ldb,
+ mem_ctx,
+ source_dn,
+ target_dn);
+ if (is_in_same_nc) {
+ /*
+ * We allow the join.py code to point out that all
+ * replication is completed, so failing now would just
+ * trigger errors, rather than trigger a GET_TGT
+ */
+ int *finished_full_join_ptr =
+ talloc_get_type(ldb_get_opaque(ldb,
+ DSDB_FULL_JOIN_REPLICATION_COMPLETED_OPAQUE_NAME),
+ int);
+ bool finished_full_join = finished_full_join_ptr && *finished_full_join_ptr;
+
+ /*
+ * if the target is already be up-to-date there's no point in
+ * retrying. This could be due to bad timing, or if a target
+ * on a one-way link was deleted. We ignore the link rather
+ * than failing the replication cycle completely
+ */
+ if (finished_full_join
+ || dsdb_repl_flags & DSDB_REPL_FLAG_TARGETS_UPTODATE) {
+ *ignore_link = true;
+ DBG_WARNING("%s is %s "
+ "but up to date. Ignoring link from %s\n",
+ ldb_dn_get_linearized(target_dn), missing_str,
+ ldb_dn_get_linearized(source_dn));
+ return LDB_SUCCESS;
+ }
+
+ /* otherwise fail the replication and retry with GET_TGT */
+ ldb_asprintf_errstring(ldb, "%s target %s GUID %s linked from %s\n",
+ missing_str,
+ ldb_dn_get_linearized(target_dn),
+ GUID_string(mem_ctx, guid),
+ ldb_dn_get_linearized(source_dn));
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+
+ /*
+ * The target of the cross-partition link is missing. Continue
+ * and try to at least add the forward-link. This isn't great,
+ * but a partial link can be fixed by dbcheck, so it's better
+ * than dropping the link completely.
+ */
+ *ignore_link = false;
+
+ if (is_obj_commit) {
+
+ /*
+ * Only log this when we're actually committing the objects.
+ * This avoids spurious logs, i.e. if we're just verifying the
+ * received link during a join.
+ */
+ DBG_WARNING("%s cross-partition target %s linked from %s\n",
+ missing_str, ldb_dn_get_linearized(target_dn),
+ ldb_dn_get_linearized(source_dn));
+ }
+
+ return LDB_SUCCESS;
+}
+
+/**
+ * Checks that the target object for a linked attribute exists.
+ * @param guid returns the target object's GUID (is returned)if it exists)
+ * @param ignore_link set to true if the linked attribute should be ignored
+ * (i.e. the target doesn't exist, but that it's OK to skip the link)
+ */
+static int replmd_check_target_exists(struct ldb_module *module,
+ struct dsdb_dn *dsdb_dn,
+ struct la_entry *la_entry,
+ struct ldb_dn *source_dn,
+ bool is_obj_commit,
+ struct GUID *guid,
+ bool *ignore_link)
+{
+ struct drsuapi_DsReplicaLinkedAttribute *la = la_entry->la;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_result *target_res;
+ TALLOC_CTX *tmp_ctx = talloc_new(la_entry);
+ const char *attrs[] = { "isDeleted", "isRecycled", NULL };
+ NTSTATUS ntstatus;
+ int ret;
+ enum deletion_state target_deletion_state = OBJECT_REMOVED;
+ bool active = (la->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE) ? true : false;
+
+ *ignore_link = false;
+ ntstatus = dsdb_get_extended_dn_guid(dsdb_dn->dn, guid, "GUID");
+
+ if (!NT_STATUS_IS_OK(ntstatus) && !active) {
+
+ /*
+ * This strange behaviour (allowing a NULL/missing
+ * GUID) originally comes from:
+ *
+ * commit e3054ce0fe0f8f62d2f5b2a77893e7a1479128bd
+ * Author: Andrew Tridgell <tridge@samba.org>
+ * Date: Mon Dec 21 21:21:55 2009 +1100
+ *
+ * s4-drs: cope better with NULL GUIDS from DRS
+ *
+ * It is valid to get a NULL GUID over DRS for a deleted forward link. We
+ * need to match by DN if possible when seeing if we should update an
+ * existing link.
+ *
+ * Pair-Programmed-With: Andrew Bartlett <abartlet@samba.org>
+ */
+ ret = dsdb_module_search_dn(module, tmp_ctx, &target_res,
+ dsdb_dn->dn, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SEARCH_ALL_PARTITIONS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ NULL);
+ } else if (!NT_STATUS_IS_OK(ntstatus)) {
+ ldb_asprintf_errstring(ldb, "Failed to find GUID in linked attribute 0x%x blob for %s from %s",
+ la->attid,
+ ldb_dn_get_linearized(dsdb_dn->dn),
+ ldb_dn_get_linearized(source_dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ } else {
+ ret = dsdb_module_search(module, tmp_ctx, &target_res,
+ NULL, LDB_SCOPE_SUBTREE,
+ attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SEARCH_ALL_PARTITIONS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ NULL,
+ "objectGUID=%s",
+ GUID_string(tmp_ctx, guid));
+ }
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to re-resolve GUID %s: %s\n",
+ GUID_string(tmp_ctx, guid),
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (target_res->count == 0) {
+
+ /*
+ * target object is unknown. Check whether to ignore the link,
+ * fail the replication, or add a partial link
+ */
+ ret = replmd_allow_missing_target(module, tmp_ctx, dsdb_dn->dn,
+ source_dn, is_obj_commit, guid,
+ la_entry->dsdb_repl_flags,
+ ignore_link, "Unknown");
+
+ } else if (target_res->count != 1) {
+ ldb_asprintf_errstring(ldb, "More than one object found matching objectGUID %s\n",
+ GUID_string(tmp_ctx, guid));
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ } else {
+ struct ldb_message *target_msg = target_res->msgs[0];
+
+ dsdb_dn->dn = talloc_steal(dsdb_dn, target_msg->dn);
+
+ /* Get the object's state (i.e. Not Deleted, Tombstone, etc) */
+ replmd_deletion_state(module, target_msg,
+ &target_deletion_state, NULL);
+
+ /*
+ * Check for deleted objects as per MS-DRSR 4.1.10.6.14
+ * ProcessLinkValue(). Link updates should not be sent for
+ * recycled and tombstone objects (deleting the links should
+ * happen when we delete the object). This probably means our
+ * copy of the target object isn't up to date.
+ */
+ if (target_deletion_state >= OBJECT_RECYCLED) {
+
+ /*
+ * target object is deleted. Check whether to ignore the
+ * link, fail the replication, or add a partial link
+ */
+ ret = replmd_allow_missing_target(module, tmp_ctx,
+ dsdb_dn->dn, source_dn,
+ is_obj_commit, guid,
+ la_entry->dsdb_repl_flags,
+ ignore_link, "Deleted");
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/**
+ * Extracts the key details about the source object for a
+ * linked-attribute entry.
+ * This returns the following details:
+ * @param ret_attr the schema details for the linked attribute
+ * @param source_msg the search result for the source object
+ */
+static int replmd_get_la_entry_source(struct ldb_module *module,
+ struct la_entry *la_entry,
+ TALLOC_CTX *mem_ctx,
+ const struct dsdb_attribute **ret_attr,
+ struct ldb_message **source_msg)
+{
+ struct drsuapi_DsReplicaLinkedAttribute *la = la_entry->la;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb, mem_ctx);
+ int ret;
+ const struct dsdb_attribute *attr;
+ struct ldb_result *res;
+ const char *attrs[4];
+
+/*
+linked_attributes[0]:
+ &objs->linked_attributes[i]: struct drsuapi_DsReplicaLinkedAttribute
+ identifier : *
+ identifier: struct drsuapi_DsReplicaObjectIdentifier
+ __ndr_size : 0x0000003a (58)
+ __ndr_size_sid : 0x00000000 (0)
+ guid : 8e95b6a9-13dd-4158-89db-3220a5be5cc7
+ sid : S-0-0
+ __ndr_size_dn : 0x00000000 (0)
+ dn : ''
+ attid : DRSUAPI_ATTID_member (0x1F)
+ value: struct drsuapi_DsAttributeValue
+ __ndr_size : 0x0000007e (126)
+ blob : *
+ blob : DATA_BLOB length=126
+ flags : 0x00000001 (1)
+ 1: DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE
+ originating_add_time : Wed Sep 2 22:20:01 2009 EST
+ meta_data: struct drsuapi_DsReplicaMetaData
+ version : 0x00000015 (21)
+ originating_change_time : Wed Sep 2 23:39:07 2009 EST
+ originating_invocation_id: 794640f3-18cf-40ee-a211-a93992b67a64
+ originating_usn : 0x000000000001e19c (123292)
+
+(for cases where the link is to a normal DN)
+ &target: struct drsuapi_DsReplicaObjectIdentifier3
+ __ndr_size : 0x0000007e (126)
+ __ndr_size_sid : 0x0000001c (28)
+ guid : 7639e594-db75-4086-b0d4-67890ae46031
+ sid : S-1-5-21-2848215498-2472035911-1947525656-19924
+ __ndr_size_dn : 0x00000022 (34)
+ dn : 'CN=UOne,OU=TestOU,DC=vsofs8,DC=com'
+ */
+
+ /* find the attribute being modified */
+ attr = dsdb_attribute_by_attributeID_id(schema, la->attid);
+ if (attr == NULL) {
+ struct GUID_txt_buf guid_str;
+ ldb_asprintf_errstring(ldb, "Unable to find attributeID 0x%x for link on <GUID=%s>",
+ la->attid,
+ GUID_buf_string(&la->identifier->guid,
+ &guid_str));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * All attributes listed here must be dealt with in some way
+ * by replmd_process_linked_attribute() otherwise in the case
+ * of isDeleted: FALSE the modify will fail with:
+ *
+ * Failed to apply linked attribute change 'attribute 'isDeleted':
+ * invalid modify flags on
+ * 'CN=g1_1527570609273,CN=Users,DC=samba,DC=example,DC=com':
+ * 0x0'
+ *
+ * This is becaue isDeleted is a Boolean, so FALSE is a
+ * legitimate value (set by Samba's deletetest.py)
+ */
+ attrs[0] = attr->lDAPDisplayName;
+ attrs[1] = "isDeleted";
+ attrs[2] = "isRecycled";
+ attrs[3] = NULL;
+
+ /*
+ * get the existing message from the db for the object with
+ * this GUID, returning attribute being modified. We will then
+ * use this msg as the basis for a modify call
+ */
+ ret = dsdb_module_search(module, mem_ctx, &res, NULL, LDB_SCOPE_SUBTREE, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SEARCH_ALL_PARTITIONS |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
+ DSDB_SEARCH_REVEAL_INTERNALS,
+ NULL,
+ "objectGUID=%s", GUID_string(mem_ctx, &la->identifier->guid));
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (res->count != 1) {
+ ldb_asprintf_errstring(ldb, "DRS linked attribute for GUID %s - DN not found",
+ GUID_string(mem_ctx, &la->identifier->guid));
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+
+ *source_msg = res->msgs[0];
+ *ret_attr = attr;
+
+ return LDB_SUCCESS;
+}
+
+/**
+ * Verifies the target object is known for a linked attribute
+ */
+static int replmd_verify_link_target(struct replmd_replicated_request *ar,
+ TALLOC_CTX *mem_ctx,
+ struct la_entry *la_entry,
+ struct ldb_dn *src_dn,
+ const struct dsdb_attribute *attr)
+{
+ int ret = LDB_SUCCESS;
+ struct ldb_module *module = ar->module;
+ struct dsdb_dn *tgt_dsdb_dn = NULL;
+ struct GUID guid = GUID_zero();
+ bool dummy;
+ WERROR status;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct drsuapi_DsReplicaLinkedAttribute *la = la_entry->la;
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb, mem_ctx);
+
+ /* the value blob for the attribute holds the target object DN */
+ status = dsdb_dn_la_from_blob(ldb, attr, schema, mem_ctx,
+ la->value.blob, &tgt_dsdb_dn);
+ if (!W_ERROR_IS_OK(status)) {
+ ldb_asprintf_errstring(ldb, "Failed to parsed linked attribute blob for %s on %s - %s\n",
+ attr->lDAPDisplayName,
+ ldb_dn_get_linearized(src_dn),
+ win_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * We can skip the target object checks if we're only syncing critical
+ * objects, or we know the target is up-to-date. If either case, we
+ * still continue even if the target doesn't exist
+ */
+ if ((la_entry->dsdb_repl_flags & (DSDB_REPL_FLAG_OBJECT_SUBSET |
+ DSDB_REPL_FLAG_TARGETS_UPTODATE)) == 0) {
+
+ ret = replmd_check_target_exists(module, tgt_dsdb_dn, la_entry,
+ src_dn, false, &guid, &dummy);
+ }
+
+ /*
+ * When we fail to find the target object, the error code we pass
+ * back here is really important. It flags back to the callers to
+ * retry this request with DRSUAPI_DRS_GET_TGT
+ */
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ret = replmd_replicated_request_werror(ar, WERR_DS_DRA_RECYCLED_TARGET);
+ }
+
+ return ret;
+}
+
+/**
+ * Finds the current active Parsed-DN value for a single-valued linked
+ * attribute, if one exists.
+ * @param ret_pdn assigned the active Parsed-DN, or NULL if none was found
+ * @returns LDB_SUCCESS (regardless of whether a match was found), unless
+ * an error occurred
+ */
+static int replmd_get_active_singleval_link(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct parsed_dn pdn_list[],
+ unsigned int count,
+ const struct dsdb_attribute *attr,
+ struct parsed_dn **ret_pdn)
+{
+ unsigned int i;
+
+ *ret_pdn = NULL;
+
+ if (!(attr->ldb_schema_attribute->flags & LDB_ATTR_FLAG_SINGLE_VALUE)) {
+
+ /* nothing to do for multi-valued linked attributes */
+ return LDB_SUCCESS;
+ }
+
+ for (i = 0; i < count; i++) {
+ int ret = LDB_SUCCESS;
+ struct parsed_dn *pdn = &pdn_list[i];
+
+ /* skip any inactive links */
+ if (dsdb_dn_is_deleted_val(pdn->v)) {
+ continue;
+ }
+
+ /* we've found an active value for this attribute */
+ *ret_pdn = pdn;
+
+ if (pdn->dsdb_dn == NULL) {
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ ret = really_parse_trusted_dn(mem_ctx, ldb, pdn,
+ attr->syntax->ldap_oid);
+ }
+
+ return ret;
+ }
+
+ /* no active link found */
+ return LDB_SUCCESS;
+}
+
+/**
+ * @returns true if the replication linked attribute info is newer than we
+ * already have in our DB
+ * @param pdn the existing linked attribute info in our DB
+ * @param la the new linked attribute info received during replication
+ */
+static bool replmd_link_update_is_newer(struct parsed_dn *pdn,
+ struct drsuapi_DsReplicaLinkedAttribute *la)
+{
+ /* see if this update is newer than what we have already */
+ struct GUID invocation_id = GUID_zero();
+ uint32_t version = 0;
+ NTTIME change_time = 0;
+
+ if (pdn == NULL) {
+
+ /* no existing info so update is newer */
+ return true;
+ }
+
+ dsdb_get_extended_dn_guid(pdn->dsdb_dn->dn, &invocation_id, "RMD_INVOCID");
+ dsdb_get_extended_dn_uint32(pdn->dsdb_dn->dn, &version, "RMD_VERSION");
+ dsdb_get_extended_dn_nttime(pdn->dsdb_dn->dn, &change_time, "RMD_CHANGETIME");
+
+ return replmd_update_is_newer(&invocation_id,
+ &la->meta_data.originating_invocation_id,
+ version,
+ la->meta_data.version,
+ change_time,
+ la->meta_data.originating_change_time);
+}
+
+/**
+ * Marks an existing linked attribute value as deleted in the DB
+ * @param pdn the parsed-DN of the target-value to delete
+ */
+static int replmd_delete_link_value(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *src_obj_dn,
+ const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ uint64_t seq_num,
+ bool is_active,
+ struct GUID *target_guid,
+ struct dsdb_dn *target_dsdb_dn,
+ struct ldb_val *output_val)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ time_t t;
+ NTTIME now;
+ const struct GUID *invocation_id = NULL;
+ int ret;
+
+ t = time(NULL);
+ unix_to_nt_time(&now, t);
+
+ invocation_id = samdb_ntds_invocation_id(ldb);
+ if (invocation_id == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* if the existing link is active, remove its backlink */
+ if (is_active) {
+
+ /*
+ * NOTE WELL: After this we will never (at runtime) be
+ * able to find this forward link (for instant
+ * removal) if/when the link target is deleted.
+ *
+ * We have dbcheck rules to cover this and cope otherwise
+ * by filtering at runtime (i.e. in the extended_dn module).
+ */
+ ret = replmd_add_backlink(module, replmd_private, schema,
+ src_obj_dn, target_guid, false,
+ attr, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* mark the existing value as deleted */
+ ret = replmd_update_la_val(mem_ctx, output_val, target_dsdb_dn,
+ target_dsdb_dn, invocation_id, seq_num,
+ seq_num, now, true);
+ return ret;
+}
+
+/**
+ * Checks for a conflict in single-valued link attributes, and tries to
+ * resolve the problem if possible.
+ *
+ * Single-valued links should only ever have one active value. If we already
+ * have an active link value, and during replication we receive an active link
+ * value for a different target DN, then we need to resolve this inconsistency
+ * and determine which value should be active. If the received info is better/
+ * newer than the existing link attribute, then we need to set our existing
+ * link as deleted. If the received info is worse/older, then we should continue
+ * to add it, but set it as an inactive link.
+ *
+ * Note that this is a corner-case that is unlikely to happen (but if it does
+ * happen, we don't want it to break replication completely).
+ *
+ * @param pdn_being_modified the parsed DN corresponding to the received link
+ * target (note this is NULL if the link does not already exist in our DB)
+ * @param pdn_list all the source object's Parsed-DNs for this attribute, i.e.
+ * any existing active or inactive values for the attribute in our DB.
+ * @param dsdb_dn the target DN for the received link attribute
+ * @param add_as_inactive gets set to true if the received link is worse than
+ * the existing link - it should still be added, but as an inactive link.
+ */
+static int replmd_check_singleval_la_conflict(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *src_obj_dn,
+ struct drsuapi_DsReplicaLinkedAttribute *la,
+ struct dsdb_dn *dsdb_dn,
+ struct parsed_dn *pdn_being_modified,
+ struct parsed_dn *pdn_list,
+ struct ldb_message_element *old_el,
+ const struct dsdb_schema *schema,
+ const struct dsdb_attribute *attr,
+ uint64_t seq_num,
+ bool *add_as_inactive)
+{
+ struct parsed_dn *active_pdn = NULL;
+ bool update_is_newer = false;
+ int ret;
+
+ /*
+ * check if there's a conflict for single-valued links, i.e. an active
+ * linked attribute already exists, but it has a different target value
+ */
+ ret = replmd_get_active_singleval_link(module, mem_ctx, pdn_list,
+ old_el->num_values, attr,
+ &active_pdn);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ * If no active value exists (or the received info is for the currently
+ * active value), then no conflict exists
+ */
+ if (active_pdn == NULL || active_pdn == pdn_being_modified) {
+ return LDB_SUCCESS;
+ }
+
+ DBG_WARNING("Link conflict for %s attribute on %s\n",
+ attr->lDAPDisplayName, ldb_dn_get_linearized(src_obj_dn));
+
+ /* Work out how to resolve the conflict based on which info is better */
+ update_is_newer = replmd_link_update_is_newer(active_pdn, la);
+
+ if (update_is_newer) {
+ DBG_WARNING("Using received value %s, over existing target %s\n",
+ ldb_dn_get_linearized(dsdb_dn->dn),
+ ldb_dn_get_linearized(active_pdn->dsdb_dn->dn));
+
+ /*
+ * Delete our existing active link. The received info will then
+ * be added (through normal link processing) as the active value
+ */
+ ret = replmd_delete_link_value(module, replmd_private, old_el,
+ src_obj_dn, schema, attr,
+ seq_num, true, &active_pdn->guid,
+ active_pdn->dsdb_dn,
+ active_pdn->v);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ } else {
+ DBG_WARNING("Using existing target %s, over received value %s\n",
+ ldb_dn_get_linearized(active_pdn->dsdb_dn->dn),
+ ldb_dn_get_linearized(dsdb_dn->dn));
+
+ /*
+ * we want to keep our existing active link and add the
+ * received link as inactive
+ */
+ *add_as_inactive = true;
+ }
+
+ return LDB_SUCCESS;
+}
+
+/**
+ * Processes one linked attribute received via replication.
+ * @param src_dn the DN of the source object for the link
+ * @param attr schema info for the linked attribute
+ * @param la_entry the linked attribute info received via DRS
+ * @param element_ctx mem context for msg->element[] (when adding a new value
+ * we need to realloc old_el->values)
+ * @param old_el the corresponding msg->element[] for the linked attribute
+ * @param pdn_list a (binary-searchable) parsed DN array for the existing link
+ * values in the msg. E.g. for a group, this is the existing members.
+ * @param change what got modified: either nothing, an existing link value was
+ * modified, or a new link value was added.
+ * @returns LDB_SUCCESS if OK, an error otherwise
+ */
+static int replmd_process_linked_attribute(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct replmd_private *replmd_private,
+ struct ldb_dn *src_dn,
+ const struct dsdb_attribute *attr,
+ struct la_entry *la_entry,
+ struct ldb_request *parent,
+ TALLOC_CTX *element_ctx,
+ struct ldb_message_element *old_el,
+ struct parsed_dn *pdn_list,
+ replmd_link_changed *change)
+{
+ struct drsuapi_DsReplicaLinkedAttribute *la = la_entry->la;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const struct dsdb_schema *schema = dsdb_get_schema(ldb, mem_ctx);
+ int ret;
+ struct dsdb_dn *dsdb_dn = NULL;
+ uint64_t seq_num = 0;
+ struct parsed_dn *pdn, *next;
+ struct GUID guid = GUID_zero();
+ bool active = (la->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE)?true:false;
+ bool ignore_link;
+ struct dsdb_dn *old_dsdb_dn = NULL;
+ struct ldb_val *val_to_update = NULL;
+ bool add_as_inactive = false;
+ WERROR status;
+
+ *change = LINK_CHANGE_NONE;
+
+ /* the value blob for the attribute holds the target object DN */
+ status = dsdb_dn_la_from_blob(ldb, attr, schema, mem_ctx,
+ la->value.blob, &dsdb_dn);
+ if (!W_ERROR_IS_OK(status)) {
+ ldb_asprintf_errstring(ldb, "Failed to parsed linked attribute blob for %s on %s - %s\n",
+ attr->lDAPDisplayName,
+ ldb_dn_get_linearized(src_dn),
+ win_errstr(status));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = replmd_check_target_exists(module, dsdb_dn, la_entry, src_dn,
+ true, &guid, &ignore_link);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ * there are some cases where the target object doesn't exist, but it's
+ * OK to ignore the linked attribute
+ */
+ if (ignore_link) {
+ return ret;
+ }
+
+ /* see if this link already exists */
+ ret = parsed_dn_find(ldb, pdn_list, old_el->num_values,
+ &guid,
+ dsdb_dn->dn,
+ dsdb_dn->extra_part, 0,
+ &pdn, &next,
+ attr->syntax->ldap_oid,
+ true);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (!replmd_link_update_is_newer(pdn, la)) {
+ DEBUG(3,("Discarding older DRS linked attribute update to %s on %s from %s\n",
+ old_el->name, ldb_dn_get_linearized(src_dn),
+ GUID_string(mem_ctx, &la->meta_data.originating_invocation_id)));
+ return LDB_SUCCESS;
+ }
+
+ /* get a seq_num for this change */
+ ret = ldb_sequence_number(ldb, LDB_SEQ_NEXT, &seq_num);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ * check for single-valued link conflicts, i.e. an active linked
+ * attribute already exists, but it has a different target value
+ */
+ if (active) {
+ ret = replmd_check_singleval_la_conflict(module, replmd_private,
+ mem_ctx, src_dn, la,
+ dsdb_dn, pdn, pdn_list,
+ old_el, schema, attr,
+ seq_num,
+ &add_as_inactive);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (pdn != NULL) {
+ uint32_t rmd_flags = dsdb_dn_rmd_flags(pdn->dsdb_dn->dn);
+
+ if (!(rmd_flags & DSDB_RMD_FLAG_DELETED)) {
+ /* remove the existing backlink */
+ ret = replmd_add_backlink(module, replmd_private,
+ schema,
+ src_dn,
+ &pdn->guid, false, attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ val_to_update = pdn->v;
+ old_dsdb_dn = pdn->dsdb_dn;
+ *change = LINK_CHANGE_MODIFIED;
+
+ } else {
+ unsigned offset;
+
+ /*
+ * We know where the new one needs to be, from the *next
+ * pointer into pdn_list.
+ */
+ if (next == NULL) {
+ offset = old_el->num_values;
+ } else {
+ if (next->dsdb_dn == NULL) {
+ ret = really_parse_trusted_dn(mem_ctx, ldb, next,
+ attr->syntax->ldap_oid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ offset = next - pdn_list;
+ if (offset > old_el->num_values) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+
+ old_el->values = talloc_realloc(element_ctx, old_el->values,
+ struct ldb_val, old_el->num_values+1);
+ if (!old_el->values) {
+ ldb_module_oom(module);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (offset != old_el->num_values) {
+ memmove(&old_el->values[offset + 1], &old_el->values[offset],
+ (old_el->num_values - offset) * sizeof(old_el->values[0]));
+ }
+
+ old_el->num_values++;
+
+ val_to_update = &old_el->values[offset];
+ old_dsdb_dn = NULL;
+ *change = LINK_CHANGE_ADDED;
+ }
+
+ /* set the link attribute's value to the info that was received */
+ ret = replmd_set_la_val(mem_ctx, val_to_update, dsdb_dn, old_dsdb_dn,
+ &la->meta_data.originating_invocation_id,
+ la->meta_data.originating_usn, seq_num,
+ la->meta_data.originating_change_time,
+ la->meta_data.version,
+ !active);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (add_as_inactive) {
+
+ /* Set the new link as inactive/deleted to avoid conflicts */
+ ret = replmd_delete_link_value(module, replmd_private, old_el,
+ src_dn, schema, attr, seq_num,
+ false, &guid, dsdb_dn,
+ val_to_update);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ } else if (active) {
+
+ /* if the new link is active, then add the new backlink */
+ ret = replmd_add_backlink(module, replmd_private,
+ schema,
+ src_dn,
+ &guid, true, attr,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ ret = dsdb_check_single_valued_link(attr, old_el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ old_el->flags |= LDB_FLAG_INTERNAL_DISABLE_SINGLE_VALUE_CHECK;
+
+ return ret;
+}
+
+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);
+}
+
+
+/*
+ we hook into the transaction operations to allow us to
+ perform the linked attribute updates at the end of the whole
+ transaction. This allows a forward linked attribute to be created
+ before the object is created. During a vampire, w2k8 sends us linked
+ attributes before the objects they are part of.
+ */
+static int replmd_start_transaction(struct ldb_module *module)
+{
+ /* create our private structure for this transaction */
+ struct replmd_private *replmd_private = talloc_get_type(ldb_module_get_private(module),
+ struct replmd_private);
+ replmd_txn_cleanup(replmd_private);
+
+ /* free any leftover mod_usn records from cancelled
+ transactions */
+ while (replmd_private->ncs) {
+ struct nc_entry *e = replmd_private->ncs;
+ DLIST_REMOVE(replmd_private->ncs, e);
+ talloc_free(e);
+ }
+
+ replmd_private->originating_updates = false;
+
+ return ldb_next_start_trans(module);
+}
+
+/**
+ * Processes a group of linked attributes that apply to the same source-object
+ * and attribute-ID (and were received in the same replication chunk).
+ */
+static int replmd_process_la_group(struct ldb_module *module,
+ struct replmd_private *replmd_private,
+ struct la_group *la_group)
+{
+ struct la_entry *la = NULL;
+ struct la_entry *prev = NULL;
+ int ret;
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct la_entry *first_la = DLIST_TAIL(la_group->la_entries);
+ struct ldb_message *msg = NULL;
+ enum deletion_state deletion_state = OBJECT_NOT_DELETED;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const struct dsdb_attribute *attr = NULL;
+ struct ldb_message_element *old_el = NULL;
+ struct parsed_dn *pdn_list = NULL;
+ replmd_link_changed change_type;
+ uint32_t num_changes = 0;
+ time_t t;
+ uint64_t seq_num = 0;
+
+ tmp_ctx = talloc_new(la_group);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /*
+ * get the attribute being modified and the search result for the
+ * source object
+ */
+ ret = replmd_get_la_entry_source(module, first_la, tmp_ctx, &attr,
+ &msg);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ * Check for deleted objects per MS-DRSR 4.1.10.6.14
+ * ProcessLinkValue, because link updates are not applied to
+ * recycled and tombstone objects. We don't have to delete
+ * any existing link, that should have happened when the
+ * object deletion was replicated or initiated.
+ *
+ * This needs isDeleted and isRecycled to be included as
+ * attributes in the search and so in msg if set.
+ */
+ replmd_deletion_state(module, msg, &deletion_state, NULL);
+
+ if (deletion_state >= OBJECT_RECYCLED) {
+ TALLOC_FREE(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ /*
+ * Now that we know the deletion_state, remove the extra
+ * attributes added for that purpose. We need to do this
+ * otherwise in the case of isDeleted: FALSE the modify will
+ * fail with:
+ *
+ * Failed to apply linked attribute change 'attribute 'isDeleted':
+ * invalid modify flags on
+ * 'CN=g1_1527570609273,CN=Users,DC=samba,DC=example,DC=com':
+ * 0x0'
+ *
+ * This is becaue isDeleted is a Boolean, so FALSE is a
+ * legitimate value (set by Samba's deletetest.py)
+ */
+ ldb_msg_remove_attr(msg, "isDeleted");
+ ldb_msg_remove_attr(msg, "isRecycled");
+
+ /* get the msg->element[] for the link attribute being processed */
+ old_el = ldb_msg_find_element(msg, attr->lDAPDisplayName);
+ if (old_el == NULL) {
+ ret = ldb_msg_add_empty(msg, attr->lDAPDisplayName,
+ LDB_FLAG_MOD_REPLACE, &old_el);
+ if (ret != LDB_SUCCESS) {
+ ldb_module_oom(module);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ } else {
+ old_el->flags = LDB_FLAG_MOD_REPLACE;
+ }
+
+ /*
+ * go through and process the link target value(s) for this particular
+ * source object and attribute. For optimization, the same msg is used
+ * across multiple calls to replmd_process_linked_attribute().
+ * Note that we should not add or remove any msg attributes inside the
+ * loop (we should only add/modify *values* for the attribute being
+ * processed). Otherwise msg->elements is realloc'd and old_el/pdn_list
+ * pointers will be invalidated
+ */
+ for (la = DLIST_TAIL(la_group->la_entries); la; la=prev) {
+ prev = DLIST_PREV(la);
+ DLIST_REMOVE(la_group->la_entries, la);
+
+ /*
+ * parse the existing links (this can be costly for a large
+ * group, so we try to minimize the times we do it)
+ */
+ if (pdn_list == NULL) {
+ ret = get_parsed_dns_trusted_fallback(module,
+ replmd_private,
+ tmp_ctx, old_el,
+ &pdn_list,
+ attr->syntax->ldap_oid,
+ NULL);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ ret = replmd_process_linked_attribute(module, tmp_ctx,
+ replmd_private,
+ msg->dn, attr, la, NULL,
+ msg->elements, old_el,
+ pdn_list, &change_type);
+ if (ret != LDB_SUCCESS) {
+ replmd_txn_cleanup(replmd_private);
+ return ret;
+ }
+
+ /*
+ * Adding a link reallocs memory, and so invalidates all the
+ * pointers in pdn_list. Reparse the PDNs on the next loop
+ */
+ if (change_type == LINK_CHANGE_ADDED) {
+ TALLOC_FREE(pdn_list);
+ }
+
+ if (change_type != LINK_CHANGE_NONE) {
+ num_changes++;
+ }
+
+ if ((++replmd_private->num_processed % 8192) == 0) {
+ DBG_NOTICE("Processed %u/%u linked attributes\n",
+ replmd_private->num_processed,
+ replmd_private->total_links);
+ }
+ }
+
+ /*
+ * it's possible we're already up-to-date and so don't need to modify
+ * the object at all (e.g. doing a 'drs replicate --full-sync')
+ */
+ if (num_changes == 0) {
+ TALLOC_FREE(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ /*
+ * Note that adding the whenChanged/etc attributes below will realloc
+ * msg->elements, invalidating the existing element/parsed-DN pointers
+ */
+ old_el = NULL;
+ TALLOC_FREE(pdn_list);
+
+ /* update whenChanged/uSNChanged as the object has changed */
+ t = time(NULL);
+ ret = ldb_sequence_number(ldb, LDB_SEQ_HIGHEST_SEQ,
+ &seq_num);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = add_time_element(msg, "whenChanged", t);
+ if (ret != LDB_SUCCESS) {
+ ldb_operr(ldb);
+ return ret;
+ }
+
+ ret = add_uint64_element(ldb, msg, "uSNChanged", seq_num);
+ if (ret != LDB_SUCCESS) {
+ ldb_operr(ldb);
+ return ret;
+ }
+
+ /* apply the link changes to the source object */
+ ret = linked_attr_modify(module, msg, NULL);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_WARNING,
+ "Failed to apply linked attribute change "
+ "Error: '%s' DN: '%s' Attribute: '%s'\n",
+ ldb_errstring(ldb),
+ ldb_dn_get_linearized(msg->dn),
+ attr->lDAPDisplayName);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+/*
+ on prepare commit we loop over our queued la_context structures and
+ apply each of them
+ */
+static int replmd_prepare_commit(struct ldb_module *module)
+{
+ struct replmd_private *replmd_private =
+ talloc_get_type(ldb_module_get_private(module), struct replmd_private);
+ struct la_group *la_group, *prev;
+ int ret;
+
+ if (replmd_private->la_list != NULL) {
+ DBG_NOTICE("Processing linked attributes\n");
+ }
+
+ /*
+ * Walk the list of linked attributes from DRS replication.
+ *
+ * We walk backwards, to do the first entry first, as we
+ * added the entries with DLIST_ADD() which puts them at the
+ * start of the list
+ *
+ * Links are grouped together so we process links for the same
+ * source object in one go.
+ */
+ for (la_group = DLIST_TAIL(replmd_private->la_list);
+ la_group != NULL;
+ la_group = prev) {
+
+ prev = DLIST_PREV(la_group);
+ DLIST_REMOVE(replmd_private->la_list, la_group);
+ ret = replmd_process_la_group(module, replmd_private,
+ la_group);
+ if (ret != LDB_SUCCESS) {
+ replmd_txn_cleanup(replmd_private);
+ return ret;
+ }
+ }
+
+ replmd_txn_cleanup(replmd_private);
+
+ /* possibly change @REPLCHANGED */
+ ret = replmd_notify_store(module, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_prepare_commit(module);
+}
+
+static int replmd_del_transaction(struct ldb_module *module)
+{
+ struct replmd_private *replmd_private =
+ talloc_get_type(ldb_module_get_private(module), struct replmd_private);
+ replmd_txn_cleanup(replmd_private);
+
+ return ldb_next_del_trans(module);
+}
+
+
+static const struct ldb_module_ops ldb_repl_meta_data_module_ops = {
+ .name = "repl_meta_data",
+ .init_context = replmd_init,
+ .add = replmd_add,
+ .modify = replmd_modify,
+ .rename = replmd_rename,
+ .del = replmd_delete,
+ .extended = replmd_extended,
+ .start_transaction = replmd_start_transaction,
+ .prepare_commit = replmd_prepare_commit,
+ .del_transaction = replmd_del_transaction,
+};
+
+int ldb_repl_meta_data_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_repl_meta_data_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/resolve_oids.c b/source4/dsdb/samdb/ldb_modules/resolve_oids.c
new file mode 100644
index 0000000..b5c5f8e
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/resolve_oids.c
@@ -0,0 +1,704 @@
+/*
+ ldb database library
+
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/samdb.h"
+
+static int resolve_oids_need_value(struct ldb_context *ldb,
+ struct dsdb_schema *schema,
+ const struct dsdb_attribute *a,
+ const struct ldb_val *valp)
+{
+ const struct dsdb_attribute *va = NULL;
+ const struct dsdb_class *vo = NULL;
+ const void *p2;
+ char *str = NULL;
+
+ if (a->syntax->oMSyntax != 6) {
+ return LDB_ERR_COMPARE_FALSE;
+ }
+
+ if (valp) {
+ p2 = memchr(valp->data, '.', valp->length);
+ } else {
+ p2 = NULL;
+ }
+
+ if (!p2) {
+ return LDB_ERR_COMPARE_FALSE;
+ }
+
+ switch (a->attributeID_id) {
+ case DRSUAPI_ATTID_objectClass:
+ case DRSUAPI_ATTID_subClassOf:
+ case DRSUAPI_ATTID_auxiliaryClass:
+ case DRSUAPI_ATTID_systemPossSuperiors:
+ case DRSUAPI_ATTID_possSuperiors:
+ str = talloc_strndup(ldb, (char *)valp->data, valp->length);
+ if (!str) {
+ return ldb_oom(ldb);
+ }
+ vo = dsdb_class_by_governsID_oid(schema, str);
+ talloc_free(str);
+ if (!vo) {
+ return LDB_ERR_COMPARE_FALSE;
+ }
+ return LDB_ERR_COMPARE_TRUE;
+ case DRSUAPI_ATTID_systemMustContain:
+ case DRSUAPI_ATTID_systemMayContain:
+ case DRSUAPI_ATTID_mustContain:
+ case DRSUAPI_ATTID_mayContain:
+ str = talloc_strndup(ldb, (char *)valp->data, valp->length);
+ if (!str) {
+ return ldb_oom(ldb);
+ }
+ va = dsdb_attribute_by_attributeID_oid(schema, str);
+ talloc_free(str);
+ if (!va) {
+ return LDB_ERR_COMPARE_FALSE;
+ }
+ return LDB_ERR_COMPARE_TRUE;
+ case DRSUAPI_ATTID_governsID:
+ case DRSUAPI_ATTID_attributeID:
+ case DRSUAPI_ATTID_attributeSyntax:
+ return LDB_ERR_COMPARE_FALSE;
+ }
+
+ return LDB_ERR_COMPARE_FALSE;
+}
+
+static int resolve_oids_parse_tree_need(struct ldb_context *ldb,
+ struct dsdb_schema *schema,
+ const struct ldb_parse_tree *tree)
+{
+ unsigned int i;
+ const struct dsdb_attribute *a = NULL;
+ const char *attr;
+ const char *p1;
+ const void *p2;
+ const struct ldb_val *valp = NULL;
+ int ret;
+
+ switch (tree->operation) {
+ case LDB_OP_AND:
+ case LDB_OP_OR:
+ for (i=0;i<tree->u.list.num_elements;i++) {
+ ret = resolve_oids_parse_tree_need(ldb, schema,
+ tree->u.list.elements[i]);
+ if (ret != LDB_ERR_COMPARE_FALSE) {
+ return ret;
+ }
+ }
+ return LDB_ERR_COMPARE_FALSE;
+ case LDB_OP_NOT:
+ return resolve_oids_parse_tree_need(ldb, schema,
+ tree->u.isnot.child);
+ case LDB_OP_EQUALITY:
+ case LDB_OP_GREATER:
+ case LDB_OP_LESS:
+ case LDB_OP_APPROX:
+ attr = tree->u.equality.attr;
+ valp = &tree->u.equality.value;
+ break;
+ case LDB_OP_SUBSTRING:
+ attr = tree->u.substring.attr;
+ break;
+ case LDB_OP_PRESENT:
+ attr = tree->u.present.attr;
+ break;
+ case LDB_OP_EXTENDED:
+ attr = tree->u.extended.attr;
+ valp = &tree->u.extended.value;
+ break;
+ default:
+ return LDB_ERR_COMPARE_FALSE;
+ }
+
+ p1 = strchr(attr, '.');
+
+ if (valp) {
+ p2 = memchr(valp->data, '.', valp->length);
+ } else {
+ p2 = NULL;
+ }
+
+ if (!p1 && !p2) {
+ return LDB_ERR_COMPARE_FALSE;
+ }
+
+ if (p1) {
+ a = dsdb_attribute_by_attributeID_oid(schema, attr);
+ } else {
+ a = dsdb_attribute_by_lDAPDisplayName(schema, attr);
+ }
+ if (!a) {
+ return LDB_ERR_COMPARE_FALSE;
+ }
+
+ if (!p2) {
+ return LDB_ERR_COMPARE_FALSE;
+ }
+
+ return resolve_oids_need_value(ldb, schema, a, valp);
+}
+
+static int resolve_oids_element_need(struct ldb_context *ldb,
+ struct dsdb_schema *schema,
+ const struct ldb_message_element *el)
+{
+ unsigned int i;
+ const struct dsdb_attribute *a = NULL;
+ const char *p1;
+
+ p1 = strchr(el->name, '.');
+
+ if (p1) {
+ a = dsdb_attribute_by_attributeID_oid(schema, el->name);
+ } else {
+ a = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ }
+ if (!a) {
+ return LDB_ERR_COMPARE_FALSE;
+ }
+
+ for (i=0; i < el->num_values; i++) {
+ int ret;
+ ret = resolve_oids_need_value(ldb, schema, a,
+ &el->values[i]);
+ if (ret != LDB_ERR_COMPARE_FALSE) {
+ return ret;
+ }
+ }
+
+ return LDB_ERR_COMPARE_FALSE;
+}
+
+static int resolve_oids_message_need(struct ldb_context *ldb,
+ struct dsdb_schema *schema,
+ const struct ldb_message *msg)
+{
+ unsigned int i;
+
+ for (i=0; i < msg->num_elements; i++) {
+ int ret;
+ ret = resolve_oids_element_need(ldb, schema,
+ &msg->elements[i]);
+ if (ret != LDB_ERR_COMPARE_FALSE) {
+ return ret;
+ }
+ }
+
+ return LDB_ERR_COMPARE_FALSE;
+}
+
+static int resolve_oids_replace_value(struct ldb_context *ldb,
+ struct dsdb_schema *schema,
+ const struct dsdb_attribute *a,
+ struct ldb_val *valp)
+{
+ const struct dsdb_attribute *va = NULL;
+ const struct dsdb_class *vo = NULL;
+ const void *p2;
+ char *str = NULL;
+
+ if (a->syntax->oMSyntax != 6) {
+ return LDB_SUCCESS;
+ }
+
+ if (valp) {
+ p2 = memchr(valp->data, '.', valp->length);
+ } else {
+ p2 = NULL;
+ }
+
+ if (!p2) {
+ return LDB_SUCCESS;
+ }
+
+ switch (a->attributeID_id) {
+ case DRSUAPI_ATTID_objectClass:
+ case DRSUAPI_ATTID_subClassOf:
+ case DRSUAPI_ATTID_auxiliaryClass:
+ case DRSUAPI_ATTID_systemPossSuperiors:
+ case DRSUAPI_ATTID_possSuperiors:
+ str = talloc_strndup(schema, (char *)valp->data, valp->length);
+ if (!str) {
+ return ldb_oom(ldb);
+ }
+ vo = dsdb_class_by_governsID_oid(schema, str);
+ talloc_free(str);
+ if (!vo) {
+ return LDB_SUCCESS;
+ }
+ *valp = data_blob_string_const(vo->lDAPDisplayName);
+ return LDB_SUCCESS;
+ case DRSUAPI_ATTID_systemMustContain:
+ case DRSUAPI_ATTID_systemMayContain:
+ case DRSUAPI_ATTID_mustContain:
+ case DRSUAPI_ATTID_mayContain:
+ str = talloc_strndup(schema, (char *)valp->data, valp->length);
+ if (!str) {
+ return ldb_oom(ldb);
+ }
+ va = dsdb_attribute_by_attributeID_oid(schema, str);
+ talloc_free(str);
+ if (!va) {
+ return LDB_SUCCESS;
+ }
+ *valp = data_blob_string_const(va->lDAPDisplayName);
+ return LDB_SUCCESS;
+ case DRSUAPI_ATTID_governsID:
+ case DRSUAPI_ATTID_attributeID:
+ case DRSUAPI_ATTID_attributeSyntax:
+ return LDB_SUCCESS;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int resolve_oids_parse_tree_replace(struct ldb_context *ldb,
+ struct dsdb_schema *schema,
+ struct ldb_parse_tree *tree)
+{
+ unsigned int i;
+ const struct dsdb_attribute *a = NULL;
+ const char **attrp;
+ const char *p1;
+ const void *p2;
+ struct ldb_val *valp = NULL;
+ int ret;
+
+ switch (tree->operation) {
+ case LDB_OP_AND:
+ case LDB_OP_OR:
+ for (i=0;i<tree->u.list.num_elements;i++) {
+ ret = resolve_oids_parse_tree_replace(ldb, schema,
+ tree->u.list.elements[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ return LDB_SUCCESS;
+ case LDB_OP_NOT:
+ return resolve_oids_parse_tree_replace(ldb, schema,
+ tree->u.isnot.child);
+ case LDB_OP_EQUALITY:
+ case LDB_OP_GREATER:
+ case LDB_OP_LESS:
+ case LDB_OP_APPROX:
+ attrp = &tree->u.equality.attr;
+ valp = &tree->u.equality.value;
+ break;
+ case LDB_OP_SUBSTRING:
+ attrp = &tree->u.substring.attr;
+ break;
+ case LDB_OP_PRESENT:
+ attrp = &tree->u.present.attr;
+ break;
+ case LDB_OP_EXTENDED:
+ attrp = &tree->u.extended.attr;
+ valp = &tree->u.extended.value;
+ break;
+ default:
+ return LDB_SUCCESS;
+ }
+
+ p1 = strchr(*attrp, '.');
+
+ if (valp) {
+ p2 = memchr(valp->data, '.', valp->length);
+ } else {
+ p2 = NULL;
+ }
+
+ if (!p1 && !p2) {
+ return LDB_SUCCESS;
+ }
+
+ if (p1) {
+ a = dsdb_attribute_by_attributeID_oid(schema, *attrp);
+ } else {
+ a = dsdb_attribute_by_lDAPDisplayName(schema, *attrp);
+ }
+ if (!a) {
+ return LDB_SUCCESS;
+ }
+
+ *attrp = a->lDAPDisplayName;
+
+ if (!p2) {
+ return LDB_SUCCESS;
+ }
+
+ if (a->syntax->oMSyntax != 6) {
+ return LDB_SUCCESS;
+ }
+
+ return resolve_oids_replace_value(ldb, schema, a, valp);
+}
+
+static int resolve_oids_element_replace(struct ldb_context *ldb,
+ struct dsdb_schema *schema,
+ struct ldb_message_element *el)
+{
+ unsigned int i;
+ const struct dsdb_attribute *a = NULL;
+ const char *p1;
+
+ p1 = strchr(el->name, '.');
+
+ if (p1) {
+ a = dsdb_attribute_by_attributeID_oid(schema, el->name);
+ } else {
+ a = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
+ }
+ if (!a) {
+ return LDB_SUCCESS;
+ }
+
+ el->name = a->lDAPDisplayName;
+
+ for (i=0; i < el->num_values; i++) {
+ int ret;
+ ret = resolve_oids_replace_value(ldb, schema, a,
+ &el->values[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int resolve_oids_message_replace(struct ldb_context *ldb,
+ struct dsdb_schema *schema,
+ struct ldb_message *msg)
+{
+ unsigned int i;
+
+ for (i=0; i < msg->num_elements; i++) {
+ int ret;
+ ret = resolve_oids_element_replace(ldb, schema,
+ &msg->elements[i]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+struct resolve_oids_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+};
+
+static int resolve_oids_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct resolve_oids_context *ac;
+
+ ac = talloc_get_type_abort(req->context, struct resolve_oids_context);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ return ldb_module_send_entry(ac->req, ares->message, ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(ac->req, ares->referral);
+
+ case LDB_REPLY_DONE:
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, LDB_SUCCESS);
+
+ }
+ return LDB_SUCCESS;
+}
+
+static int resolve_oids_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct dsdb_schema *schema;
+ struct ldb_parse_tree *tree;
+ struct ldb_request *down_req;
+ struct resolve_oids_context *ac;
+ int ret;
+ bool needed = false;
+ const char * const *attrs1;
+ const char **attrs2;
+ unsigned int i;
+
+ ldb = ldb_module_get_ctx(module);
+ schema = dsdb_get_schema(ldb, NULL);
+
+ if (!schema) {
+ return ldb_next_request(module, req);
+ }
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.search.base)) {
+ return ldb_next_request(module, req);
+ }
+
+ ret = resolve_oids_parse_tree_need(ldb, schema,
+ req->op.search.tree);
+ if (ret == LDB_ERR_COMPARE_TRUE) {
+ needed = true;
+ } else if (ret != LDB_ERR_COMPARE_FALSE) {
+ return ret;
+ }
+
+ attrs1 = req->op.search.attrs;
+
+ for (i=0; attrs1 && attrs1[i]; i++) {
+ const char *p;
+ const struct dsdb_attribute *a;
+
+ p = strchr(attrs1[i], '.');
+ if (p == NULL) {
+ continue;
+ }
+
+ a = dsdb_attribute_by_attributeID_oid(schema, attrs1[i]);
+ if (a == NULL) {
+ continue;
+ }
+
+ needed = true;
+ break;
+ }
+
+ if (!needed) {
+ return ldb_next_request(module, req);
+ }
+
+ ac = talloc(req, struct resolve_oids_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->module = module;
+ ac->req = req;
+
+ tree = ldb_parse_tree_copy_shallow(ac, req->op.search.tree);
+ if (!tree) {
+ return ldb_oom(ldb);
+ }
+
+ schema = talloc_reference(tree, schema);
+ if (!schema) {
+ return ldb_oom(ldb);
+ }
+
+ ret = resolve_oids_parse_tree_replace(ldb, schema,
+ tree);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ attrs2 = str_list_copy_const(ac,
+ discard_const_p(const char *, req->op.search.attrs));
+ if (req->op.search.attrs && !attrs2) {
+ return ldb_oom(ldb);
+ }
+
+ for (i=0; attrs2 && attrs2[i]; i++) {
+ const char *p;
+ const struct dsdb_attribute *a;
+
+ p = strchr(attrs2[i], '.');
+ if (p == NULL) {
+ continue;
+ }
+
+ a = dsdb_attribute_by_attributeID_oid(schema, attrs2[i]);
+ if (a == NULL) {
+ continue;
+ }
+
+ attrs2[i] = a->lDAPDisplayName;
+ }
+
+ ret = ldb_build_search_req_ex(&down_req, ldb, ac,
+ req->op.search.base,
+ req->op.search.scope,
+ tree,
+ attrs2,
+ req->controls,
+ ac, resolve_oids_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* go on with the call chain */
+ return ldb_next_request(module, down_req);
+}
+
+static int resolve_oids_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct dsdb_schema *schema;
+ int ret;
+ struct ldb_message *msg;
+ struct ldb_request *down_req;
+ struct resolve_oids_context *ac;
+
+ ldb = ldb_module_get_ctx(module);
+ schema = dsdb_get_schema(ldb, NULL);
+
+ if (!schema) {
+ return ldb_next_request(module, req);
+ }
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ ret = resolve_oids_message_need(ldb, schema,
+ req->op.add.message);
+ if (ret == LDB_ERR_COMPARE_FALSE) {
+ return ldb_next_request(module, req);
+ } else if (ret != LDB_ERR_COMPARE_TRUE) {
+ return ret;
+ }
+
+ ac = talloc(req, struct resolve_oids_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->module = module;
+ ac->req = req;
+
+ msg = ldb_msg_copy_shallow(ac, ac->req->op.add.message);
+ if (!msg) {
+ return ldb_oom(ldb);
+ }
+
+ if (!talloc_reference(msg, schema)) {
+ return ldb_oom(ldb);
+ }
+
+ ret = resolve_oids_message_replace(ldb, schema, msg);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_build_add_req(&down_req, ldb, ac,
+ msg,
+ req->controls,
+ ac, resolve_oids_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* go on with the call chain */
+ return ldb_next_request(module, down_req);
+}
+
+static int resolve_oids_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct dsdb_schema *schema;
+ int ret;
+ struct ldb_message *msg;
+ struct ldb_request *down_req;
+ struct resolve_oids_context *ac;
+
+ ldb = ldb_module_get_ctx(module);
+ schema = dsdb_get_schema(ldb, NULL);
+
+ if (!schema) {
+ return ldb_next_request(module, req);
+ }
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ ret = resolve_oids_message_need(ldb, schema,
+ req->op.mod.message);
+ if (ret == LDB_ERR_COMPARE_FALSE) {
+ return ldb_next_request(module, req);
+ } else if (ret != LDB_ERR_COMPARE_TRUE) {
+ return ret;
+ }
+
+ ac = talloc(req, struct resolve_oids_context);
+ if (ac == NULL) {
+ return ldb_oom(ldb);
+ }
+ ac->module = module;
+ ac->req = req;
+
+ /* we have to copy the message as the caller might have it as a const */
+ msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
+ if (msg == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ if (!talloc_reference(msg, schema)) {
+ return ldb_oom(ldb);
+ }
+
+ ret = resolve_oids_message_replace(ldb, schema, msg);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_build_mod_req(&down_req, ldb, ac,
+ msg,
+ req->controls,
+ ac, resolve_oids_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* go on with the call chain */
+ return ldb_next_request(module, down_req);
+}
+
+static const struct ldb_module_ops ldb_resolve_oids_module_ops = {
+ .name = "resolve_oids",
+ .search = resolve_oids_search,
+ .add = resolve_oids_add,
+ .modify = resolve_oids_modify,
+};
+
+
+int ldb_resolve_oids_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_resolve_oids_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/ridalloc.c b/source4/dsdb/samdb/ldb_modules/ridalloc.c
new file mode 100644
index 0000000..072a434
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/ridalloc.c
@@ -0,0 +1,829 @@
+/*
+ RID allocation helper functions
+
+ Copyright (C) Andrew Bartlett 2010
+ Copyright (C) Andrew Tridgell 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: RID allocation logic
+ *
+ * Description: manage RID Set and RID Manager objects
+ *
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "lib/util/server_id.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "lib/messaging/irpc.h"
+#include "param/param.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "dsdb/samdb/ldb_modules/ridalloc.h"
+
+/*
+ Note: the RID allocation attributes in AD are very badly named. Here
+ is what we think they really do:
+
+ in RID Set object:
+ - rIDPreviousAllocationPool: the pool which a DC is currently
+ pulling RIDs from. Managed by client DC
+
+ - rIDAllocationPool: the pool that the DC will switch to next,
+ when rIDPreviousAllocationPool is exhausted. Managed by RID Manager.
+
+ - rIDNextRID: the last RID allocated by this DC. Managed by client DC
+
+ in RID Manager object:
+ - rIDAvailablePool: the pool where the RID Manager gets new rID
+ pools from when it gets a EXOP_RID_ALLOC getncchanges call (or
+ locally when the DC is the RID Manager)
+ */
+
+
+/*
+ make a IRPC call to the drepl task to ask it to get the RID
+ Manager to give us another RID pool.
+
+ This function just sends the message to the drepl task then
+ returns immediately. It should be called well before we
+ completely run out of RIDs
+ */
+static int ridalloc_poke_rid_manager(struct ldb_module *module)
+{
+ struct imessaging_context *msg;
+ unsigned num_servers;
+ struct server_id *servers;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct loadparm_context *lp_ctx =
+ (struct loadparm_context *)ldb_get_opaque(ldb, "loadparm");
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ NTSTATUS status;
+
+ msg = imessaging_client_init(tmp_ctx, lp_ctx,
+ ldb_get_event_context(ldb));
+ if (!msg) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "Failed to send MSG_DREPL_ALLOCATE_RID, "
+ "unable init client messaging context");
+ DEBUG(3,(__location__ ": Failed to create messaging context\n"));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ status = irpc_servers_byname(msg, msg, "dreplsrv",
+ &num_servers, &servers);
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "Failed to send MSG_DREPL_ALLOCATE_RID, "
+ "unable to locate dreplsrv");
+ /* this means the drepl service is not running */
+ talloc_free(tmp_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ status = imessaging_send(msg, servers[0], MSG_DREPL_ALLOCATE_RID, NULL);
+
+ /* Only error out if an error happened, not on STATUS_MORE_ENTRIES, ie a delayed message */
+ if (NT_STATUS_IS_ERR(status)) {
+ struct server_id_buf idbuf;
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "Failed to send MSG_DREPL_ALLOCATE_RID to dreplsrv at %s: %s",
+ server_id_str_buf(*servers, &idbuf),
+ nt_errstr(status));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+
+static const char * const ridalloc_ridset_attrs[] = {
+ "rIDAllocationPool",
+ "rIDPreviousAllocationPool",
+ "rIDNextRID",
+ "rIDUsedPool",
+ NULL
+};
+
+struct ridalloc_ridset_values {
+ uint64_t alloc_pool;
+ uint64_t prev_pool;
+ uint32_t next_rid;
+ uint32_t used_pool;
+};
+
+static void ridalloc_get_ridset_values(struct ldb_message *msg, struct ridalloc_ridset_values *v)
+{
+ v->alloc_pool = ldb_msg_find_attr_as_uint64(msg, "rIDAllocationPool", UINT64_MAX);
+ v->prev_pool = ldb_msg_find_attr_as_uint64(msg, "rIDPreviousAllocationPool", UINT64_MAX);
+ v->next_rid = ldb_msg_find_attr_as_uint(msg, "rIDNextRID", UINT32_MAX);
+ v->used_pool = ldb_msg_find_attr_as_uint(msg, "rIDUsedPool", UINT32_MAX);
+}
+
+static int ridalloc_set_ridset_values(struct ldb_module *module,
+ struct ldb_message *msg,
+ const struct ridalloc_ridset_values *o,
+ const struct ridalloc_ridset_values *n)
+{
+ const uint32_t *o32, *n32;
+ const uint64_t *o64, *n64;
+ int ret;
+
+#define SETUP_PTRS(field, optr, nptr, max) do { \
+ optr = &o->field; \
+ nptr = &n->field; \
+ if (o->field == max) { \
+ optr = NULL; \
+ } \
+ if (n->field == max) { \
+ nptr = NULL; \
+ } \
+ if (o->field == n->field) { \
+ optr = NULL; \
+ nptr = NULL; \
+ } \
+} while(0)
+
+ SETUP_PTRS(alloc_pool, o64, n64, UINT64_MAX);
+ ret = dsdb_msg_constrainted_update_uint64(module, msg,
+ "rIDAllocationPool",
+ o64, n64);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ SETUP_PTRS(prev_pool, o64, n64, UINT64_MAX);
+ ret = dsdb_msg_constrainted_update_uint64(module, msg,
+ "rIDPreviousAllocationPool",
+ o64, n64);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ SETUP_PTRS(next_rid, o32, n32, UINT32_MAX);
+ ret = dsdb_msg_constrainted_update_uint32(module, msg,
+ "rIDNextRID",
+ o32, n32);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ SETUP_PTRS(used_pool, o32, n32, UINT32_MAX);
+ ret = dsdb_msg_constrainted_update_uint32(module, msg,
+ "rIDUsedPool",
+ o32, n32);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+#undef SETUP_PTRS
+
+ return LDB_SUCCESS;
+}
+
+/*
+ allocate a new range of RIDs in the RID Manager object
+ */
+static int ridalloc_rid_manager_allocate(struct ldb_module *module, struct ldb_dn *rid_manager_dn, uint64_t *new_pool,
+ struct ldb_request *parent)
+{
+ int ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ const char *attrs[] = { "rIDAvailablePool", NULL };
+ uint64_t rid_pool, new_rid_pool, dc_pool;
+ uint32_t rid_pool_lo, rid_pool_hi;
+ struct ldb_result *res;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const unsigned alloc_size = 500;
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res, rid_manager_dn,
+ attrs, DSDB_FLAG_NEXT_MODULE, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to find rIDAvailablePool in %s - %s",
+ ldb_dn_get_linearized(rid_manager_dn), ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ rid_pool = ldb_msg_find_attr_as_uint64(res->msgs[0], "rIDAvailablePool", 0);
+ rid_pool_lo = rid_pool & 0xFFFFFFFF;
+ rid_pool_hi = rid_pool >> 32;
+ if (rid_pool_lo >= rid_pool_hi) {
+ ldb_asprintf_errstring(ldb, "Out of RIDs in RID Manager - rIDAvailablePool is %u-%u",
+ rid_pool_lo, rid_pool_hi);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* lower part of new pool is the low part of the rIDAvailablePool */
+ dc_pool = rid_pool_lo;
+
+ /* allocate 500 RIDs to this DC */
+ rid_pool_lo = MIN(rid_pool_hi, rid_pool_lo + alloc_size);
+
+ /* work out upper part of new pool */
+ dc_pool |= (((uint64_t)rid_pool_lo-1)<<32);
+
+ /* and new rIDAvailablePool value */
+ new_rid_pool = rid_pool_lo | (((uint64_t)rid_pool_hi)<<32);
+
+ ret = dsdb_module_constrainted_update_uint64(module, rid_manager_dn, "rIDAvailablePool",
+ &rid_pool, &new_rid_pool, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to update rIDAvailablePool - %s",
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ (*new_pool) = dc_pool;
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+/*
+ create a RID Set object for the specified DC
+ */
+static int ridalloc_create_rid_set_ntds(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_dn *rid_manager_dn,
+ struct ldb_dn *ntds_dn, struct ldb_dn **dn,
+ struct ldb_request *parent)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ struct ldb_dn *server_dn, *machine_dn, *rid_set_dn;
+ int ret;
+ struct ldb_message *msg;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ static const struct ridalloc_ridset_values o = {
+ .alloc_pool = UINT64_MAX,
+ .prev_pool = UINT64_MAX,
+ .next_rid = UINT32_MAX,
+ .used_pool = UINT32_MAX,
+ };
+ struct ridalloc_ridset_values n = {
+ .alloc_pool = 0,
+ .prev_pool = 0,
+ .next_rid = 0,
+ .used_pool = 0,
+ };
+ const char *no_attrs[] = { NULL };
+ struct ldb_result *res;
+
+ /*
+ steps:
+
+ find the machine object for the DC
+ construct the RID Set DN
+ load rIDAvailablePool to find next available set
+ modify RID Manager object to update rIDAvailablePool
+ add the RID Set object
+ link to the RID Set object in machine object
+ */
+
+ server_dn = ldb_dn_get_parent(tmp_ctx, ntds_dn);
+ if (!server_dn) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+
+ ret = dsdb_module_reference_dn(module, tmp_ctx, server_dn, "serverReference", &machine_dn, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to find serverReference in %s - %s",
+ ldb_dn_get_linearized(server_dn), ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ rid_set_dn = ldb_dn_copy(tmp_ctx, machine_dn);
+ if (rid_set_dn == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+
+ if (! ldb_dn_add_child_fmt(rid_set_dn, "CN=RID Set")) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+
+ /* grab a pool from the RID Manager object */
+ ret = ridalloc_rid_manager_allocate(module, rid_manager_dn, &n.alloc_pool, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* create the RID Set object */
+ msg = ldb_msg_new(tmp_ctx);
+ msg->dn = rid_set_dn;
+
+ ret = ldb_msg_add_string(msg, "objectClass", "rIDSet");
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = ridalloc_set_ridset_values(module, msg, &o, &n);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* we need this to go all the way to the top of the module
+ * stack, as we need all the extra attributes added (including
+ * complex ones like ntsecuritydescriptor). We must do this
+ * as system, otherwise a user might end up owning the RID
+ * set, and that would be bad... */
+ ret = dsdb_module_add(module, msg,
+ DSDB_FLAG_TOP_MODULE | DSDB_FLAG_AS_SYSTEM
+ | DSDB_MODIFY_RELAX, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to add RID Set %s - %s",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* add the rIDSetReferences link */
+ msg = ldb_msg_new(tmp_ctx);
+ msg->dn = machine_dn;
+
+ /* we need the extended DN of the RID Set object for
+ * rIDSetReferences */
+ ret = dsdb_module_search_dn(module, msg, &res, rid_set_dn, no_attrs,
+ DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to find extended DN of RID Set %s - %s",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ rid_set_dn = res->msgs[0]->dn;
+
+
+ ret = ldb_msg_add_string(msg, "rIDSetReferences", ldb_dn_get_extended_linearized(msg, rid_set_dn, 1));
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ msg->elements[0].flags = LDB_FLAG_MOD_ADD;
+
+ ret = dsdb_module_modify(module, msg,
+ DSDB_FLAG_NEXT_MODULE|DSDB_FLAG_AS_SYSTEM,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to add rIDSetReferences to %s - %s",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ (*dn) = talloc_steal(mem_ctx, rid_set_dn);
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+
+/*
+ create a RID Set object for this DC
+ */
+int ridalloc_create_own_rid_set(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_dn **dn, struct ldb_request *parent)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ struct ldb_dn *rid_manager_dn, *fsmo_role_dn;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct GUID fsmo_role_guid;
+ const struct GUID *our_ntds_guid;
+ NTSTATUS status;
+
+ /* work out who is the RID Manager */
+ ret = dsdb_module_rid_manager_dn(module, tmp_ctx, &rid_manager_dn, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to find RID Manager object - %s",
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* find the DN of the RID Manager */
+ ret = dsdb_module_reference_dn(module, tmp_ctx, rid_manager_dn, "fSMORoleOwner", &fsmo_role_dn, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to find fSMORoleOwner in RID Manager object - %s",
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ status = dsdb_get_extended_dn_guid(fsmo_role_dn, &fsmo_role_guid, "GUID");
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ /* clear the cache so we don't get an old ntds_guid */
+ if (ldb_set_opaque(ldb, "cache.ntds_guid", NULL) != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ our_ntds_guid = samdb_ntds_objectGUID(ldb_module_get_ctx(module));
+ if (!our_ntds_guid) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ if (!GUID_equal(&fsmo_role_guid, our_ntds_guid)) {
+ ret = ridalloc_poke_rid_manager(module);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "Request for remote creation of "
+ "RID Set for this DC failed: %s",
+ ldb_errstring(ldb));
+ } else {
+ ldb_asprintf_errstring(ldb,
+ "Remote RID Set creation needed");
+ }
+ talloc_free(tmp_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ ret = ridalloc_create_rid_set_ntds(module, mem_ctx, rid_manager_dn, fsmo_role_dn, dn, parent);
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ get a new RID pool for ourselves
+ also returns the first rid for the new pool
+ */
+
+int ridalloc_new_own_pool(struct ldb_module *module, uint64_t *new_pool, struct ldb_request *parent)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct ldb_dn *rid_manager_dn, *fsmo_role_dn;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ bool is_us;
+
+ /* work out who is the RID Manager */
+ ret = dsdb_module_rid_manager_dn(module, tmp_ctx, &rid_manager_dn, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to find RID Manager object - %s",
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* find the DN of the RID Manager */
+ ret = dsdb_module_reference_dn(module, tmp_ctx, rid_manager_dn, "fSMORoleOwner", &fsmo_role_dn, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to find fSMORoleOwner in RID Manager object - %s",
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = samdb_dn_is_our_ntdsa(ldb, fsmo_role_dn, &is_us);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to confirm if our ntdsDsa is %s: %s",
+ ldb_dn_get_linearized(fsmo_role_dn), ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (!is_us) {
+ ret = ridalloc_poke_rid_manager(module);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Request for remote refresh of RID Set allocation failed: %s",
+ ldb_errstring(ldb));
+ } else {
+ ldb_asprintf_errstring(ldb, "Remote RID Set refresh needed");
+ }
+ talloc_free(tmp_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* grab a pool from the RID Manager object */
+ ret = ridalloc_rid_manager_allocate(module, rid_manager_dn, new_pool, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+
+/* allocate a RID using our RID Set
+ If we run out of RIDs then allocate a new pool
+ either locally or by contacting the RID Manager
+*/
+int ridalloc_allocate_rid(struct ldb_module *module, uint32_t *rid, struct ldb_request *parent)
+{
+ struct ldb_context *ldb;
+ int ret;
+ struct ldb_dn *rid_set_dn;
+ struct ldb_result *res;
+ struct ldb_message *msg;
+ struct ridalloc_ridset_values oridset;
+ struct ridalloc_ridset_values nridset;
+ uint32_t prev_pool_lo, prev_pool_hi;
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+
+ (*rid) = 0;
+ ldb = ldb_module_get_ctx(module);
+
+ ret = samdb_rid_set_dn(ldb, tmp_ctx, &rid_set_dn);
+ if (ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
+ ret = ridalloc_create_own_rid_set(module, tmp_ctx, &rid_set_dn, parent);
+ }
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, __location__ ": No RID Set DN - %s",
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res, rid_set_dn,
+ ridalloc_ridset_attrs, DSDB_FLAG_NEXT_MODULE, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, __location__ ": No RID Set %s",
+ ldb_dn_get_linearized(rid_set_dn));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ridalloc_get_ridset_values(res->msgs[0], &oridset);
+ if (oridset.alloc_pool == UINT64_MAX) {
+ ldb_asprintf_errstring(ldb, __location__ ": Bad RID Set %s",
+ ldb_dn_get_linearized(rid_set_dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ nridset = oridset;
+
+ /*
+ * If we never used a pool, setup out first pool
+ */
+ if (nridset.prev_pool == UINT64_MAX ||
+ nridset.next_rid == UINT32_MAX) {
+ nridset.prev_pool = nridset.alloc_pool;
+ nridset.next_rid = nridset.prev_pool & 0xFFFFFFFF;
+ } else {
+ nridset.next_rid += 1;
+ }
+
+ /*
+ * Now check if our current pool is still usable
+ */
+ prev_pool_lo = nridset.prev_pool & 0xFFFFFFFF;
+ prev_pool_hi = nridset.prev_pool >> 32;
+ if (nridset.next_rid > prev_pool_hi) {
+ /*
+ * We need a new pool, check if we already have a new one
+ * Otherwise we need to get a new pool.
+ */
+ if (nridset.alloc_pool == nridset.prev_pool) {
+ /*
+ * if we are the RID Manager,
+ * we can get a new pool localy.
+ * Otherwise we fail the operation and
+ * ask async for a new pool.
+ */
+ ret = ridalloc_new_own_pool(module, &nridset.alloc_pool, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "NO RID values available: %s",
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ /*
+ * increment the rIDUsedPool attribute
+ *
+ * Note: w2k8r2 doesn't update this attribute,
+ * at least if it's itself the rid master.
+ */
+ nridset.used_pool += 1;
+
+ /* now use the new pool */
+ nridset.prev_pool = nridset.alloc_pool;
+ prev_pool_lo = nridset.prev_pool & 0xFFFFFFFF;
+ prev_pool_hi = nridset.prev_pool >> 32;
+ nridset.next_rid = prev_pool_lo;
+ }
+
+ if (nridset.next_rid < prev_pool_lo || nridset.next_rid > prev_pool_hi) {
+ ldb_asprintf_errstring(ldb, __location__ ": Bad rid chosen %u from range %u-%u",
+ (unsigned)nridset.next_rid,
+ (unsigned)prev_pool_lo,
+ (unsigned)prev_pool_hi);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /*
+ * if we are half-exhausted then try to get a new pool.
+ */
+ if (nridset.next_rid > (prev_pool_hi + prev_pool_lo)/2 &&
+ nridset.alloc_pool == nridset.prev_pool) {
+ /*
+ * if we are the RID Manager,
+ * we can get a new pool localy.
+ * Otherwise we fail the operation and
+ * ask async for a new pool.
+ */
+ ret = ridalloc_new_own_pool(module, &nridset.alloc_pool, parent);
+ if (ret == LDB_ERR_UNWILLING_TO_PERFORM) {
+ ldb_reset_err_string(ldb);
+ ret = LDB_SUCCESS;
+ }
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ /*
+ * update the values
+ */
+ msg = ldb_msg_new(tmp_ctx);
+ if (msg == NULL) {
+ return ldb_module_oom(module);
+ }
+ msg->dn = rid_set_dn;
+
+ ret = ridalloc_set_ridset_values(module, msg,
+ &oridset, &nridset);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE|DSDB_FLAG_AS_SYSTEM, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ talloc_free(tmp_ctx);
+ *rid = nridset.next_rid;
+ return LDB_SUCCESS;
+}
+
+
+/*
+ called by DSDB_EXTENDED_ALLOCATE_RID_POOL extended operation in samldb
+
+ This is for the DRS server to allocate a RID Pool for another server.
+
+ Called by another server over DRS (which calls this extended
+ operation), it runs on the RID Manager only.
+ */
+int ridalloc_allocate_rid_pool_fsmo(struct ldb_module *module, struct dsdb_fsmo_extended_op *exop,
+ struct ldb_request *parent)
+{
+ struct ldb_dn *ntds_dn, *server_dn, *machine_dn, *rid_set_dn;
+ struct ldb_dn *rid_manager_dn;
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_result *res;
+ struct ldb_message *msg;
+ struct ridalloc_ridset_values oridset, nridset;
+
+ ret = dsdb_module_dn_by_guid(module, tmp_ctx, &exop->destination_dsa_guid, &ntds_dn, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, __location__ ": Unable to find NTDS object for guid %s - %s\n",
+ GUID_string(tmp_ctx, &exop->destination_dsa_guid), ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ server_dn = ldb_dn_get_parent(tmp_ctx, ntds_dn);
+ if (!server_dn) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+
+ ret = dsdb_module_reference_dn(module, tmp_ctx, server_dn, "serverReference", &machine_dn, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, __location__ ": Failed to find serverReference in %s - %s",
+ ldb_dn_get_linearized(server_dn), ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_module_rid_manager_dn(module, tmp_ctx, &rid_manager_dn, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, __location__ ": Failed to find RID Manager object - %s",
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_module_reference_dn(module, tmp_ctx, machine_dn, "rIDSetReferences", &rid_set_dn, parent);
+ if (ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
+ ret = ridalloc_create_rid_set_ntds(module, tmp_ctx, rid_manager_dn, ntds_dn, &rid_set_dn, parent);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to find rIDSetReferences in %s - %s",
+ ldb_dn_get_linearized(machine_dn), ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res, rid_set_dn,
+ ridalloc_ridset_attrs, DSDB_FLAG_NEXT_MODULE, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, __location__ ": No RID Set %s",
+ ldb_dn_get_linearized(rid_set_dn));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ridalloc_get_ridset_values(res->msgs[0], &oridset);
+ if (oridset.alloc_pool == UINT64_MAX) {
+ ldb_asprintf_errstring(ldb, __location__ ": Bad RID Set %s",
+ ldb_dn_get_linearized(rid_set_dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ nridset = oridset;
+
+ if (exop->fsmo_info != 0) {
+
+ if (nridset.alloc_pool != exop->fsmo_info) {
+ /* it has already been updated */
+ DEBUG(2,(__location__ ": rIDAllocationPool fsmo_info mismatch - already changed (0x%llx 0x%llx)\n",
+ (unsigned long long)exop->fsmo_info,
+ (unsigned long long)nridset.alloc_pool));
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+ }
+
+ /* grab a pool from the RID Manager object */
+ ret = ridalloc_rid_manager_allocate(module, rid_manager_dn, &nridset.alloc_pool, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /*
+ * update the values
+ */
+ msg = ldb_msg_new(tmp_ctx);
+ if (msg == NULL) {
+ return ldb_module_oom(module);
+ }
+ msg->dn = rid_set_dn;
+
+ ret = ridalloc_set_ridset_values(module, msg,
+ &oridset, &nridset);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE|DSDB_FLAG_AS_SYSTEM, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to modify RID Set object %s - %s",
+ ldb_dn_get_linearized(rid_set_dn), ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
diff --git a/source4/dsdb/samdb/ldb_modules/rootdse.c b/source4/dsdb/samdb/ldb_modules/rootdse.c
new file mode 100644
index 0000000..865a432
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/rootdse.c
@@ -0,0 +1,1796 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ rootDSE ldb module
+
+ Copyright (C) Andrew Tridgell 2005
+ Copyright (C) Simo Sorce 2005-2008
+ Copyright (C) Matthieu Patou <mat@matws.net> 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include <ldb.h>
+#include <ldb_module.h>
+#include "system/time.h"
+#include "dsdb/samdb/samdb.h"
+#include "version.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "libcli/security/security.h"
+#include "librpc/ndr/libndr.h"
+#include "auth/auth.h"
+#include "param/param.h"
+#include "lib/messaging/irpc.h"
+#include "librpc/gen_ndr/ndr_irpc_c.h"
+#include "lib/tsocket/tsocket.h"
+#include "cldap_server/cldap_server.h"
+#include "lib/events/events.h"
+
+#undef strcasecmp
+
+struct rootdse_private_data {
+ unsigned int num_controls;
+ char **controls;
+ unsigned int num_partitions;
+ struct ldb_dn **partitions;
+ bool block_anonymous;
+ struct tevent_context *saved_ev;
+ struct tevent_context *private_ev;
+};
+
+struct rootdse_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+ struct ldb_val netlogon;
+};
+
+/*
+ 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);
+}
+
+
+/*
+ expand a DN attribute to include extended DN information if requested
+ */
+static int expand_dn_in_message(struct ldb_module *module, struct ldb_message *msg,
+ const char *attrname, struct ldb_control *edn_control,
+ struct ldb_request *req)
+{
+ struct ldb_dn *dn, *dn2;
+ struct ldb_val *v;
+ int ret;
+ struct ldb_request *req2;
+ char *dn_string;
+ const char *no_attrs[] = { NULL };
+ struct ldb_result *res;
+ struct ldb_extended_dn_control *edn;
+ TALLOC_CTX *tmp_ctx = talloc_new(req);
+ struct ldb_context *ldb;
+ int edn_type = 0;
+ unsigned int i;
+ struct ldb_message_element *el;
+
+ ldb = ldb_module_get_ctx(module);
+
+ edn = talloc_get_type(edn_control->data, struct ldb_extended_dn_control);
+ if (edn) {
+ edn_type = edn->type;
+ }
+
+ el = ldb_msg_find_element(msg, attrname);
+ if (!el || el->num_values == 0) {
+ return LDB_SUCCESS;
+ }
+
+ for (i = 0; i < el->num_values; i++) {
+ v = &el->values[i];
+ if (v == NULL) {
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ dn_string = talloc_strndup(tmp_ctx, (const char *)v->data, v->length);
+ if (dn_string == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (res == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ dn = ldb_dn_new(tmp_ctx, ldb, dn_string);
+ if (dn == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ ret = ldb_build_search_req(&req2, ldb, tmp_ctx,
+ dn,
+ LDB_SCOPE_BASE,
+ NULL,
+ no_attrs,
+ NULL,
+ res, ldb_search_default_callback,
+ req);
+ LDB_REQ_SET_LOCATION(req2);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_request_add_controls(req2, DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_EXTENDED_DN);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ldb_error(ldb, ret, "Failed to add control");
+ }
+
+ ret = ldb_next_request(module, req2);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req2->handle, LDB_WAIT_ALL);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (!res || res->count != 1) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ dn2 = res->msgs[0]->dn;
+
+ v->data = (uint8_t *)ldb_dn_get_extended_linearized(msg->elements, dn2, edn_type);
+ if (v->data == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+ v->length = strlen((char *)v->data);
+ }
+
+ talloc_free(tmp_ctx);
+
+ return LDB_SUCCESS;
+}
+
+/*
+ see if we are master for a FSMO role
+ */
+static int dsdb_module_we_are_master(struct ldb_module *module, struct ldb_dn *dn, bool *master,
+ struct ldb_request *parent)
+{
+ const char *attrs[] = { "fSMORoleOwner", NULL };
+ TALLOC_CTX *tmp_ctx = talloc_new(parent);
+ struct ldb_result *res;
+ int ret;
+ struct ldb_dn *owner_dn;
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res,
+ dn, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_EXTENDED_DN,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ owner_dn = ldb_msg_find_attr_as_dn(ldb_module_get_ctx(module),
+ tmp_ctx, res->msgs[0], "fSMORoleOwner");
+ if (!owner_dn) {
+ *master = false;
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+
+ ret = samdb_dn_is_our_ntdsa(ldb_module_get_ctx(module), dn, master);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module), "Failed to confirm if our ntdsDsa is %s: %s",
+ ldb_dn_get_linearized(owner_dn), ldb_errstring(ldb_module_get_ctx(module)));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+/*
+ add dynamically generated attributes to rootDSE result
+*/
+static int rootdse_add_dynamic(struct rootdse_context *ac, struct ldb_message *msg)
+{
+ struct ldb_context *ldb;
+ struct rootdse_private_data *priv = talloc_get_type(ldb_module_get_private(ac->module), struct rootdse_private_data);
+ const char * const *attrs = ac->req->op.search.attrs;
+ const char **server_sasl = NULL;
+ const struct dsdb_schema *schema;
+ int *val;
+ struct ldb_control *edn_control;
+ const char *dn_attrs[] = {
+ "configurationNamingContext",
+ "defaultNamingContext",
+ "rootDomainNamingContext",
+ "schemaNamingContext",
+ "serverName",
+ "validFSMOs",
+ "namingContexts",
+ NULL
+ };
+ const char *guid_attrs[] = {
+ "dsServiceName",
+ NULL
+ };
+ unsigned int i;
+
+ ldb = ldb_module_get_ctx(ac->module);
+ schema = dsdb_get_schema(ldb, NULL);
+
+ msg->dn = ldb_dn_new(msg, ldb, NULL);
+
+ /* don't return the distinguishedName, 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, "serverName")) {
+ if (ldb_msg_add_linearized_dn(msg, "serverName",
+ samdb_server_dn(ldb, msg)) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ if (do_attribute(attrs, "dnsHostName")) {
+ struct ldb_result *res;
+ int ret;
+ const char *dns_attrs[] = { "dNSHostName", NULL };
+ ret = dsdb_module_search_dn(ac->module, msg, &res, samdb_server_dn(ldb, msg),
+ dns_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM,
+ ac->req);
+ if (ret == LDB_SUCCESS) {
+ const char *hostname = ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", NULL);
+ if (hostname != NULL) {
+ if (ldb_msg_add_string(msg, "dnsHostName", hostname)) {
+ goto failed;
+ }
+ }
+ }
+ }
+
+ if (do_attribute(attrs, "ldapServiceName")) {
+ struct loadparm_context *lp_ctx
+ = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+ char *ldap_service_name, *hostname;
+
+ hostname = strlower_talloc(msg, lpcfg_netbios_name(lp_ctx));
+ if (hostname == NULL) {
+ goto failed;
+ }
+
+ ldap_service_name = talloc_asprintf(msg, "%s:%s$@%s",
+ samdb_forest_name(ldb, msg),
+ hostname, lpcfg_realm(lp_ctx));
+ if (ldap_service_name == NULL) {
+ goto failed;
+ }
+
+ if (ldb_msg_add_string(msg, "ldapServiceName",
+ ldap_service_name) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ if (do_attribute(attrs, "currentTime")) {
+ char *timestr = ldb_timestring(msg, time(NULL));
+
+ if (timestr == NULL) {
+ goto failed;
+ }
+
+ if (ldb_msg_add_steal_string(
+ msg, "currentTime", timestr) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ if (priv && do_attribute(attrs, "supportedControl")) {
+ 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) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+ }
+
+ if (priv && do_attribute(attrs, "namingContexts")) {
+ 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)) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+ }
+
+ server_sasl = talloc_get_type(ldb_get_opaque(ldb, "supportedSASLMechanisms"),
+ const char *);
+ if (server_sasl && do_attribute(attrs, "supportedSASLMechanisms")) {
+ 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) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+ }
+
+ if (do_attribute(attrs, "highestCommittedUSN")) {
+ uint64_t seq_num;
+ int ret = ldb_sequence_number(ldb, LDB_SEQ_HIGHEST_SEQ, &seq_num);
+ if (ret == LDB_SUCCESS) {
+ if (samdb_msg_add_uint64(ldb, msg, msg,
+ "highestCommittedUSN",
+ seq_num) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+ }
+
+ if (schema && do_attribute_explicit(attrs, "dsSchemaAttrCount")) {
+ struct dsdb_attribute *cur;
+ unsigned int n = 0;
+
+ for (cur = schema->attributes; cur; cur = cur->next) {
+ n++;
+ }
+
+ if (samdb_msg_add_uint(ldb, msg, msg, "dsSchemaAttrCount",
+ n) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ if (schema && do_attribute_explicit(attrs, "dsSchemaClassCount")) {
+ struct dsdb_class *cur;
+ unsigned int n = 0;
+
+ for (cur = schema->classes; cur; cur = cur->next) {
+ n++;
+ }
+
+ if (samdb_msg_add_uint(ldb, msg, msg, "dsSchemaClassCount",
+ n) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ if (schema && do_attribute_explicit(attrs, "dsSchemaPrefixCount")) {
+ if (samdb_msg_add_uint(ldb, msg, msg, "dsSchemaPrefixCount",
+ schema->prefixmap->length) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ if (do_attribute_explicit(attrs, "validFSMOs")) {
+ struct ldb_dn *dns[3];
+
+ dns[0] = ldb_get_schema_basedn(ldb);
+ dns[1] = samdb_partitions_dn(ldb, msg);
+ dns[2] = ldb_get_default_basedn(ldb);
+
+ for (i=0; i<3; i++) {
+ bool master;
+ int ret = dsdb_module_we_are_master(ac->module, dns[i], &master, ac->req);
+ if (ret != LDB_SUCCESS) {
+ goto failed;
+ }
+ if (master && ldb_msg_add_fmt(msg, "validFSMOs", "%s",
+ ldb_dn_get_linearized(dns[i])) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+ }
+
+ if (do_attribute_explicit(attrs, "vendorVersion")) {
+ if (ldb_msg_add_fmt(msg, "vendorVersion",
+ "%s", SAMBA_VERSION_STRING) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ if (do_attribute(attrs, "domainFunctionality")) {
+ if (samdb_msg_add_int(ldb, msg, msg, "domainFunctionality",
+ dsdb_functional_level(ldb)) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ if (do_attribute(attrs, "forestFunctionality")) {
+ if (samdb_msg_add_int(ldb, msg, msg, "forestFunctionality",
+ dsdb_forest_functional_level(ldb)) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ if (do_attribute(attrs, "domainControllerFunctionality")
+ && (val = talloc_get_type(ldb_get_opaque(ldb, "domainControllerFunctionality"), int))) {
+ if (samdb_msg_add_int(ldb, msg, msg,
+ "domainControllerFunctionality",
+ *val) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ if (do_attribute(attrs, "isGlobalCatalogReady")) {
+ /* MS-ADTS 3.1.1.3.2.10
+ Note, we should only return true here is we have
+ completed at least one synchronisation. As both
+ provision and vampire do a full sync, this means we
+ can return true is the gc bit is set in the NTDSDSA
+ options */
+ if (ldb_msg_add_fmt(msg, "isGlobalCatalogReady",
+ "%s", samdb_is_gc(ldb)?"TRUE":"FALSE") != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ if (do_attribute_explicit(attrs, "tokenGroups")) {
+ /* Obtain the user's session_info */
+ struct auth_session_info *session_info
+ = (struct auth_session_info *)ldb_get_opaque(
+ ldb,
+ DSDB_SESSION_INFO);
+ if (session_info && session_info->security_token) {
+ /* The list of groups this user is in */
+ for (i = 0; i < session_info->security_token->num_sids; i++) {
+ if (samdb_msg_add_dom_sid(ldb, msg, msg,
+ "tokenGroups",
+ &session_info->security_token->sids[i]) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+ }
+ }
+
+ if (ac->netlogon.length > 0) {
+ if (ldb_msg_add_steal_value(msg, "netlogon", &ac->netlogon) != LDB_SUCCESS) {
+ goto failed;
+ }
+ }
+
+ /* TODO: lots more dynamic attributes should be added here */
+
+ edn_control = ldb_request_get_control(ac->req, LDB_CONTROL_EXTENDED_DN_OID);
+
+ /* convert any GUID attributes to be in the right form */
+ for (i=0; guid_attrs[i]; i++) {
+ struct ldb_result *res;
+ struct ldb_message_element *el;
+ struct ldb_dn *attr_dn;
+ const char *no_attrs[] = { NULL };
+ int ret;
+
+ if (!do_attribute(attrs, guid_attrs[i])) continue;
+
+ attr_dn = ldb_msg_find_attr_as_dn(ldb, ac->req, msg, guid_attrs[i]);
+ if (attr_dn == NULL) {
+ continue;
+ }
+
+ ret = dsdb_module_search_dn(ac->module, ac->req, &res,
+ attr_dn, no_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SHOW_EXTENDED_DN,
+ ac->req);
+ if (ret != LDB_SUCCESS) {
+ DBG_WARNING("Failed to convert GUID into full DN in rootDSE for %s: %s: %s\n",
+ guid_attrs[i],
+ ldb_dn_get_extended_linearized(ac, attr_dn, 1),
+ ldb_errstring(ldb));
+ /*
+ * Provide a meaninful error string but not
+ * confidential DB contents possibly in the
+ * original string
+ */
+ ldb_asprintf_errstring(ldb,
+ "Failed to find full DN for %s: %s",
+ guid_attrs[i],
+ ldb_dn_get_extended_linearized(ac, attr_dn, 1));
+ /* Overstamp the error code, it would confuse the caller */
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ el = ldb_msg_find_element(msg, guid_attrs[i]);
+ if (el == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ talloc_steal(el->values, res->msgs[0]->dn);
+ if (edn_control) {
+ struct ldb_extended_dn_control *edn;
+ int edn_type = 0;
+ edn = talloc_get_type(edn_control->data, struct ldb_extended_dn_control);
+ if (edn != NULL) {
+ edn_type = edn->type;
+ }
+ el->values[0].data = (uint8_t *)ldb_dn_get_extended_linearized(el->values,
+ res->msgs[0]->dn,
+ edn_type);
+ } else {
+ el->values[0].data = (uint8_t *)talloc_strdup(el->values,
+ ldb_dn_get_linearized(res->msgs[0]->dn));
+ }
+ if (el->values[0].data == NULL) {
+ return ldb_oom(ldb);
+ }
+ el->values[0].length = strlen((const char *)el->values[0].data);
+ }
+
+ /* if the client sent us the EXTENDED_DN control then we need
+ to expand the DNs to have GUID and SID. W2K8 join relies on
+ this */
+ if (edn_control) {
+ int ret;
+ for (i=0; dn_attrs[i]; i++) {
+ if (!do_attribute(attrs, dn_attrs[i])) continue;
+ ret = expand_dn_in_message(ac->module, msg, dn_attrs[i],
+ edn_control, ac->req);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,(__location__ ": Failed to expand DN in rootDSE for %s\n",
+ dn_attrs[i]));
+ goto failed;
+ }
+ }
+ }
+
+ return LDB_SUCCESS;
+
+failed:
+ return ldb_operr(ldb);
+}
+
+/*
+ handle search requests
+*/
+
+static struct rootdse_context *rootdse_init_context(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct rootdse_context *ac;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = talloc_zero(req, struct rootdse_context);
+ if (ac == NULL) {
+ ldb_set_errstring(ldb, "Out of Memory");
+ return NULL;
+ }
+
+ ac->module = module;
+ ac->req = req;
+
+ return ac;
+}
+
+static int rootdse_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct rootdse_context *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct rootdse_context);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ /* for each record returned post-process to add any dynamic
+ attributes that have been asked for */
+ ret = rootdse_add_dynamic(ac, ares->message);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ return ldb_module_send_entry(ac->req, ares->message, ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+ /* should we allow the backend to return referrals in this case
+ * ?? */
+ break;
+
+ case LDB_REPLY_DONE:
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+/*
+ filter from controls from clients in several ways
+
+ 1) mark our registered controls as non-critical in the request
+
+ This is needed as clients may mark controls as critical even if
+ they are not needed at all in a request. For example, the centrify
+ client sets the SD_FLAGS control as critical on ldap modify
+ requests which are setting the dNSHostName attribute on the
+ machine account. That request doesn't need SD_FLAGS at all, but
+ centrify adds it on all ldap requests.
+
+ 2) if this request is untrusted then remove any non-registered
+ controls that are non-critical
+
+ This is used on ldap:// connections to prevent remote users from
+ setting an internal control that may be dangerous
+
+ 3) if this request is untrusted then fail any request that includes
+ a critical non-registered control
+ */
+static int rootdse_filter_controls(struct ldb_module *module, struct ldb_request *req)
+{
+ unsigned int i, j;
+ struct rootdse_private_data *priv = talloc_get_type(ldb_module_get_private(module), struct rootdse_private_data);
+ bool is_untrusted;
+
+ if (!req->controls) {
+ return LDB_SUCCESS;
+ }
+
+ is_untrusted = ldb_req_is_untrusted(req);
+
+ for (i=0; req->controls[i]; i++) {
+ bool is_registered = false;
+ bool is_critical = (req->controls[i]->critical != 0);
+
+ if (req->controls[i]->oid == NULL) {
+ continue;
+ }
+
+ if (is_untrusted || is_critical) {
+ for (j=0; j<priv->num_controls; j++) {
+ if (strcasecmp(priv->controls[j], req->controls[i]->oid) == 0) {
+ is_registered = true;
+ break;
+ }
+ }
+ }
+
+ if (is_untrusted && !is_registered) {
+ if (!is_critical) {
+ /* remove it by marking the oid NULL */
+ req->controls[i]->oid = NULL;
+ req->controls[i]->data = NULL;
+ req->controls[i]->critical = 0;
+ continue;
+ }
+ /* its a critical unregistered control - give
+ an error */
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "Attempt to use critical non-registered control '%s'",
+ req->controls[i]->oid);
+ return LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION;
+ }
+
+ if (!is_critical) {
+ continue;
+ }
+
+ /*
+ * If the control is DIRSYNC, SORT or VLV then we keep the
+ * critical flag as the modules will need to act upon it.
+ *
+ * These modules have to unset the critical flag after the
+ * request has been seen by the correct module.
+ */
+ if (is_registered &&
+ strcmp(req->controls[i]->oid,
+ LDB_CONTROL_DIRSYNC_OID) != 0 &&
+ strcmp(req->controls[i]->oid,
+ LDB_CONTROL_VLV_REQ_OID) != 0 &&
+ strcmp(req->controls[i]->oid,
+ LDB_CONTROL_SERVER_SORT_OID) != 0) {
+ req->controls[i]->critical = 0;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+/* Ensure that anonymous users are not allowed to make anything other than rootDSE search operations */
+
+static int rootdse_filter_operations(struct ldb_module *module, struct ldb_request *req)
+{
+ struct auth_session_info *session_info;
+ struct rootdse_private_data *priv = talloc_get_type(ldb_module_get_private(module), struct rootdse_private_data);
+ bool is_untrusted = ldb_req_is_untrusted(req);
+ bool is_anonymous = true;
+ if (is_untrusted == false) {
+ return LDB_SUCCESS;
+ }
+
+ session_info = (struct auth_session_info *)ldb_get_opaque(
+ ldb_module_get_ctx(module),
+ DSDB_SESSION_INFO);
+ if (session_info) {
+ is_anonymous = security_token_is_anonymous(session_info->security_token);
+ }
+
+ if (is_anonymous == false || (priv && priv->block_anonymous == false)) {
+ return LDB_SUCCESS;
+ }
+
+ if (req->operation == LDB_SEARCH) {
+ if (req->op.search.scope == LDB_SCOPE_BASE && ldb_dn_is_null(req->op.search.base)) {
+ return LDB_SUCCESS;
+ }
+ }
+ ldb_set_errstring(ldb_module_get_ctx(module), "Operation unavailable without authentication");
+ return LDB_ERR_OPERATIONS_ERROR;
+}
+
+static int rootdse_handle_netlogon(struct rootdse_context *ac)
+{
+ struct ldb_context *ldb;
+ struct ldb_parse_tree *tree;
+ struct loadparm_context *lp_ctx;
+ struct tsocket_address *src_addr;
+ TALLOC_CTX *tmp_ctx = talloc_new(ac->req);
+ const char *domain, *host, *user, *domain_guid;
+ char *src_addr_s = NULL;
+ struct dom_sid *domain_sid;
+ int acct_control = -1;
+ int version = -1;
+ NTSTATUS status;
+ struct netlogon_samlogon_response netlogon;
+ int ret = LDB_ERR_OPERATIONS_ERROR;
+
+ ldb = ldb_module_get_ctx(ac->module);
+ tree = ac->req->op.search.tree;
+ lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+ src_addr = talloc_get_type(ldb_get_opaque(ldb, "remoteAddress"),
+ struct tsocket_address);
+ if (src_addr) {
+ src_addr_s = tsocket_address_inet_addr_string(src_addr,
+ tmp_ctx);
+ }
+
+ status = parse_netlogon_request(tree, lp_ctx, tmp_ctx,
+ &domain, &host, &user, &domain_guid,
+ &domain_sid, &acct_control, &version);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto failed;
+ }
+
+ status = fill_netlogon_samlogon_response(ldb, tmp_ctx,
+ domain, NULL, domain_sid,
+ domain_guid,
+ user, acct_control,
+ src_addr_s,
+ version, lp_ctx,
+ &netlogon, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto failed;
+ }
+
+ status = push_netlogon_samlogon_response(&ac->netlogon, ac, &netlogon);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto failed;
+ }
+
+ ret = LDB_SUCCESS;
+failed:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static int rootdse_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct rootdse_context *ac;
+ struct ldb_request *down_req;
+ int ret;
+
+ ret = rootdse_filter_operations(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = rootdse_filter_controls(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ /* 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 = rootdse_init_context(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ if (do_attribute_explicit(req->op.search.attrs, "netlogon")) {
+ ret = rootdse_handle_netlogon(ac);
+ /* We have to return an empty result, so don't forward `ret' */
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, LDB_SUCCESS);
+ }
+ }
+
+ /* in our db we store the rootDSE with a DN of @ROOTDSE */
+ ret = ldb_build_search_req(&down_req, ldb, ac,
+ ldb_dn_new(ac, ldb, "@ROOTDSE"),
+ LDB_SCOPE_BASE,
+ NULL,
+ req->op.search.attrs,
+ NULL,/* for now skip the controls from the client */
+ ac, rootdse_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, down_req);
+}
+
+static struct rootdse_private_data *rootdse_get_private_data(struct ldb_module *module)
+{
+ void *priv = ldb_module_get_private(module);
+ struct rootdse_private_data *data = NULL;
+ struct ldb_context *ldb
+ = ldb_module_get_ctx(module);
+
+ if (priv != NULL) {
+ data = talloc_get_type_abort(priv,
+ struct rootdse_private_data);
+ }
+
+ if (data != NULL) {
+ return data;
+ }
+
+ data = talloc_zero(module, struct rootdse_private_data);
+ if (data == NULL) {
+ return NULL;
+ }
+
+ data->num_controls = 0;
+ data->controls = NULL;
+ data->num_partitions = 0;
+ data->partitions = NULL;
+ data->block_anonymous = true;
+
+ ldb_module_set_private(module, data);
+
+ ldb_set_default_dns(ldb);
+
+ return data;
+}
+
+
+static int rootdse_register_control(struct ldb_module *module, struct ldb_request *req)
+{
+ struct rootdse_private_data *priv =
+ rootdse_get_private_data(module);
+ char **list;
+
+ if (priv == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ list = talloc_realloc(priv, priv->controls, char *, priv->num_controls + 1);
+ if (!list) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ list[priv->num_controls] = talloc_strdup(list, req->op.reg_control.oid);
+ if (!list[priv->num_controls]) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ priv->num_controls += 1;
+ priv->controls = list;
+
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
+}
+
+static int rootdse_register_partition(struct ldb_module *module, struct ldb_request *req)
+{
+ struct rootdse_private_data *priv =
+ rootdse_get_private_data(module);
+ struct ldb_dn **list;
+
+ if (priv == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ list = talloc_realloc(priv, priv->partitions, struct ldb_dn *, priv->num_partitions + 1);
+ if (!list) {
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ list[priv->num_partitions] = ldb_dn_copy(list, req->op.reg_partition.dn);
+ if (!list[priv->num_partitions]) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ priv->num_partitions += 1;
+ priv->partitions = list;
+
+ return ldb_module_done(req, NULL, NULL, 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)
+{
+ int ret;
+ struct ldb_result *res;
+ const char *attrs[] = { "msDS-Behavior-Version", NULL };
+ const char *ds_attrs[] = { "dsServiceName", NULL };
+ TALLOC_CTX *mem_ctx;
+
+ struct ldb_context *ldb
+ = ldb_module_get_ctx(module);
+
+ struct rootdse_private_data *data
+ = rootdse_get_private_data(module);
+
+ if (data == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ ret = ldb_next_init(module);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ mem_ctx = talloc_new(data);
+ if (!mem_ctx) {
+ return ldb_oom(ldb);
+ }
+
+ /* Now that the partitions are set up, do a search for:
+ - domainControllerFunctionality
+ - domainFunctionality
+ - forestFunctionality
+
+ Then stuff these values into an opaque
+ */
+ ret = dsdb_module_search(module, mem_ctx, &res,
+ ldb_get_default_basedn(ldb),
+ LDB_SCOPE_BASE, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM,
+ NULL, NULL);
+ if (ret == LDB_SUCCESS && res->count == 1) {
+ int domain_behaviour_version
+ = ldb_msg_find_attr_as_int(res->msgs[0],
+ "msDS-Behavior-Version", -1);
+ if (domain_behaviour_version != -1) {
+ int *val = talloc(ldb, int);
+ if (!val) {
+ talloc_free(mem_ctx);
+ return ldb_oom(ldb);
+ }
+ *val = domain_behaviour_version;
+ ret = ldb_set_opaque(ldb, "domainFunctionality", val);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+ }
+
+ ret = dsdb_module_search(module, mem_ctx, &res,
+ samdb_partitions_dn(ldb, mem_ctx),
+ LDB_SCOPE_BASE, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM,
+ NULL, NULL);
+ if (ret == LDB_SUCCESS && res->count == 1) {
+ int forest_behaviour_version
+ = ldb_msg_find_attr_as_int(res->msgs[0],
+ "msDS-Behavior-Version", -1);
+ if (forest_behaviour_version != -1) {
+ int *val = talloc(ldb, int);
+ if (!val) {
+ talloc_free(mem_ctx);
+ return ldb_oom(ldb);
+ }
+ *val = forest_behaviour_version;
+ ret = ldb_set_opaque(ldb, "forestFunctionality", val);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+ }
+
+ /* For now, our own server's location in the DB is recorded in
+ * the @ROOTDSE record */
+ ret = dsdb_module_search(module, mem_ctx, &res,
+ ldb_dn_new(mem_ctx, ldb, "@ROOTDSE"),
+ LDB_SCOPE_BASE, ds_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM,
+ NULL, NULL);
+ if (ret == LDB_SUCCESS && res->count == 1) {
+ struct ldb_dn *ds_dn
+ = ldb_msg_find_attr_as_dn(ldb, mem_ctx, res->msgs[0],
+ "dsServiceName");
+ if (ds_dn) {
+ ret = dsdb_module_search(module, mem_ctx, &res, ds_dn,
+ LDB_SCOPE_BASE, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM,
+ NULL, NULL);
+ if (ret == LDB_SUCCESS && res->count == 1) {
+ int domain_controller_behaviour_version
+ = ldb_msg_find_attr_as_int(res->msgs[0],
+ "msDS-Behavior-Version", -1);
+ if (domain_controller_behaviour_version != -1) {
+ int *val = talloc(ldb, int);
+ if (!val) {
+ talloc_free(mem_ctx);
+ return ldb_oom(ldb);
+ }
+ *val = domain_controller_behaviour_version;
+ ret = ldb_set_opaque(ldb,
+ "domainControllerFunctionality", val);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(mem_ctx);
+ return ret;
+ }
+ }
+ }
+ }
+ }
+
+ data->block_anonymous = dsdb_block_anonymous_ops(module, NULL);
+
+ talloc_free(mem_ctx);
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * This function gets the string SCOPE_DN:OPTIONAL_FEATURE_GUID and parse it
+ * to a DN and a GUID object
+ */
+static int get_optional_feature_dn_guid(struct ldb_request *req, struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn **op_feature_scope_dn,
+ struct GUID *op_feature_guid)
+{
+ const struct ldb_message *msg = req->op.mod.message;
+ const char *ldb_val_str;
+ char *dn, *guid;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NTSTATUS status;
+
+ ldb_val_str = ldb_msg_find_attr_as_string(msg, "enableOptionalFeature", NULL);
+ if (!ldb_val_str) {
+ ldb_set_errstring(ldb,
+ "rootdse: unable to find 'enableOptionalFeature'!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ guid = strchr(ldb_val_str, ':');
+ if (!guid) {
+ ldb_set_errstring(ldb,
+ "rootdse: unable to find GUID in 'enableOptionalFeature'!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ status = GUID_from_string(guid+1, op_feature_guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_set_errstring(ldb,
+ "rootdse: bad GUID in 'enableOptionalFeature'!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ dn = talloc_strndup(tmp_ctx, ldb_val_str, guid-ldb_val_str);
+ if (!dn) {
+ ldb_set_errstring(ldb,
+ "rootdse: bad DN in 'enableOptionalFeature'!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ *op_feature_scope_dn = ldb_dn_new(mem_ctx, ldb, dn);
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+/*
+ * This function gets the OPTIONAL_FEATURE_GUID and looks for the optional feature
+ * ldb_message object.
+ */
+static int dsdb_find_optional_feature(struct ldb_module *module, struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx, struct GUID op_feature_guid, struct ldb_message **msg,
+ struct ldb_request *parent)
+{
+ struct ldb_result *res;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ int ret;
+
+ ret = dsdb_module_search(module, tmp_ctx, &res, NULL, LDB_SCOPE_SUBTREE,
+ NULL,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_SEARCH_SEARCH_ALL_PARTITIONS,
+ parent,
+ "(&(objectClass=msDS-OptionalFeature)"
+ "(msDS-OptionalFeatureGUID=%s))",GUID_string(tmp_ctx, &op_feature_guid));
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ if (res->count == 0) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+ if (res->count != 1) {
+ ldb_asprintf_errstring(ldb,
+ "More than one object found matching optional feature GUID %s\n",
+ GUID_string(tmp_ctx, &op_feature_guid));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *msg = talloc_steal(mem_ctx, res->msgs[0]);
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+static int rootdse_enable_recycle_bin(struct ldb_module *module,struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx, struct ldb_dn *op_feature_scope_dn,
+ struct ldb_message *op_feature_msg, struct ldb_request *parent)
+{
+ int ret;
+ const int domain_func_level = dsdb_functional_level(ldb);
+ struct ldb_dn *ntds_settings_dn;
+ TALLOC_CTX *tmp_ctx;
+ unsigned int el_count = 0;
+ struct ldb_message *msg;
+
+ ret = ldb_msg_find_attr_as_int(op_feature_msg, "msDS-RequiredForestBehaviorVersion", 0);
+ if (domain_func_level < ret){
+ ldb_asprintf_errstring(ldb,
+ "rootdse_enable_recycle_bin: Domain functional level must be at least %d\n",
+ ret);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ ntds_settings_dn = samdb_ntds_settings_dn(ldb, tmp_ctx);
+ if (!ntds_settings_dn) {
+ talloc_free(tmp_ctx);
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, "Failed to find NTDS settings DN");
+ }
+
+ ntds_settings_dn = ldb_dn_copy(tmp_ctx, ntds_settings_dn);
+ if (!ntds_settings_dn) {
+ talloc_free(tmp_ctx);
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, "Failed to copy NTDS settings DN");
+ }
+
+ msg = ldb_msg_new(tmp_ctx);
+ if (msg == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+ msg->dn = ntds_settings_dn;
+
+ ldb_msg_add_linearized_dn(msg, "msDS-EnabledFeature", op_feature_msg->dn);
+ msg->elements[el_count++].flags = LDB_FLAG_MOD_ADD;
+
+ ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "rootdse_enable_recycle_bin: Failed to modify object %s - %s",
+ ldb_dn_get_linearized(ntds_settings_dn),
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ msg->dn = op_feature_scope_dn;
+ ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "rootdse_enable_recycle_bin: Failed to modify object %s - %s",
+ ldb_dn_get_linearized(op_feature_scope_dn),
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int rootdse_enableoptionalfeature(struct ldb_module *module, struct ldb_request *req)
+{
+ /*
+ steps:
+ - check for system (only system can enable features)
+ - extract GUID from the request
+ - find the feature object
+ - check functional level, must be at least msDS-RequiredForestBehaviorVersion
+ - check if it is already enabled (if enabled return LDAP_ATTRIBUTE_OR_VALUE_EXISTS) - probably not needed, just return error from the add/modify
+ - add/modify objects (see ntdsconnection code for an example)
+ */
+
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct GUID op_feature_guid;
+ struct ldb_dn *op_feature_scope_dn;
+ struct ldb_message *op_feature_msg;
+ struct auth_session_info *session_info =
+ (struct auth_session_info *)ldb_get_opaque(
+ ldb,
+ DSDB_SESSION_INFO);
+ TALLOC_CTX *tmp_ctx = talloc_new(ldb);
+ int ret;
+ const char *guid_string;
+
+ if (security_session_user_level(session_info, NULL) != SECURITY_SYSTEM) {
+ ldb_set_errstring(ldb, "rootdse: Insufficient rights for enableoptionalfeature");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ ret = get_optional_feature_dn_guid(req, ldb, tmp_ctx, &op_feature_scope_dn, &op_feature_guid);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ guid_string = GUID_string(tmp_ctx, &op_feature_guid);
+ if (!guid_string) {
+ ldb_set_errstring(ldb, "rootdse: bad optional feature GUID");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ ret = dsdb_find_optional_feature(module, ldb, tmp_ctx, op_feature_guid, &op_feature_msg, req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "rootdse: unable to find optional feature for %s - %s",
+ guid_string, ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (strcasecmp(DS_GUID_FEATURE_RECYCLE_BIN, guid_string) == 0) {
+ ret = rootdse_enable_recycle_bin(module, ldb,
+ tmp_ctx, op_feature_scope_dn,
+ op_feature_msg, req);
+ } else {
+ ldb_asprintf_errstring(ldb,
+ "rootdse: unknown optional feature %s",
+ guid_string);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "rootdse: failed to set optional feature for %s - %s",
+ guid_string, ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ talloc_free(tmp_ctx);
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);;
+}
+
+static int rootdse_schemaupdatenow(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_result *ext_res;
+ int ret;
+ struct ldb_dn *schema_dn;
+
+ schema_dn = ldb_get_schema_basedn(ldb);
+ if (!schema_dn) {
+ ldb_reset_err_string(ldb);
+ ldb_debug(ldb, LDB_DEBUG_WARNING,
+ "rootdse_modify: no schema dn present: (skip ldb_extended call)\n");
+ return ldb_next_request(module, req);
+ }
+
+ /*
+ * schemaUpdateNow has been requested. Allow this to refresh the schema
+ * even if we're currently in the middle of a transaction
+ */
+ ret = ldb_set_opaque(ldb, "dsdb_schema_refresh_expected", (void *)1);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ ret = ldb_extended(ldb, DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID, schema_dn, &ext_res);
+ if (ret != LDB_SUCCESS) {
+ ldb_set_opaque(ldb, "dsdb_schema_refresh_expected", (void *)0);
+ return ldb_operr(ldb);
+ }
+
+ talloc_free(ext_res);
+
+ ret = ldb_set_opaque(ldb, "dsdb_schema_refresh_expected", (void *)0);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ return ldb_module_done(req, NULL, NULL, ret);
+}
+
+static int rootdse_schemaupgradeinprogress(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret = LDB_SUCCESS;
+ struct ldb_dn *schema_dn;
+
+ schema_dn = ldb_get_schema_basedn(ldb);
+ if (!schema_dn) {
+ ldb_reset_err_string(ldb);
+ ldb_debug(ldb, LDB_DEBUG_WARNING,
+ "rootdse_modify: no schema dn present: (skip ldb_extended call)\n");
+ return ldb_next_request(module, req);
+ }
+
+ /* FIXME we have to do something in order to relax constraints for DRS
+ * setting schemaUpgradeInProgress cause the fschemaUpgradeInProgress
+ * in all LDAP connection (2K3/2K3R2) or in the current connection (2K8 and +)
+ * to be set to true.
+ */
+
+ /* from 5.113 LDAPConnections in DRSR.pdf
+ * fschemaUpgradeInProgress: A Boolean that specifies certain constraint
+ * validations are skipped when adding, updating, or removing directory
+ * objects on the opened connection. The skipped constraint validations
+ * are documented in the applicable constraint sections in [MS-ADTS].
+ */
+ return ldb_module_done(req, NULL, NULL, ret);
+}
+
+static int rootdse_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret;
+
+ ret = rootdse_filter_operations(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = rootdse_filter_controls(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ If dn is not "" we should let it pass through
+ */
+ if (!ldb_dn_is_null(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb_set_errstring(ldb, "rootdse_add: you cannot add a new rootdse entry!");
+ return LDB_ERR_NAMING_VIOLATION;
+}
+
+static int rootdse_start_trans(struct ldb_module *module)
+{
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct rootdse_private_data *data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct rootdse_private_data);
+ ret = ldb_next_start_trans(module);
+ if (ret == LDB_SUCCESS) {
+ if (data->private_ev != NULL) {
+ return ldb_operr(ldb);
+ }
+ data->private_ev = s4_event_context_init(data);
+ if (data->private_ev == NULL) {
+ return ldb_operr(ldb);
+ }
+ data->saved_ev = ldb_get_event_context(ldb);
+ ldb_set_event_context(ldb, data->private_ev);
+ }
+ return ret;
+}
+
+static int rootdse_end_trans(struct ldb_module *module)
+{
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct rootdse_private_data *data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct rootdse_private_data);
+ ret = ldb_next_end_trans(module);
+ if (data->saved_ev == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ if (data->private_ev != ldb_get_event_context(ldb)) {
+ return ldb_operr(ldb);
+ }
+ ldb_set_event_context(ldb, data->saved_ev);
+ data->saved_ev = NULL;
+ TALLOC_FREE(data->private_ev);
+ return ret;
+}
+
+static int rootdse_del_trans(struct ldb_module *module)
+{
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct rootdse_private_data *data = talloc_get_type_abort(ldb_module_get_private(module),
+ struct rootdse_private_data);
+ ret = ldb_next_del_trans(module);
+ if (data->saved_ev == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ if (data->private_ev != ldb_get_event_context(ldb)) {
+ return ldb_operr(ldb);
+ }
+ ldb_set_event_context(ldb, data->saved_ev);
+ data->saved_ev = NULL;
+ TALLOC_FREE(data->private_ev);
+ return ret;
+}
+
+struct fsmo_transfer_state {
+ struct ldb_context *ldb;
+ struct ldb_request *req;
+ struct ldb_module *module;
+};
+
+/*
+ called when a FSMO transfer operation has completed
+ */
+static void rootdse_fsmo_transfer_callback(struct tevent_req *treq)
+{
+ struct fsmo_transfer_state *fsmo = tevent_req_callback_data(treq, struct fsmo_transfer_state);
+ NTSTATUS status;
+ WERROR werr;
+ int ret;
+ struct ldb_request *req = fsmo->req;
+ struct ldb_context *ldb = fsmo->ldb;
+ struct ldb_module *module = fsmo->module;
+
+ status = dcerpc_drepl_takeFSMORole_recv(treq, fsmo, &werr);
+ talloc_free(fsmo);
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_asprintf_errstring(ldb, "Failed FSMO transfer: %s", nt_errstr(status));
+ /*
+ * Now that it is failed, start the transaction up
+ * again so the wrappers can close it without additional error
+ */
+ rootdse_start_trans(module);
+ ldb_module_done(req, NULL, NULL, LDB_ERR_UNAVAILABLE);
+ return;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ ldb_asprintf_errstring(ldb, "Failed FSMO transfer: %s", win_errstr(werr));
+ /*
+ * Now that it is failed, start the transaction up
+ * again so the wrappers can close it without additional error
+ */
+ rootdse_start_trans(module);
+ ldb_module_done(req, NULL, NULL, LDB_ERR_UNAVAILABLE);
+ return;
+ }
+
+ /*
+ * Now that it is done, start the transaction up again so the
+ * wrappers can close it without error
+ */
+ ret = rootdse_start_trans(module);
+ ldb_module_done(req, NULL, NULL, ret);
+}
+
+static int rootdse_become_master(struct ldb_module *module,
+ struct ldb_request *req,
+ enum drepl_role_master role)
+{
+ struct imessaging_context *msg;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx = talloc_new(req);
+ struct loadparm_context *lp_ctx = ldb_get_opaque(ldb, "loadparm");
+ bool am_rodc;
+ struct dcerpc_binding_handle *irpc_handle;
+ int ret;
+ struct auth_session_info *session_info;
+ enum security_user_level level;
+ struct fsmo_transfer_state *fsmo;
+ struct tevent_req *treq;
+
+ session_info = (struct auth_session_info *)ldb_get_opaque(
+ ldb_module_get_ctx(module),
+ DSDB_SESSION_INFO);
+ level = security_session_user_level(session_info, NULL);
+ if (level < SECURITY_ADMINISTRATOR) {
+ return ldb_error(ldb, LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS, "Denied rootDSE modify for non-administrator");
+ }
+
+ ret = samdb_rodc(ldb, &am_rodc);
+ if (ret != LDB_SUCCESS) {
+ return ldb_error(ldb, ret, "Could not determine if server is RODC.");
+ }
+
+ if (am_rodc) {
+ return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM,
+ "RODC cannot become a role master.");
+ }
+
+ /*
+ * We always delete the transaction, not commit it, because
+ * this gives the least surprise to this surprising action (as
+ * we will never record anything done to this point
+ */
+ rootdse_del_trans(module);
+
+ /*
+ * We must use the global event loop to run this IRPC in
+ * single process mode
+ */
+ ldb_handle_use_global_event_context(req->handle);
+
+ msg = imessaging_client_init(tmp_ctx, lp_ctx,
+ ldb_get_event_context(ldb));
+ if (!msg) {
+ ldb_asprintf_errstring(ldb, "Failed to generate client messaging context in %s", lpcfg_imessaging_path(tmp_ctx, lp_ctx));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ irpc_handle = irpc_binding_handle_by_name(tmp_ctx, msg,
+ "dreplsrv",
+ &ndr_table_irpc);
+ if (irpc_handle == NULL) {
+ return ldb_oom(ldb);
+ }
+ fsmo = talloc_zero(req, struct fsmo_transfer_state);
+ if (fsmo == NULL) {
+ return ldb_oom(ldb);
+ }
+ fsmo->ldb = ldb;
+ fsmo->req = req;
+ fsmo->module = module;
+
+ /*
+ * we send the call asynchronously, as the ldap client is
+ * expecting to get an error back if the role transfer fails
+ *
+ * We need more than the default 10 seconds IRPC allows, so
+ * set a longer timeout (default ldb timeout is 300 seconds).
+ * We send an async reply when we are done.
+ *
+ * We are the first module, so don't bother working out how
+ * long we have spent so far.
+ */
+ dcerpc_binding_handle_set_timeout(irpc_handle, req->timeout);
+
+ treq = dcerpc_drepl_takeFSMORole_send(req, ldb_get_event_context(ldb), irpc_handle, role);
+ if (treq == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ tevent_req_set_callback(treq, rootdse_fsmo_transfer_callback, fsmo);
+ return LDB_SUCCESS;
+}
+
+static int rootdse_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret;
+
+ ret = rootdse_filter_operations(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = rootdse_filter_controls(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ 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
+ */
+ if (ldb_msg_find_element(req->op.mod.message, "schemaUpdateNow")) {
+ return rootdse_schemaupdatenow(module, req);
+ }
+ if (ldb_msg_find_element(req->op.mod.message, "becomeDomainMaster")) {
+ return rootdse_become_master(module, req, DREPL_NAMING_MASTER);
+ }
+ if (ldb_msg_find_element(req->op.mod.message, "becomeInfrastructureMaster")) {
+ return rootdse_become_master(module, req, DREPL_INFRASTRUCTURE_MASTER);
+ }
+ if (ldb_msg_find_element(req->op.mod.message, "becomeRidMaster")) {
+ return rootdse_become_master(module, req, DREPL_RID_MASTER);
+ }
+ if (ldb_msg_find_element(req->op.mod.message, "becomeSchemaMaster")) {
+ return rootdse_become_master(module, req, DREPL_SCHEMA_MASTER);
+ }
+ if (ldb_msg_find_element(req->op.mod.message, "becomePdc")) {
+ return rootdse_become_master(module, req, DREPL_PDC_MASTER);
+ }
+ if (ldb_msg_find_element(req->op.mod.message, "enableOptionalFeature")) {
+ return rootdse_enableoptionalfeature(module, req);
+ }
+ if (ldb_msg_find_element(req->op.mod.message, "schemaUpgradeInProgress")) {
+ return rootdse_schemaupgradeinprogress(module, req);
+ }
+
+ ldb_set_errstring(ldb, "rootdse_modify: unknown attribute to change!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+}
+
+static int rootdse_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret;
+
+ ret = rootdse_filter_operations(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = rootdse_filter_controls(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ If dn is not "" we should let it pass through
+ */
+ if (!ldb_dn_is_null(req->op.rename.olddn)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb_set_errstring(ldb, "rootdse_remove: you cannot rename the rootdse entry!");
+ return LDB_ERR_NO_SUCH_OBJECT;
+}
+
+static int rootdse_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret;
+
+ ret = rootdse_filter_operations(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = rootdse_filter_controls(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ If dn is not "" we should let it pass through
+ */
+ if (!ldb_dn_is_null(req->op.del.dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb_set_errstring(ldb, "rootdse_remove: you cannot delete the rootdse entry!");
+ return LDB_ERR_NO_SUCH_OBJECT;
+}
+
+static int rootdse_extended(struct ldb_module *module, struct ldb_request *req)
+{
+ int ret;
+
+ ret = rootdse_filter_operations(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = rootdse_filter_controls(module, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, req);
+}
+
+static const struct ldb_module_ops ldb_rootdse_module_ops = {
+ .name = "rootdse",
+ .init_context = rootdse_init,
+ .search = rootdse_search,
+ .request = rootdse_request,
+ .add = rootdse_add,
+ .modify = rootdse_modify,
+ .rename = rootdse_rename,
+ .extended = rootdse_extended,
+ .del = rootdse_delete,
+ .start_transaction = rootdse_start_trans,
+ .end_transaction = rootdse_end_trans,
+ .del_transaction = rootdse_del_trans
+};
+
+int ldb_rootdse_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_rootdse_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/samba3sam.c b/source4/dsdb/samdb/ldb_modules/samba3sam.c
new file mode 100644
index 0000000..ebf25ac
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/samba3sam.c
@@ -0,0 +1,977 @@
+/*
+ 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_module.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 "lib/samba3/samba3.h"
+
+/*
+ * sambaSID -> member (dn!)
+ * sambaSIDList -> member (dn!)
+ * sambaDomainName -> name
+ * sambaTrustPassword
+ * sambaUnixIdPool
+ * sambaIdmapEntry
+ * 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, 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-%u", 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);
+ out = ldb_val_dup(ctx, val);
+
+ return out;
+}
+
+static struct ldb_val lookup_homedir(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
+{
+ struct ldb_context *ldb;
+ struct passwd *pwd;
+ struct ldb_val retval;
+
+ ldb = ldb_module_get_ctx(module);
+
+ pwd = getpwnam((char *)val->data);
+
+ if (!pwd) {
+ ldb_debug(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);
+ }
+
+ /* "pw_gid" is per POSIX definition "unsigned".
+ * But write it out as "signed" for LDAP compliance. */
+ retval.data = (uint8_t *)talloc_asprintf(ctx, "%d", (int) 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);
+ }
+
+ /* "pw_uid" is per POSIX definition "unsigned".
+ * But write it out as "signed" for LDAP compliance. */
+ retval.data = (uint8_t *)talloc_asprintf(ctx, "%d", (int) 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,
+ 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, 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 },
+ },
+ { .local_name = NULL }
+};
+
+const struct ldb_map_attribute samba3_attributes[] =
+{
+ /* sambaNextRid -> nextRid */
+ {
+ .local_name = "nextRid",
+ .type = LDB_MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaNextRid",
+ },
+ },
+ },
+
+ /* sambaBadPasswordTime -> badPasswordtime*/
+ {
+ .local_name = "badPasswordTime",
+ .type = LDB_MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaBadPasswordTime",
+ },
+ },
+ },
+
+ /* sambaLMPassword -> lmPwdHash*/
+ {
+ .local_name = "dBCSPwd",
+ .type = LDB_MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "sambaLMPassword",
+ .convert_local = bin2hex,
+ .convert_remote = hex2bin,
+ },
+ },
+ },
+
+ /* sambaGroupType -> groupType */
+ {
+ .local_name = "groupType",
+ .type = LDB_MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaGroupType",
+ },
+ },
+ },
+
+ /* sambaNTPassword -> ntPwdHash*/
+ {
+ .local_name = "ntpwdhash",
+ .type = LDB_MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "sambaNTPassword",
+ .convert_local = bin2hex,
+ .convert_remote = hex2bin,
+ },
+ },
+ },
+
+ /* sambaPrimaryGroupSID -> primaryGroupID */
+ {
+ .local_name = "primaryGroupID",
+ .type = LDB_MAP_GENERATE,
+ .u = {
+ .generate = {
+ .remote_names = { "sambaPrimaryGroupSID", NULL },
+ .generate_local = generate_primaryGroupID,
+ .generate_remote = generate_sambaPrimaryGroupSID,
+ },
+ },
+ },
+
+ /* sambaBadPasswordCount -> badPwdCount */
+ {
+ .local_name = "badPwdCount",
+ .type = LDB_MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaBadPasswordCount",
+ },
+ },
+ },
+
+ /* sambaLogonTime -> lastLogon*/
+ {
+ .local_name = "lastLogon",
+ .type = LDB_MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaLogonTime",
+ },
+ },
+ },
+
+ /* sambaLogoffTime -> lastLogoff*/
+ {
+ .local_name = "lastLogoff",
+ .type = LDB_MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaLogoffTime",
+ },
+ },
+ },
+
+ /* uid -> unixName */
+ {
+ .local_name = "unixName",
+ .type = LDB_MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "uid",
+ },
+ },
+ },
+
+ /* displayName -> name */
+ {
+ .local_name = "name",
+ .type = LDB_MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "displayName",
+ },
+ },
+ },
+
+ /* cn */
+ {
+ .local_name = "cn",
+ .type = LDB_MAP_KEEP,
+ },
+
+ /* sAMAccountName -> cn */
+ {
+ .local_name = "sAMAccountName",
+ .type = LDB_MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "uid",
+ .convert_remote = convert_uid_samaccount,
+ },
+ },
+ },
+
+ /* objectCategory */
+ {
+ .local_name = "objectCategory",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* objectGUID */
+ {
+ .local_name = "objectGUID",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* objectVersion */
+ {
+ .local_name = "objectVersion",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* codePage */
+ {
+ .local_name = "codePage",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* dNSHostName */
+ {
+ .local_name = "dNSHostName",
+ .type = LDB_MAP_IGNORE,
+ },
+
+
+ /* dnsDomain */
+ {
+ .local_name = "dnsDomain",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* dnsRoot */
+ {
+ .local_name = "dnsRoot",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* countryCode */
+ {
+ .local_name = "countryCode",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* nTMixedDomain */
+ {
+ .local_name = "nTMixedDomain",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* operatingSystem */
+ {
+ .local_name = "operatingSystem",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* operatingSystemVersion */
+ {
+ .local_name = "operatingSystemVersion",
+ .type = LDB_MAP_IGNORE,
+ },
+
+
+ /* servicePrincipalName */
+ {
+ .local_name = "servicePrincipalName",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* msDS-Behavior-Version */
+ {
+ .local_name = "msDS-Behavior-Version",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* msDS-KeyVersionNumber */
+ {
+ .local_name = "msDS-KeyVersionNumber",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* msDs-masteredBy */
+ {
+ .local_name = "msDs-masteredBy",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* ou */
+ {
+ .local_name = "ou",
+ .type = LDB_MAP_KEEP,
+ },
+
+ /* dc */
+ {
+ .local_name = "dc",
+ .type = LDB_MAP_KEEP,
+ },
+
+ /* description */
+ {
+ .local_name = "description",
+ .type = LDB_MAP_KEEP,
+ },
+
+ /* sambaSID -> objectSid*/
+ {
+ .local_name = "objectSid",
+ .type = LDB_MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "sambaSID",
+ .convert_local = decode_sid,
+ .convert_remote = encode_sid,
+ },
+ },
+ },
+
+ /* sambaPwdLastSet -> pwdLastSet */
+ {
+ .local_name = "pwdLastSet",
+ .type = LDB_MAP_RENAME,
+ .u = {
+ .rename = {
+ .remote_name = "sambaPwdLastSet",
+ },
+ },
+ },
+
+ /* accountExpires */
+ {
+ .local_name = "accountExpires",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* adminCount */
+ {
+ .local_name = "adminCount",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* canonicalName */
+ {
+ .local_name = "canonicalName",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* createTimestamp */
+ {
+ .local_name = "createTimestamp",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* creationTime */
+ {
+ .local_name = "creationTime",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* dMDLocation */
+ {
+ .local_name = "dMDLocation",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* fSMORoleOwner */
+ {
+ .local_name = "fSMORoleOwner",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* forceLogoff */
+ {
+ .local_name = "forceLogoff",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* instanceType */
+ {
+ .local_name = "instanceType",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* invocationId */
+ {
+ .local_name = "invocationId",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* isCriticalSystemObject */
+ {
+ .local_name = "isCriticalSystemObject",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* localPolicyFlags */
+ {
+ .local_name = "localPolicyFlags",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* lockOutObservationWindow */
+ {
+ .local_name = "lockOutObservationWindow",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* lockoutDuration */
+ {
+ .local_name = "lockoutDuration",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* lockoutThreshold */
+ {
+ .local_name = "lockoutThreshold",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* logonCount */
+ {
+ .local_name = "logonCount",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* masteredBy */
+ {
+ .local_name = "masteredBy",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* maxPwdAge */
+ {
+ .local_name = "maxPwdAge",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* member */
+ {
+ .local_name = "member",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* memberOf */
+ {
+ .local_name = "memberOf",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* minPwdAge */
+ {
+ .local_name = "minPwdAge",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* minPwdLength */
+ {
+ .local_name = "minPwdLength",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* modifiedCount */
+ {
+ .local_name = "modifiedCount",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* modifiedCountAtLastProm */
+ {
+ .local_name = "modifiedCountAtLastProm",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* modifyTimestamp */
+ {
+ .local_name = "modifyTimestamp",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* nCName */
+ {
+ .local_name = "nCName",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* nETBIOSName */
+ {
+ .local_name = "nETBIOSName",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* oEMInformation */
+ {
+ .local_name = "oEMInformation",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* privilege */
+ {
+ .local_name = "privilege",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* pwdHistoryLength */
+ {
+ .local_name = "pwdHistoryLength",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* pwdProperties */
+ {
+ .local_name = "pwdProperties",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* rIDAvailablePool */
+ {
+ .local_name = "rIDAvailablePool",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* revision */
+ {
+ .local_name = "revision",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* ridManagerReference */
+ {
+ .local_name = "ridManagerReference",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* sAMAccountType */
+ {
+ .local_name = "sAMAccountType",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* sPNMappings */
+ {
+ .local_name = "sPNMappings",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* serverReference */
+ {
+ .local_name = "serverReference",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* serverState */
+ {
+ .local_name = "serverState",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* showInAdvancedViewOnly */
+ {
+ .local_name = "showInAdvancedViewOnly",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* subRefs */
+ {
+ .local_name = "subRefs",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* systemFlags */
+ {
+ .local_name = "systemFlags",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* uASCompat */
+ {
+ .local_name = "uASCompat",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* uSNChanged */
+ {
+ .local_name = "uSNChanged",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* uSNCreated */
+ {
+ .local_name = "uSNCreated",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* userPassword */
+ {
+ .local_name = "userPassword",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* userAccountControl */
+ {
+ .local_name = "userAccountControl",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* whenChanged */
+ {
+ .local_name = "whenChanged",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* whenCreated */
+ {
+ .local_name = "whenCreated",
+ .type = LDB_MAP_IGNORE,
+ },
+
+ /* uidNumber */
+ {
+ .local_name = "unixName",
+ .type = LDB_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 = LDB_MAP_CONVERT,
+ .u = {
+ .convert = {
+ .remote_name = "gidNumber",
+ .convert_local = lookup_gid,
+ },
+ },
+ },
+
+ /* homeDirectory */
+ {
+ .local_name = "unixName",
+ .type = LDB_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);
+}
+
+static const struct ldb_module_ops ldb_samba3sam_module_ops = {
+ LDB_MAP_OPS
+ .name = "samba3sam",
+ .init_context = samba3sam_init,
+};
+
+
+/* A dummy module to help the samba3sam tests */
+static int show_deleted_ignore_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_control *show_del, *show_rec;
+
+ /* check if there's a show deleted control */
+ show_del = ldb_request_get_control(req, LDB_CONTROL_SHOW_DELETED_OID);
+ /* check if there's a show recycled control */
+ show_rec = ldb_request_get_control(req, LDB_CONTROL_SHOW_RECYCLED_OID);
+
+ /* mark the controls as done */
+ if (show_del != NULL) {
+ show_del->critical = 0;
+ }
+ if (show_rec != NULL) {
+ show_rec->critical = 0;
+ }
+
+ /* perform the search */
+ return ldb_next_request(module, req);
+}
+
+static const struct ldb_module_ops ldb_show_deleted_module_ops = {
+ .name = "show_deleted_ignore",
+ .search = show_deleted_ignore_search
+};
+
+int ldb_samba3sam_module_init(const char *version)
+{
+ int ret;
+
+ LDB_MODULE_CHECK_VERSION(version);
+ ret = ldb_register_module(&ldb_show_deleted_module_ops);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_register_module(&ldb_samba3sam_module_ops);
+}
+
diff --git a/source4/dsdb/samdb/ldb_modules/samba3sid.c b/source4/dsdb/samdb/ldb_modules/samba3sid.c
new file mode 100644
index 0000000..f38ab40
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/samba3sid.c
@@ -0,0 +1,207 @@
+/*
+ samba3sid module
+
+ Copyright (C) Andrew Bartlett 2010
+ Copyright (C) Andrew Tridgell 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ add objectSid to users and groups using samba3 nextRid method
+ */
+
+#include "includes.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "ldb_wrap.h"
+#include "param/param.h"
+
+/*
+ RID algorithm from pdb_ldap.c in source3/passdb/
+ (loosely based on Volkers code)
+ */
+static int samba3sid_next_sid(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, char **sid,
+ struct ldb_request *parent)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ struct ldb_result *res;
+ const char *attrs[] = { "sambaNextRid", "sambaNextUserRid",
+ "sambaNextGroupRid", "sambaSID", NULL };
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_message *msg;
+ uint32_t sambaNextRid, sambaNextGroupRid, sambaNextUserRid, rid;
+ const char *sambaSID;
+
+ ret = dsdb_module_search(module, tmp_ctx, &res, NULL, LDB_SCOPE_SUBTREE,
+ attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SEARCH_ALL_PARTITIONS,
+ parent,
+ "(&(objectClass=sambaDomain)(sambaDomainName=%s))",
+ lpcfg_sam_name(ldb_get_opaque(ldb, "loadparm")));
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ __location__
+ ": Failed to find domain object - %s",
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ if (res->count != 1) {
+ ldb_asprintf_errstring(ldb,
+ __location__
+ ": Expected exactly 1 domain object - got %u",
+ res->count);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ msg = res->msgs[0];
+
+ sambaNextRid = ldb_msg_find_attr_as_uint(msg, "sambaNextRid",
+ (uint32_t) -1);
+ sambaNextUserRid = ldb_msg_find_attr_as_uint(msg, "sambaNextUserRid",
+ (uint32_t) -1);
+ sambaNextGroupRid = ldb_msg_find_attr_as_uint(msg, "sambaNextGroupRid",
+ (uint32_t) -1);
+ sambaSID = ldb_msg_find_attr_as_string(msg, "sambaSID", NULL);
+
+ if (sambaSID == NULL) {
+ ldb_asprintf_errstring(ldb,
+ __location__
+ ": No sambaSID in %s",
+ ldb_dn_get_linearized(msg->dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* choose the highest of the 3 - see pdb_ldap.c for an
+ * explaination */
+ rid = sambaNextRid;
+ if ((sambaNextUserRid != (uint32_t) -1) && (sambaNextUserRid > rid)) {
+ rid = sambaNextUserRid;
+ }
+ if ((sambaNextGroupRid != (uint32_t) -1) && (sambaNextGroupRid > rid)) {
+ rid = sambaNextGroupRid;
+ }
+ if (rid == (uint32_t) -1) {
+ ldb_asprintf_errstring(ldb,
+ __location__
+ ": No sambaNextRid in %s",
+ ldb_dn_get_linearized(msg->dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* sambaNextRid is actually the previous RID .... */
+ rid += 1;
+
+ (*sid) = talloc_asprintf(tmp_ctx, "%s-%u", sambaSID, rid);
+ if (!*sid) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+
+ ret = dsdb_module_constrainted_update_uint32(module, msg->dn,
+ "sambaNextRid",
+ &sambaNextRid, &rid, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ __location__
+ ": Failed to update sambaNextRid - %s",
+ ldb_errstring(ldb));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ talloc_steal(mem_ctx, *sid);
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+
+
+/* add */
+static int samba3sid_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ int ret;
+ const struct ldb_message *msg = req->op.add.message;
+ struct ldb_message *new_msg;
+ char *sid;
+ struct ldb_request *new_req;
+
+ ldb = ldb_module_get_ctx(module);
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ if (!samdb_find_attribute(ldb, msg, "objectclass", "posixAccount") &&
+ !samdb_find_attribute(ldb, msg, "objectclass", "posixGroup")) {
+ /* its not a user or a group */
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_msg_find_element(msg, "sambaSID")) {
+ /* a SID was supplied */
+ return ldb_next_request(module, req);
+ }
+
+ new_msg = ldb_msg_copy_shallow(req, req->op.add.message);
+ if (!new_msg) {
+ return ldb_module_oom(module);
+ }
+
+ ret = samba3sid_next_sid(module, new_msg, &sid, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_msg_add_steal_string(new_msg, "sambaSID", sid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_build_add_req(&new_req, ldb, req,
+ new_msg,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(new_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, new_req);
+}
+
+static const struct ldb_module_ops ldb_samba3sid_module_ops = {
+ .name = "samba3sid",
+ .add = samba3sid_add,
+};
+
+
+int ldb_samba3sid_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_samba3sid_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/samba_dsdb.c b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c
new file mode 100644
index 0000000..d9de16e
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c
@@ -0,0 +1,608 @@
+/*
+ Samba4 module loading module
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009
+
+ 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: Samba4 module loading module
+ *
+ * Description: Implement a single 'module' in the ldb database,
+ * which loads the remaining modules based on 'choice of configuration' attributes
+ *
+ * This is to avoid forcing a reprovision of the ldb databases when we change the internal structure of the code
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include <ldb.h>
+#include <ldb_errors.h>
+#include <ldb_module.h>
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/ndr/libndr.h"
+#include "auth/credentials/credentials.h"
+#include "param/secrets.h"
+#include "lib/ldb-samba/ldb_wrap.h"
+
+static int read_at_rootdse_record(struct ldb_context *ldb, struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ struct ldb_message **msg, struct ldb_request *parent)
+{
+ int ret;
+ static const char *rootdse_attrs[] = { "defaultNamingContext", "configurationNamingContext", "schemaNamingContext", NULL };
+ struct ldb_result *rootdse_res;
+ struct ldb_dn *rootdse_dn;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return ldb_oom(ldb);
+ }
+
+ rootdse_dn = ldb_dn_new(tmp_ctx, ldb, "@ROOTDSE");
+ if (!rootdse_dn) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &rootdse_res, rootdse_dn,
+ rootdse_attrs, DSDB_FLAG_NEXT_MODULE, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ talloc_steal(mem_ctx, rootdse_res->msgs);
+ *msg = rootdse_res->msgs[0];
+
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
+static int prepare_modules_line(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ const struct ldb_message *rootdse_msg,
+ struct ldb_message *msg, const char *backend_attr,
+ const char *backend_mod, const char **backend_mod_list)
+{
+ int ret;
+ const char **backend_full_list;
+ const char *backend_dn;
+ char *mod_list_string;
+ char *full_string;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return ldb_oom(ldb);
+ }
+
+ if (backend_attr) {
+ backend_dn = ldb_msg_find_attr_as_string(rootdse_msg, backend_attr, NULL);
+ if (!backend_dn) {
+ ldb_asprintf_errstring(ldb,
+ "samba_dsdb_init: "
+ "unable to read %s from %s:%s",
+ backend_attr, ldb_dn_get_linearized(rootdse_msg->dn),
+ ldb_errstring(ldb));
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ } else {
+ backend_dn = "*";
+ }
+
+ if (backend_mod) {
+ char **b = str_list_make_single(tmp_ctx, backend_mod);
+ backend_full_list = discard_const_p(const char *, b);
+ } else {
+ char **b = str_list_make_empty(tmp_ctx);
+ backend_full_list = discard_const_p(const char *, b);
+ }
+ if (!backend_full_list) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ backend_full_list = str_list_append_const(backend_full_list, backend_mod_list);
+ if (!backend_full_list) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ mod_list_string = str_list_join(tmp_ctx, backend_full_list, ',');
+
+ /* str_list_append allocates on NULL */
+ talloc_free(backend_full_list);
+
+ if (!mod_list_string) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ full_string = talloc_asprintf(tmp_ctx, "%s:%s", backend_dn, mod_list_string);
+ ret = ldb_msg_add_steal_string(msg, "modules", full_string);
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static bool check_required_features(struct ldb_message_element *el)
+{
+ if (el != NULL) {
+ int k;
+ DATA_BLOB esf = data_blob_string_const(
+ SAMBA_ENCRYPTED_SECRETS_FEATURE);
+ DATA_BLOB lmdbl1 = data_blob_string_const(
+ SAMBA_LMDB_LEVEL_ONE_FEATURE);
+ for (k = 0; k < el->num_values; k++) {
+ if ((data_blob_cmp(&esf, &el->values[k]) != 0) &&
+ (data_blob_cmp(&lmdbl1, &el->values[k]) != 0)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static int samba_dsdb_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret, lock_ret, len, i, j;
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct ldb_result *res;
+ struct ldb_message *rootdse_msg = NULL, *partition_msg;
+ struct ldb_dn *samba_dsdb_dn, *partition_dn, *indexlist_dn;
+ struct ldb_module *backend_module, *module_chain;
+ const char **final_module_list, **reverse_module_list;
+ /*
+ Add modules to the list to activate them by default
+ beware often order is important
+
+ Some Known ordering constraints:
+ - rootdse must be first, as it makes redirects from "" -> cn=rootdse
+ - extended_dn_in must be before objectclass.c, as it resolves the DN
+ - objectclass must be before password_hash and samldb since these LDB
+ modules require the expanded "objectClass" list
+ - objectclass must be before descriptor and acl, as both assume that
+ objectClass values are sorted
+ - objectclass_attrs must be behind operational in order to see all
+ attributes (the operational module protects and therefore
+ suppresses per default some important ones)
+ - partition must be last
+ - each partition has its own module list then
+
+ The list is presented here as a set of declarations to show the
+ stack visually - the code below then handles the creation of the list
+ based on the parameters loaded from the database.
+ */
+ static const char *modules_list1[] = {"resolve_oids",
+ "rootdse",
+ "dsdb_notification",
+ "schema_load",
+ "lazy_commit",
+ "dirsync",
+ "dsdb_paged_results",
+ "vlv",
+ "ranged_results",
+ "anr",
+ "server_sort",
+ "asq",
+ "extended_dn_store",
+ NULL };
+ /* extended_dn_in or extended_dn_in_openldap goes here */
+ static const char *modules_list1a[] = {"audit_log",
+ "objectclass",
+ "tombstone_reanimate",
+ "descriptor",
+ "acl",
+ "aclread",
+ "samldb",
+ "password_hash",
+ "instancetype",
+ "objectclass_attrs",
+ NULL };
+
+ const char **link_modules;
+ static const char *tdb_modules_list[] = {
+ "rdn_name",
+ "subtree_delete",
+ "repl_meta_data",
+ "group_audit_log",
+ "encrypted_secrets",
+ "operational",
+ "unique_object_sids",
+ "subtree_rename",
+ "linked_attributes",
+ NULL};
+
+ const char *extended_dn_module;
+ const char *extended_dn_module_ldb = "extended_dn_out_ldb";
+ const char *extended_dn_in_module = "extended_dn_in";
+
+ static const char *modules_list2[] = {"dns_notify",
+ "show_deleted",
+ "new_partition",
+ "partition",
+ NULL };
+
+ const char **backend_modules;
+ static const char *samba_dsdb_attrs[] = { SAMBA_COMPATIBLE_FEATURES_ATTR,
+ SAMBA_REQUIRED_FEATURES_ATTR, NULL };
+ static const char *indexlist_attrs[] = { SAMBA_FEATURES_SUPPORTED_FLAG, NULL };
+
+ const char *current_supportedFeatures[] = {SAMBA_SORTED_LINKS_FEATURE};
+
+ if (!tmp_ctx) {
+ return ldb_oom(ldb);
+ }
+
+ ret = ldb_register_samba_handlers(ldb);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ samba_dsdb_dn = ldb_dn_new(tmp_ctx, ldb, "@SAMBA_DSDB");
+ if (!samba_dsdb_dn) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ indexlist_dn = ldb_dn_new(tmp_ctx, ldb, "@INDEXLIST");
+ if (!samba_dsdb_dn) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ partition_dn = ldb_dn_new(tmp_ctx, ldb, DSDB_PARTITION_DN);
+ if (!partition_dn) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+#define CHECK_LDB_RET(check_ret) \
+ do { \
+ if (check_ret != LDB_SUCCESS) { \
+ talloc_free(tmp_ctx); \
+ return check_ret; \
+ } \
+ } while (0)
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res, samba_dsdb_dn,
+ samba_dsdb_attrs, DSDB_FLAG_NEXT_MODULE, NULL);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ /* do nothing, a very old db being upgraded */
+ } else if (ret == LDB_SUCCESS) {
+ struct ldb_message_element *requiredFeatures;
+ struct ldb_message_element *old_compatibleFeatures;
+
+ requiredFeatures = ldb_msg_find_element(res->msgs[0], SAMBA_REQUIRED_FEATURES_ATTR);
+ if (!check_required_features(requiredFeatures)) {
+ ldb_set_errstring(
+ ldb,
+ "This Samba database was created with "
+ "a newer Samba version and is marked "
+ "with extra requiredFeatures in "
+ "@SAMBA_DSDB. This database can not "
+ "safely be read by this Samba version");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ old_compatibleFeatures = ldb_msg_find_element(res->msgs[0],
+ SAMBA_COMPATIBLE_FEATURES_ATTR);
+
+ if (old_compatibleFeatures) {
+ struct ldb_message *features_msg;
+ struct ldb_message_element *features_el;
+ int samba_options_supported = 0;
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res,
+ indexlist_dn,
+ indexlist_attrs,
+ DSDB_FLAG_NEXT_MODULE, NULL);
+ if (ret == LDB_SUCCESS) {
+ samba_options_supported
+ = ldb_msg_find_attr_as_int(res->msgs[0],
+ SAMBA_FEATURES_SUPPORTED_FLAG,
+ 0);
+
+ } else if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ /*
+ * If we don't have @INDEXLIST yet, then we
+ * are so early in set-up that we know this is
+ * a blank DB, so no need to wripe out old
+ * features
+ */
+ samba_options_supported = 1;
+ }
+
+ features_msg = ldb_msg_new(res);
+ if (features_msg == NULL) {
+ return ldb_module_operr(module);
+ }
+ features_msg->dn = samba_dsdb_dn;
+
+ ldb_msg_add_empty(features_msg, SAMBA_COMPATIBLE_FEATURES_ATTR,
+ LDB_FLAG_MOD_DELETE, &features_el);
+
+ if (samba_options_supported == 1) {
+ for (i = 0;
+ old_compatibleFeatures && i < old_compatibleFeatures->num_values;
+ i++) {
+ for (j = 0;
+ j < ARRAY_SIZE(current_supportedFeatures); j++) {
+ if (strcmp((char *)old_compatibleFeatures->values[i].data,
+ current_supportedFeatures[j]) == 0) {
+ break;
+ }
+ }
+ if (j == ARRAY_SIZE(current_supportedFeatures)) {
+ /*
+ * Add to list of features to remove
+ * (rather than all features)
+ */
+ ret = ldb_msg_add_value(features_msg, SAMBA_COMPATIBLE_FEATURES_ATTR,
+ &old_compatibleFeatures->values[i],
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ if (features_el->num_values > 0) {
+ /* Delete by list */
+ ret = ldb_next_start_trans(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = dsdb_module_modify(module, features_msg, DSDB_FLAG_NEXT_MODULE, NULL);
+ if (ret != LDB_SUCCESS) {
+ ldb_next_del_trans(module);
+ return ret;
+ }
+ ret = ldb_next_end_trans(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ } else {
+ /* Delete all */
+ ret = ldb_next_start_trans(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = dsdb_module_modify(module, features_msg, DSDB_FLAG_NEXT_MODULE, NULL);
+ if (ret != LDB_SUCCESS) {
+ ldb_next_del_trans(module);
+ return ret;
+ }
+ ret = ldb_next_end_trans(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ } else {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ backend_modules = NULL;
+ extended_dn_module = extended_dn_module_ldb;
+ link_modules = tdb_modules_list;
+
+#define CHECK_MODULE_LIST \
+ do { \
+ if (!final_module_list) { \
+ talloc_free(tmp_ctx); \
+ return ldb_oom(ldb); \
+ } \
+ } while (0)
+
+ final_module_list = str_list_copy_const(tmp_ctx, modules_list1);
+ CHECK_MODULE_LIST;
+
+ final_module_list = str_list_add_const(final_module_list, extended_dn_in_module);
+ CHECK_MODULE_LIST;
+
+ final_module_list = str_list_append_const(final_module_list, modules_list1a);
+ CHECK_MODULE_LIST;
+
+ final_module_list = str_list_append_const(final_module_list, link_modules);
+ CHECK_MODULE_LIST;
+
+ final_module_list = str_list_add_const(final_module_list, extended_dn_module);
+ CHECK_MODULE_LIST;
+
+ final_module_list = str_list_append_const(final_module_list, modules_list2);
+ CHECK_MODULE_LIST;
+
+
+ ret = read_at_rootdse_record(ldb, module, tmp_ctx, &rootdse_msg, NULL);
+ CHECK_LDB_RET(ret);
+
+ partition_msg = ldb_msg_new(tmp_ctx);
+ partition_msg->dn = ldb_dn_new(partition_msg, ldb, "@" DSDB_OPAQUE_PARTITION_MODULE_MSG_OPAQUE_NAME);
+
+ ret = prepare_modules_line(ldb, tmp_ctx,
+ rootdse_msg,
+ partition_msg, "schemaNamingContext",
+ "schema_data", backend_modules);
+ CHECK_LDB_RET(ret);
+
+ ret = prepare_modules_line(ldb, tmp_ctx,
+ rootdse_msg,
+ partition_msg, NULL,
+ NULL, backend_modules);
+ CHECK_LDB_RET(ret);
+
+ ret = ldb_set_opaque(ldb, DSDB_OPAQUE_PARTITION_MODULE_MSG_OPAQUE_NAME, partition_msg);
+ CHECK_LDB_RET(ret);
+
+ talloc_steal(ldb, partition_msg);
+
+ /* Now prepare the module chain. Oddly, we must give it to
+ * ldb_module_load_list in REVERSE */
+ for (len = 0; final_module_list[len]; len++) { /* noop */};
+
+ reverse_module_list = talloc_array(tmp_ctx, const char *, len+1);
+ if (!reverse_module_list) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+ for (i=0; i < len; i++) {
+ reverse_module_list[i] = final_module_list[(len - 1) - i];
+ }
+ reverse_module_list[i] = NULL;
+
+ /* The backend (at least until the partitions module
+ * reconfigures things) is the next module in the currently
+ * loaded chain */
+ backend_module = ldb_module_next(module);
+ ret = ldb_module_load_list(ldb, reverse_module_list, backend_module, &module_chain);
+ CHECK_LDB_RET(ret);
+
+ talloc_free(tmp_ctx);
+ /* Set this as the 'next' module, so that we effectively append it to
+ * module chain */
+ ldb_module_set_next(module, module_chain);
+
+ ret = ldb_next_read_lock(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_next_init(module);
+
+ lock_ret = ldb_next_read_unlock(module);
+
+ if (lock_ret != LDB_SUCCESS) {
+ return lock_ret;
+ }
+
+ return ret;
+}
+
+static const struct ldb_module_ops ldb_samba_dsdb_module_ops = {
+ .name = "samba_dsdb",
+ .init_context = samba_dsdb_init,
+};
+
+static struct ldb_message *dsdb_flags_ignore_fixup(TALLOC_CTX *mem_ctx,
+ const struct ldb_message *_msg)
+{
+ struct ldb_message *msg = NULL;
+ unsigned int i;
+
+ /* we have to copy the message as the caller might have it as a const */
+ msg = ldb_msg_copy_shallow(mem_ctx, _msg);
+ if (msg == NULL) {
+ return NULL;
+ }
+
+ for (i=0; i < msg->num_elements;) {
+ struct ldb_message_element *e = &msg->elements[i];
+
+ if (!(e->flags & DSDB_FLAG_INTERNAL_FORCE_META_DATA)) {
+ i++;
+ continue;
+ }
+
+ e->flags &= ~DSDB_FLAG_INTERNAL_FORCE_META_DATA;
+
+ if (e->num_values != 0) {
+ i++;
+ continue;
+ }
+
+ ldb_msg_remove_element(msg, e);
+ }
+
+ return msg;
+}
+
+static int dsdb_flags_ignore_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_request *down_req = NULL;
+ struct ldb_message *msg = NULL;
+ int ret;
+
+ msg = dsdb_flags_ignore_fixup(req, req->op.add.message);
+ if (msg == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ ret = ldb_build_add_req(&down_req, ldb, req,
+ msg,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* go on with the call chain */
+ return ldb_next_request(module, down_req);
+}
+
+static int dsdb_flags_ignore_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_request *down_req = NULL;
+ struct ldb_message *msg = NULL;
+ int ret;
+
+ msg = dsdb_flags_ignore_fixup(req, req->op.mod.message);
+ if (msg == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ ret = ldb_build_mod_req(&down_req, ldb, req,
+ msg,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* go on with the call chain */
+ return ldb_next_request(module, down_req);
+}
+
+static const struct ldb_module_ops ldb_dsdb_flags_ignore_module_ops = {
+ .name = "dsdb_flags_ignore",
+ .add = dsdb_flags_ignore_add,
+ .modify = dsdb_flags_ignore_modify,
+};
+
+int ldb_samba_dsdb_module_init(const char *version)
+{
+ int ret;
+ LDB_MODULE_CHECK_VERSION(version);
+ ret = ldb_register_module(&ldb_samba_dsdb_module_ops);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = ldb_register_module(&ldb_dsdb_flags_ignore_module_ops);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return LDB_SUCCESS;
+}
diff --git a/source4/dsdb/samdb/ldb_modules/samba_secrets.c b/source4/dsdb/samdb/ldb_modules/samba_secrets.c
new file mode 100644
index 0000000..d184ee7
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/samba_secrets.c
@@ -0,0 +1,103 @@
+/*
+ Samba4 module loading module (for secrets)
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009
+
+ 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: Samba4 module loading module (for secrets.ldb)
+ *
+ * Description: Implement a single 'module' in the secrets.ldb database
+ *
+ * This is to avoid forcing a reprovision of the ldb databases when we change the internal structure of the code
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include <ldb.h>
+#include <ldb_errors.h>
+#include <ldb_module.h>
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "dsdb/samdb/samdb.h"
+
+
+static int samba_secrets_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret, len, i;
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct ldb_module *backend_module, *module_chain;
+ const char **reverse_module_list;
+ /*
+ Add modules to the list to activate them by default
+ beware often order is important
+
+ The list is presented here as a set of declarations to show the
+ stack visually
+ */
+ static const char *modules_list[] = {"update_keytab",
+ "secrets_tdb_sync",
+ "objectguid",
+ "rdn_name",
+ NULL };
+
+ if (!tmp_ctx) {
+ return ldb_oom(ldb);
+ }
+
+ /* Now prepare the module chain. Oddly, we must give it to ldb_load_modules_list in REVERSE */
+ for (len = 0; modules_list[len]; len++) { /* noop */};
+
+ reverse_module_list = talloc_array(tmp_ctx, const char *, len+1);
+ if (!reverse_module_list) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+ for (i=0; i < len; i++) {
+ reverse_module_list[i] = modules_list[(len - 1) - i];
+ }
+ reverse_module_list[i] = NULL;
+
+ /* The backend (at least until the partitions module
+ * reconfigures things) is the next module in the currently
+ * loaded chain */
+ backend_module = ldb_module_next(module);
+ ret = ldb_module_load_list(ldb, reverse_module_list, backend_module, &module_chain);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ talloc_free(tmp_ctx);
+ /* Set this as the 'next' module, so that we effectivly append it to module chain */
+ ldb_module_set_next(module, module_chain);
+
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_samba_secrets_module_ops = {
+ .name = "samba_secrets",
+ .init_context = samba_secrets_init,
+};
+
+int ldb_samba_secrets_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_samba_secrets_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c
new file mode 100644
index 0000000..d501973
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/samldb.c
@@ -0,0 +1,5724 @@
+/*
+ SAM ldb module
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2014
+ Copyright (C) Simo Sorce 2004-2008
+ Copyright (C) Matthias Dieter Wallnöfer 2009-2011
+ Copyright (C) Matthieu Patou 2012
+ Copyright (C) Catalyst.Net Ltd 2017
+
+ 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: various internal DSDB triggers - most for SAM specific objects
+ *
+ * Author: Simo Sorce
+ */
+
+#include "includes.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "ldb_module.h"
+#include "auth/auth.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "dsdb/samdb/ldb_modules/ridalloc.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "ldb_wrap.h"
+#include "param/param.h"
+#include "libds/common/flag_mapping.h"
+#include "system/network.h"
+#include "librpc/gen_ndr/irpc.h"
+#include "lib/util/smb_strtox.h"
+
+#undef strcasecmp
+
+struct samldb_ctx;
+enum samldb_add_type {
+ SAMLDB_TYPE_USER,
+ SAMLDB_TYPE_GROUP,
+ SAMLDB_TYPE_CLASS,
+ SAMLDB_TYPE_ATTRIBUTE
+};
+
+typedef int (*samldb_step_fn_t)(struct samldb_ctx *);
+
+struct samldb_step {
+ struct samldb_step *next;
+ samldb_step_fn_t fn;
+};
+
+struct samldb_ctx {
+ struct ldb_module *module;
+ struct ldb_request *req;
+
+ /* used for add operations */
+ enum samldb_add_type type;
+
+ /*
+ * should we apply the need_trailing_dollar restriction to
+ * samAccountName
+ */
+
+ bool need_trailing_dollar;
+
+ /* the resulting message */
+ struct ldb_message *msg;
+
+ /* used in "samldb_find_for_defaultObjectCategory" */
+ struct ldb_dn *dn, *res_dn;
+
+ /* all the async steps necessary to complete the operation */
+ struct samldb_step *steps;
+ struct samldb_step *curstep;
+
+ /* If someone set an ares to forward controls and response back to the caller */
+ struct ldb_reply *ares;
+};
+
+static struct samldb_ctx *samldb_ctx_init(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct samldb_ctx *ac;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = talloc_zero(req, struct samldb_ctx);
+ if (ac == NULL) {
+ ldb_oom(ldb);
+ return NULL;
+ }
+
+ ac->module = module;
+ ac->req = req;
+
+ return ac;
+}
+
+static int samldb_add_step(struct samldb_ctx *ac, samldb_step_fn_t fn)
+{
+ struct samldb_step *step, *stepper;
+
+ step = talloc_zero(ac, struct samldb_step);
+ if (step == NULL) {
+ return ldb_oom(ldb_module_get_ctx(ac->module));
+ }
+
+ step->fn = fn;
+
+ if (ac->steps == NULL) {
+ ac->steps = step;
+ ac->curstep = step;
+ } else {
+ if (ac->curstep == NULL)
+ return ldb_operr(ldb_module_get_ctx(ac->module));
+ for (stepper = ac->curstep; stepper->next != NULL;
+ stepper = stepper->next);
+ stepper->next = step;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_first_step(struct samldb_ctx *ac)
+{
+ if (ac->steps == NULL) {
+ return ldb_operr(ldb_module_get_ctx(ac->module));
+ }
+
+ ac->curstep = ac->steps;
+ return ac->curstep->fn(ac);
+}
+
+static int samldb_next_step(struct samldb_ctx *ac)
+{
+ if (ac->curstep->next) {
+ ac->curstep = ac->curstep->next;
+ return ac->curstep->fn(ac);
+ }
+
+ /* We exit the samldb module here. If someone set an "ares" to forward
+ * controls and response back to the caller, use them. */
+ if (ac->ares) {
+ return ldb_module_done(ac->req, ac->ares->controls,
+ ac->ares->response, LDB_SUCCESS);
+ } else {
+ return ldb_module_done(ac->req, NULL, NULL, LDB_SUCCESS);
+ }
+}
+
+static int samldb_get_single_valued_attr(struct ldb_context *ldb,
+ struct samldb_ctx *ac,
+ const char *attr,
+ const char **value)
+{
+ /*
+ * The steps we end up going through to get and check a single valued
+ * attribute.
+ */
+ struct ldb_message_element *el = NULL;
+ int ret;
+
+ *value = NULL;
+
+ ret = dsdb_get_expected_new_values(ac,
+ ac->msg,
+ attr,
+ &el,
+ ac->req->operation);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (el == NULL) {
+ /* we are not affected */
+ return LDB_SUCCESS;
+ }
+
+ if (el->num_values > 1) {
+ ldb_asprintf_errstring(
+ ldb,
+ "samldb: %s has %u values, should be single-valued!",
+ attr, el->num_values);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ } else if (el->num_values == 0) {
+ ldb_asprintf_errstring(
+ ldb,
+ "samldb: new value for %s "
+ "not provided for mandatory, single-valued attribute!",
+ attr);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+
+ if (el->values[0].length == 0) {
+ ldb_asprintf_errstring(
+ ldb,
+ "samldb: %s is of zero length, should have a value!",
+ attr);
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ *value = (char *)el->values[0].data;
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_unique_attr_check(struct samldb_ctx *ac, const char *attr,
+ const char *attr_conflict,
+ struct ldb_dn *base_dn)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ const char * const no_attrs[] = { NULL };
+ struct ldb_result *res = NULL;
+ const char *str = NULL;
+ const char *enc_str = NULL;
+ int ret;
+
+ ret = samldb_get_single_valued_attr(ldb, ac, attr, &str);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (str == NULL) {
+ /* the attribute wasn't found */
+ return LDB_SUCCESS;
+ }
+
+ enc_str = ldb_binary_encode_string(ac, str);
+ if (enc_str == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ /*
+ * No other object should have the attribute with this value.
+ */
+ if (attr_conflict != NULL) {
+ ret = dsdb_module_search(ac->module, ac, &res,
+ base_dn,
+ LDB_SCOPE_SUBTREE, no_attrs,
+ DSDB_FLAG_NEXT_MODULE, ac->req,
+ "(|(%s=%s)(%s=%s))",
+ attr, enc_str,
+ attr_conflict, enc_str);
+ } else {
+ ret = dsdb_module_search(ac->module, ac, &res,
+ base_dn,
+ LDB_SCOPE_SUBTREE, no_attrs,
+ DSDB_FLAG_NEXT_MODULE, ac->req,
+ "(%s=%s)", attr, enc_str);
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (res->count > 1) {
+ return ldb_operr(ldb);
+ } else if (res->count == 1) {
+ if (ldb_dn_compare(res->msgs[0]->dn, ac->msg->dn) != 0) {
+ ldb_asprintf_errstring(ldb,
+ "samldb: %s '%s' already in use!",
+ attr, enc_str);
+ return LDB_ERR_ENTRY_ALREADY_EXISTS;
+ }
+ }
+ talloc_free(res);
+
+ return LDB_SUCCESS;
+}
+
+
+
+static inline int samldb_sam_account_upn_clash_sub_search(
+ struct samldb_ctx *ac,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *base_dn,
+ const char *attr,
+ const char *value,
+ const char *err_msg
+ )
+{
+ /*
+ * A very specific helper function for samldb_sam_account_upn_clash(),
+ * where we end up doing this same thing several times in a row.
+ */
+ const char * const no_attrs[] = { NULL };
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct ldb_result *res = NULL;
+ int ret;
+ char *enc_value = ldb_binary_encode_string(ac, value);
+ if (enc_value == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ ret = dsdb_module_search(ac->module, mem_ctx, &res,
+ base_dn,
+ LDB_SCOPE_SUBTREE, no_attrs,
+ DSDB_FLAG_NEXT_MODULE, ac->req,
+ "(%s=%s)",
+ attr, enc_value);
+ talloc_free(enc_value);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ } else if (res->count > 1) {
+ return ldb_operr(ldb);
+ } else if (res->count == 1) {
+ if (ldb_dn_compare(res->msgs[0]->dn, ac->msg->dn) != 0){
+ ldb_asprintf_errstring(ldb,
+ "samldb: %s '%s' "
+ "is already in use %s",
+ attr, value, err_msg);
+ /* different errors for different attrs */
+ if (strcasecmp("userPrincipalName", attr) == 0) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ return LDB_ERR_ENTRY_ALREADY_EXISTS;
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+static int samaccountname_bad_chars_check(struct samldb_ctx *ac,
+ const char *name)
+{
+ /*
+ * The rules here are based on
+ *
+ * https://social.technet.microsoft.com/wiki/contents/articles/11216.active-directory-requirements-for-creating-objects.aspx
+ *
+ * Windows considers UTF-8 sequences that map to "similar" characters
+ * (e.g. 'a', 'ā') to be the same sAMAccountName, and we don't. Names
+ * that are not valid UTF-8 *are* allowed.
+ *
+ * Additionally, Samba collapses multiple spaces, and Windows doesn't.
+ */
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ size_t i;
+
+ for (i = 0; name[i] != '\0'; i++) {
+ uint8_t c = name[i];
+ char *p = NULL;
+ if (c < 32 || c == 127) {
+ ldb_asprintf_errstring(
+ ldb,
+ "samldb: sAMAccountName contains invalid "
+ "0x%.2x character\n", c);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ p = strchr("\"[]:;|=+*?<>/\\,", c);
+ if (p != NULL) {
+ ldb_asprintf_errstring(
+ ldb,
+ "samldb: sAMAccountName contains invalid "
+ "'%c' character\n", c);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+
+ if (i == 0) {
+ ldb_asprintf_errstring(
+ ldb,
+ "samldb: sAMAccountName is empty\n");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ if (name[i - 1] == '.') {
+ ldb_asprintf_errstring(
+ ldb,
+ "samldb: sAMAccountName ends with '.'");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ return LDB_SUCCESS;
+}
+
+static int samldb_sam_account_upn_clash(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ int ret;
+ struct ldb_dn *base_dn = ldb_get_default_basedn(ldb);
+ TALLOC_CTX *tmp_ctx = NULL;
+ const char *real_sam = NULL;
+ const char *real_upn = NULL;
+ char *implied_sam = NULL;
+ char *implied_upn = NULL;
+ const char *realm = NULL;
+
+ ret = samldb_get_single_valued_attr(ldb, ac,
+ "sAMAccountName",
+ &real_sam);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = samldb_get_single_valued_attr(ldb, ac,
+ "userPrincipalName",
+ &real_upn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (real_upn == NULL && real_sam == NULL) {
+ /* Not changing these things, so we're done */
+ return LDB_SUCCESS;
+ }
+
+ tmp_ctx = talloc_new(ac);
+ realm = samdb_dn_to_dns_domain(tmp_ctx, base_dn);
+ if (realm == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb);
+ }
+
+ if (real_upn != NULL) {
+ /*
+ * note we take the last @ in the upn because the first (i.e.
+ * sAMAccountName equivalent) part can contain @.
+ *
+ * It is also OK (per Windows) for a UPN to have zero @s.
+ */
+ char *at = NULL;
+ char *upn_realm = NULL;
+ implied_sam = talloc_strdup(tmp_ctx, real_upn);
+ if (implied_sam == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(ac->module);
+ }
+
+ at = strrchr(implied_sam, '@');
+ if (at == NULL) {
+ /*
+ * there is no @ in this UPN, so we treat the whole
+ * thing as a sAMAccountName for the purposes of a
+ * clash.
+ */
+ DBG_INFO("samldb: userPrincipalName '%s' contains "
+ "no '@' character\n", implied_sam);
+ } else {
+ /*
+ * Now, this upn only implies a sAMAccountName if the
+ * realm is our realm. So we need to compare the tail
+ * of the upn to the realm.
+ */
+ *at = '\0';
+ upn_realm = at + 1;
+ if (strcasecmp(upn_realm, realm) != 0) {
+ /* implied_sam is not the implied
+ * sAMAccountName after all, because it is
+ * from a different realm. */
+ TALLOC_FREE(implied_sam);
+ }
+ }
+ }
+
+ if (real_sam != NULL) {
+ implied_upn = talloc_asprintf(tmp_ctx, "%s@%s",
+ real_sam, realm);
+ if (implied_upn == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(ac->module);
+ }
+ }
+
+ /*
+ * Now we have all of the actual and implied names, in which to search
+ * for conflicts.
+ */
+ if (real_sam != NULL) {
+ ret = samldb_sam_account_upn_clash_sub_search(
+ ac, tmp_ctx, base_dn, "sAMAccountName",
+ real_sam, "");
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ ret = samaccountname_bad_chars_check(ac, real_sam);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+ if (implied_upn != NULL) {
+ ret = samldb_sam_account_upn_clash_sub_search(
+ ac, tmp_ctx, base_dn, "userPrincipalName", implied_upn,
+ "(implied by sAMAccountName)");
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+ if (real_upn != NULL) {
+ ret = samldb_sam_account_upn_clash_sub_search(
+ ac, tmp_ctx, base_dn, "userPrincipalName",
+ real_upn, "");
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+ if (implied_sam != NULL) {
+ ret = samldb_sam_account_upn_clash_sub_search(
+ ac, tmp_ctx, base_dn, "sAMAccountName", implied_sam,
+ "(implied by userPrincipalName)");
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+
+/* This is run during an add or modify */
+static int samldb_sam_accountname_valid_check(struct samldb_ctx *ac)
+{
+ int ret = 0;
+ bool is_admin;
+ struct security_token *user_token = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct ldb_message_element *el = NULL;
+
+ ret = dsdb_get_expected_new_values(ac,
+ ac->msg,
+ "samAccountName",
+ &el,
+ ac->req->operation);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (el == NULL || el->num_values == 0) {
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: 'samAccountName' can't be deleted/empty!",
+ W_ERROR_V(WERR_DS_ILLEGAL_MOD_OPERATION));
+ if (ac->req->operation == LDB_ADD) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ } else {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+
+ ret = samldb_unique_attr_check(ac, "samAccountName", NULL,
+ ldb_get_default_basedn(
+ ldb_module_get_ctx(ac->module)));
+
+ /*
+ * Error code munging to try and match what must be some quite
+ * strange code-paths in Windows
+ */
+ if (ret == LDB_ERR_CONSTRAINT_VIOLATION
+ && ac->req->operation == LDB_MODIFY) {
+ ret = LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+ } else if (ret == LDB_ERR_OBJECT_CLASS_VIOLATION) {
+ ret = LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = samldb_sam_account_upn_clash(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (!ac->need_trailing_dollar) {
+ return LDB_SUCCESS;
+ }
+
+ /* This does not permit a single $ */
+ if (el->values[0].length < 2) {
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: 'samAccountName' "
+ "can't just be one character!",
+ W_ERROR_V(WERR_DS_ILLEGAL_MOD_OPERATION));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ user_token = acl_user_token(ac->module);
+ if (user_token == NULL) {
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+
+ is_admin
+ = security_token_has_builtin_administrators(user_token);
+
+ if (is_admin) {
+ /*
+ * Administrators are allowed to select strange names.
+ * This is poor practice but not prevented.
+ */
+ return false;
+ }
+
+ if (el->values[0].data[el->values[0].length - 1] != '$') {
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: 'samAccountName' "
+ "must have a trailing $!",
+ W_ERROR_V(WERR_DS_ILLEGAL_MOD_OPERATION));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (el->values[0].data[el->values[0].length - 2] == '$') {
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: 'samAccountName' "
+ "must not have a double trailing $!",
+ W_ERROR_V(WERR_DS_ILLEGAL_MOD_OPERATION));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ return ret;
+}
+
+static int samldb_schema_attributeid_valid_check(struct samldb_ctx *ac)
+{
+ int ret = samldb_unique_attr_check(ac, "attributeID", "governsID",
+ ldb_get_schema_basedn(
+ ldb_module_get_ctx(ac->module)));
+ if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
+ ret = LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ return ret;
+}
+
+static int samldb_schema_governsid_valid_check(struct samldb_ctx *ac)
+{
+ int ret = samldb_unique_attr_check(ac, "governsID", "attributeID",
+ ldb_get_schema_basedn(
+ ldb_module_get_ctx(ac->module)));
+ if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
+ ret = LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ return ret;
+}
+
+static int samldb_schema_ldapdisplayname_valid_check(struct samldb_ctx *ac)
+{
+ int ret = samldb_unique_attr_check(ac, "lDAPDisplayName", NULL,
+ ldb_get_schema_basedn(
+ ldb_module_get_ctx(ac->module)));
+ if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
+ ret = LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ return ret;
+}
+
+static int samldb_check_linkid_used(struct samldb_ctx *ac,
+ struct dsdb_schema *schema,
+ struct ldb_dn *schema_dn,
+ struct ldb_context *ldb,
+ int32_t linkID,
+ bool *found)
+{
+ int ret;
+ struct ldb_result *ldb_res;
+
+ if (dsdb_attribute_by_linkID(schema, linkID)) {
+ *found = true;
+ return LDB_SUCCESS;
+ }
+
+ ret = dsdb_module_search(ac->module, ac,
+ &ldb_res,
+ schema_dn, LDB_SCOPE_ONELEVEL, NULL,
+ DSDB_FLAG_NEXT_MODULE,
+ ac->req,
+ "(linkID=%d)", linkID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ __location__": Searching for linkID=%d failed - %s\n",
+ linkID,
+ ldb_errstring(ldb));
+ return ldb_operr(ldb);
+ }
+
+ *found = (ldb_res->count != 0);
+ talloc_free(ldb_res);
+
+ return LDB_SUCCESS;
+}
+
+/* Find the next open forward linkID in the schema. */
+static int samldb_generate_next_linkid(struct samldb_ctx *ac,
+ struct dsdb_schema *schema,
+ int32_t *next_linkID)
+{
+ int ret;
+ struct ldb_context *ldb;
+ struct ldb_dn *schema_dn;
+ bool linkID_used = true;
+
+ /*
+ * Windows starts at about 0xB0000000 in order to stop potential
+ * collisions with future additions to the schema. We pass this
+ * around as a signed int sometimes, but this should be sufficient.
+ */
+ *next_linkID = 0x40000000;
+
+ ldb = ldb_module_get_ctx(ac->module);
+ schema_dn = ldb_get_schema_basedn(ldb);
+
+ while (linkID_used) {
+ *next_linkID += 2;
+ ret = samldb_check_linkid_used(ac, schema,
+ schema_dn, ldb,
+ *next_linkID, &linkID_used);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_schema_add_handle_linkid(struct samldb_ctx *ac)
+{
+ int ret;
+ bool ok, found = false;
+ struct ldb_message_element *el;
+ const char *enc_str;
+ const struct dsdb_attribute *attr;
+ struct ldb_context *ldb;
+ struct ldb_dn *schema_dn;
+ struct dsdb_schema *schema;
+ int32_t new_linkID = 0;
+
+ ldb = ldb_module_get_ctx(ac->module);
+ schema = dsdb_get_schema(ldb, ac);
+ schema_dn = ldb_get_schema_basedn(ldb);
+
+ ret = dsdb_get_expected_new_values(ac,
+ ac->msg,
+ "linkID",
+ &el,
+ ac->req->operation);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (el == NULL || el->num_values == 0) {
+ return LDB_SUCCESS;
+ }
+
+ enc_str = ldb_binary_encode(ac, el->values[0]);
+ if (enc_str == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ ok = (strcmp(enc_str, "0") == 0);
+ if (ok) {
+ return LDB_SUCCESS;
+ }
+
+ /*
+ * This OID indicates that the caller wants the linkID
+ * to be automatically generated. We therefore assign
+ * it the next open linkID.
+ */
+ ok = (strcmp(enc_str, "1.2.840.113556.1.2.50") == 0);
+ if (ok) {
+ ret = samldb_generate_next_linkid(ac, schema, &new_linkID);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ldb_msg_remove_element(ac->msg, el);
+ ret = samdb_msg_add_int(ldb, ac->msg, ac->msg, "linkID",
+ new_linkID);
+ return ret;
+ }
+
+ /*
+ * Using either the attributeID or lDAPDisplayName of
+ * another attribute in the linkID field indicates that
+ * we should make this the backlink of that attribute.
+ */
+ attr = dsdb_attribute_by_attributeID_oid(schema, enc_str);
+ if (attr == NULL) {
+ attr = dsdb_attribute_by_lDAPDisplayName(schema, enc_str);
+ }
+
+ if (attr != NULL) {
+ /*
+ * The attribute we're adding this as a backlink of must
+ * be a forward link.
+ */
+ if (attr->linkID % 2 != 0) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ new_linkID = attr->linkID + 1;
+
+ /* Make sure that this backlink doesn't already exist. */
+ ret = samldb_check_linkid_used(ac, schema,
+ schema_dn, ldb,
+ new_linkID, &found);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (found) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ ldb_msg_remove_element(ac->msg, el);
+ ret = samdb_msg_add_int(ldb, ac->msg, ac->msg, "linkID",
+ new_linkID);
+ return ret;
+ }
+
+ schema_dn = ldb_get_schema_basedn(ldb_module_get_ctx(ac->module));
+ ret = samldb_unique_attr_check(ac, "linkID", NULL, schema_dn);
+ if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ } else {
+ return ret;
+ }
+}
+
+static int samldb_check_mapiid_used(struct samldb_ctx *ac,
+ struct dsdb_schema *schema,
+ struct ldb_dn *schema_dn,
+ struct ldb_context *ldb,
+ int32_t mapiid,
+ bool *found)
+{
+ int ret;
+ struct ldb_result *ldb_res;
+
+ ret = dsdb_module_search(ac->module, ac,
+ &ldb_res,
+ schema_dn, LDB_SCOPE_ONELEVEL, NULL,
+ DSDB_FLAG_NEXT_MODULE,
+ ac->req,
+ "(mAPIID=%d)", mapiid);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ __location__": Searching for mAPIID=%d failed - %s\n",
+ mapiid,
+ ldb_errstring(ldb));
+ return ldb_operr(ldb);
+ }
+
+ *found = (ldb_res->count != 0);
+ talloc_free(ldb_res);
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_generate_next_mapiid(struct samldb_ctx *ac,
+ struct dsdb_schema *schema,
+ int32_t *next_mapiid)
+{
+ int ret;
+ struct ldb_context *ldb;
+ struct ldb_dn *schema_dn;
+ bool mapiid_used = true;
+
+ /* Windows' generation seems to start about here */
+ *next_mapiid = 60000;
+
+ ldb = ldb_module_get_ctx(ac->module);
+ schema_dn = ldb_get_schema_basedn(ldb);
+
+ while (mapiid_used) {
+ *next_mapiid += 1;
+ ret = samldb_check_mapiid_used(ac, schema,
+ schema_dn, ldb,
+ *next_mapiid, &mapiid_used);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_schema_add_handle_mapiid(struct samldb_ctx *ac)
+{
+ int ret;
+ bool ok;
+ struct ldb_message_element *el;
+ const char *enc_str;
+ struct ldb_context *ldb;
+ struct ldb_dn *schema_dn;
+ struct dsdb_schema *schema;
+ int32_t new_mapiid = 0;
+
+ /*
+ * The mAPIID of a new attribute should be automatically generated
+ * if a specific OID is put as the mAPIID, as according to
+ * [MS-ADTS] 3.1.1.2.3.2.
+ */
+
+ ldb = ldb_module_get_ctx(ac->module);
+ schema = dsdb_get_schema(ldb, ac);
+ schema_dn = ldb_get_schema_basedn(ldb);
+
+ ret = dsdb_get_expected_new_values(ac,
+ ac->msg,
+ "mAPIID",
+ &el,
+ ac->req->operation);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (el == NULL || el->num_values == 0) {
+ return LDB_SUCCESS;
+ }
+
+ enc_str = ldb_binary_encode(ac, el->values[0]);
+ if (enc_str == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ ok = (strcmp(enc_str, "1.2.840.113556.1.2.49") == 0);
+ if (ok) {
+ ret = samldb_generate_next_mapiid(ac, schema,
+ &new_mapiid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ldb_msg_remove_element(ac->msg, el);
+ ret = samdb_msg_add_int(ldb, ac->msg, ac->msg,
+ "mAPIID", new_mapiid);
+ return ret;
+ }
+
+ schema_dn = ldb_get_schema_basedn(ldb_module_get_ctx(ac->module));
+ ret = samldb_unique_attr_check(ac, "mAPIID", NULL, schema_dn);
+ if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ } else {
+ return ret;
+ }
+}
+
+/* sAMAccountName handling */
+static int samldb_generate_sAMAccountName(struct samldb_ctx *ac,
+ struct ldb_message *msg)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ char *name;
+
+ /*
+ * This is currently a Samba-only behaviour, to add a trailing
+ * $ even for the generated accounts.
+ */
+
+ if (ac->need_trailing_dollar) {
+ /* Format: $000000-00000000000$ */
+ name = talloc_asprintf(msg, "$%.6X-%.6X%.5X$",
+ (unsigned int)generate_random(),
+ (unsigned int)generate_random(),
+ (unsigned int)generate_random());
+ } else {
+ /* Format: $000000-000000000000 */
+
+ name = talloc_asprintf(msg, "$%.6X-%.6X%.6X",
+ (unsigned int)generate_random(),
+ (unsigned int)generate_random(),
+ (unsigned int)generate_random());
+ }
+ if (name == NULL) {
+ return ldb_oom(ldb);
+ }
+ return ldb_msg_add_steal_string(msg, "sAMAccountName", name);
+}
+
+static int samldb_check_sAMAccountName(struct samldb_ctx *ac)
+{
+ int ret;
+
+ if (ldb_msg_find_element(ac->msg, "sAMAccountName") == NULL) {
+ ret = samldb_generate_sAMAccountName(ac, ac->msg);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ ret = samldb_sam_accountname_valid_check(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return samldb_next_step(ac);
+}
+
+
+static bool samldb_msg_add_sid(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, 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 SID using our RID Set */
+static int samldb_allocate_sid(struct samldb_ctx *ac)
+{
+ uint32_t rid;
+ struct dom_sid *sid;
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ int ret;
+
+ ret = ridalloc_allocate_rid(ac->module, &rid, ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ sid = dom_sid_add_rid(ac, samdb_domain_sid(ldb), rid);
+ if (sid == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ if ( ! samldb_msg_add_sid(ac->msg, "objectSid", sid)) {
+ return ldb_operr(ldb);
+ }
+
+ return samldb_next_step(ac);
+}
+
+/*
+ see if a krbtgt_number is available
+ */
+static bool samldb_krbtgtnumber_available(struct samldb_ctx *ac,
+ uint32_t krbtgt_number)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(ac);
+ struct ldb_result *res;
+ const char * const no_attrs[] = { NULL };
+ int ret;
+
+ ret = dsdb_module_search(ac->module, tmp_ctx, &res,
+ ldb_get_default_basedn(ldb_module_get_ctx(ac->module)),
+ LDB_SCOPE_SUBTREE, no_attrs,
+ DSDB_FLAG_NEXT_MODULE,
+ ac->req,
+ "(msDS-SecondaryKrbTgtNumber=%u)",
+ krbtgt_number);
+ if (ret == LDB_SUCCESS && res->count == 0) {
+ talloc_free(tmp_ctx);
+ return true;
+ }
+ talloc_free(tmp_ctx);
+ return false;
+}
+
+/* special handling for add in RODC join */
+static int samldb_rodc_add(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ uint32_t krbtgt_number, i_start, i;
+ int ret;
+ struct ldb_val newpass_utf16;
+
+ /* find a unused msDS-SecondaryKrbTgtNumber */
+ i_start = generate_random() & 0xFFFF;
+ if (i_start == 0) {
+ i_start = 1;
+ }
+
+ for (i=i_start; i<=0xFFFF; i++) {
+ if (samldb_krbtgtnumber_available(ac, i)) {
+ krbtgt_number = i;
+ goto found;
+ }
+ }
+ for (i=1; i<i_start; i++) {
+ if (samldb_krbtgtnumber_available(ac, i)) {
+ krbtgt_number = i;
+ goto found;
+ }
+ }
+
+ ldb_asprintf_errstring(ldb,
+ "%08X: Unable to find available msDS-SecondaryKrbTgtNumber",
+ W_ERROR_V(WERR_NO_SYSTEM_RESOURCES));
+ return LDB_ERR_OTHER;
+
+found:
+
+ ldb_msg_remove_attr(ac->msg, "msDS-SecondaryKrbTgtNumber");
+ ret = samdb_msg_append_uint(ldb, ac->msg, ac->msg,
+ "msDS-SecondaryKrbTgtNumber", krbtgt_number,
+ LDB_FLAG_INTERNAL_DISABLE_VALIDATION);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ ret = ldb_msg_add_fmt(ac->msg, "sAMAccountName", "krbtgt_%u",
+ krbtgt_number);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ newpass_utf16 = data_blob_talloc_zero(ac->module, 256);
+ if (newpass_utf16.data == NULL) {
+ return ldb_oom(ldb);
+ }
+ /*
+ * Note that the password_hash module will ignore
+ * this value and use it's own generate_secret_buffer()
+ * that's why we can just use generate_random_buffer()
+ * here.
+ */
+ generate_random_buffer(newpass_utf16.data, newpass_utf16.length);
+ ret = ldb_msg_add_steal_value(ac->msg, "clearTextPassword", &newpass_utf16);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ return samldb_next_step(ac);
+}
+
+static int samldb_find_for_defaultObjectCategory(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct ldb_result *res;
+ const char * const no_attrs[] = { NULL };
+ int ret;
+
+ ac->res_dn = NULL;
+
+ ret = dsdb_module_search(ac->module, ac, &res,
+ ac->dn, LDB_SCOPE_BASE, no_attrs,
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT
+ | DSDB_FLAG_NEXT_MODULE,
+ ac->req,
+ "(objectClass=classSchema)");
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ /* Don't be pricky when the DN doesn't exist if we have the */
+ /* RELAX control specified */
+ if (ldb_request_get_control(ac->req,
+ LDB_CONTROL_RELAX_OID) == NULL) {
+ ldb_set_errstring(ldb,
+ "samldb_find_defaultObjectCategory: "
+ "Invalid DN for 'defaultObjectCategory'!");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+ if ((ret != LDB_ERR_NO_SUCH_OBJECT) && (ret != LDB_SUCCESS)) {
+ return ret;
+ }
+
+ if (ret == LDB_SUCCESS) {
+ /* ensure the defaultObjectCategory has a full GUID */
+ struct ldb_message *m;
+ m = ldb_msg_new(ac->msg);
+ if (m == NULL) {
+ return ldb_oom(ldb);
+ }
+ m->dn = ac->msg->dn;
+ if (ldb_msg_add_string(m, "defaultObjectCategory",
+ ldb_dn_get_extended_linearized(m, res->msgs[0]->dn, 1)) !=
+ LDB_SUCCESS) {
+ return ldb_oom(ldb);
+ }
+ m->elements[0].flags = LDB_FLAG_MOD_REPLACE;
+
+ ret = dsdb_module_modify(ac->module, m,
+ DSDB_FLAG_NEXT_MODULE,
+ ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+
+ ac->res_dn = ac->dn;
+
+ return samldb_next_step(ac);
+}
+
+/**
+ * msDS-IntId attributeSchema attribute handling
+ * during LDB_ADD request processing
+ */
+static int samldb_add_handle_msDS_IntId(struct samldb_ctx *ac)
+{
+ int ret;
+ bool id_exists;
+ uint32_t msds_intid;
+ int32_t system_flags;
+ struct ldb_context *ldb;
+ struct ldb_result *ldb_res;
+ struct ldb_dn *schema_dn;
+ struct samldb_msds_intid_persistant *msds_intid_struct;
+ struct dsdb_schema *schema;
+
+ ldb = ldb_module_get_ctx(ac->module);
+ schema_dn = ldb_get_schema_basedn(ldb);
+
+ /* replicated update should always go through */
+ if (ldb_request_get_control(ac->req,
+ DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
+ return LDB_SUCCESS;
+ }
+
+ /* msDS-IntId is handled by system and should never be
+ * passed by clients */
+ if (ldb_msg_find_element(ac->msg, "msDS-IntId")) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* do not generate msDS-IntId if Relax control is passed */
+ if (ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) {
+ return LDB_SUCCESS;
+ }
+
+ /* check Functional Level */
+ if (dsdb_functional_level(ldb) < DS_DOMAIN_FUNCTION_2003) {
+ return LDB_SUCCESS;
+ }
+
+ /* check systemFlags for SCHEMA_BASE_OBJECT flag */
+ system_flags = ldb_msg_find_attr_as_int(ac->msg, "systemFlags", 0);
+ if (system_flags & SYSTEM_FLAG_SCHEMA_BASE_OBJECT) {
+ return LDB_SUCCESS;
+ }
+ schema = dsdb_get_schema(ldb, NULL);
+ if (!schema) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "samldb_schema_info_update: no dsdb_schema loaded");
+ DEBUG(0,(__location__ ": %s\n", ldb_errstring(ldb)));
+ return ldb_operr(ldb);
+ }
+
+ msds_intid_struct = (struct samldb_msds_intid_persistant*) ldb_get_opaque(ldb, SAMLDB_MSDS_INTID_OPAQUE);
+ if (!msds_intid_struct) {
+ msds_intid_struct = talloc(ldb, struct samldb_msds_intid_persistant);
+ /* Generate new value for msDs-IntId
+ * Value should be in 0x80000000..0xBFFFFFFF range */
+ msds_intid = generate_random() % 0X3FFFFFFF;
+ msds_intid += 0x80000000;
+ msds_intid_struct->msds_intid = msds_intid;
+ DEBUG(2, ("No samldb_msds_intid_persistant struct, allocating a new one\n"));
+ } else {
+ msds_intid = msds_intid_struct->msds_intid;
+ }
+
+ /* probe id values until unique one is found */
+ do {
+ msds_intid++;
+ if (msds_intid > 0xBFFFFFFF) {
+ msds_intid = 0x80000001;
+ }
+ /*
+ * We search in the schema if we have already this
+ * intid (using dsdb_attribute_by_attributeID_id
+ * because in the range 0x80000000 0xBFFFFFFFF,
+ * attributeID is a DSDB_ATTID_TYPE_INTID).
+ *
+ * If so generate another random value.
+ *
+ * We have to check the DB in case someone else has
+ * modified the database while we are doing our
+ * changes too (this case should be very bery rare) in
+ * order to be sure.
+ */
+ if (dsdb_attribute_by_attributeID_id(schema, msds_intid)) {
+ id_exists = true;
+ msds_intid = generate_random() % 0X3FFFFFFF;
+ msds_intid += 0x80000000;
+ continue;
+ }
+
+
+ ret = dsdb_module_search(ac->module, ac,
+ &ldb_res,
+ schema_dn, LDB_SCOPE_ONELEVEL, NULL,
+ DSDB_FLAG_NEXT_MODULE,
+ ac->req,
+ "(msDS-IntId=%d)", msds_intid);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ __location__": Searching for msDS-IntId=%d failed - %s\n",
+ msds_intid,
+ ldb_errstring(ldb));
+ return ldb_operr(ldb);
+ }
+ id_exists = (ldb_res->count > 0);
+ talloc_free(ldb_res);
+
+ } while(id_exists);
+ msds_intid_struct->msds_intid = msds_intid;
+ ldb_set_opaque(ldb, SAMLDB_MSDS_INTID_OPAQUE, msds_intid_struct);
+
+ return samdb_msg_add_int(ldb, ac->msg, ac->msg, "msDS-IntId",
+ msds_intid);
+}
+
+
+/*
+ * samldb_add_entry (async)
+ */
+
+static int samldb_add_entry_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct samldb_ctx *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct samldb_ctx);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ares->type == LDB_REPLY_REFERRAL) {
+ return ldb_module_send_referral(ac->req, ares->referral);
+ }
+
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+ if (ares->type != LDB_REPLY_DONE) {
+ ldb_asprintf_errstring(ldb, "Invalid LDB reply type %d", ares->type);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ /* The caller may wish to get controls back from the add */
+ ac->ares = talloc_steal(ac, ares);
+
+ ret = samldb_next_step(ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ return ret;
+}
+
+static int samldb_add_entry(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb;
+ struct ldb_request *req;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ ret = ldb_build_add_req(&req, ldb, ac,
+ ac->msg,
+ ac->req->controls,
+ ac, samldb_add_entry_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ac->module, req);
+}
+
+/*
+ * return true if msg carries an attributeSchema that is intended to be RODC
+ * filtered but is also a system-critical attribute.
+ */
+static bool check_rodc_critical_attribute(struct ldb_message *msg)
+{
+ uint32_t schemaFlagsEx, searchFlags, rodc_filtered_flags;
+
+ schemaFlagsEx = ldb_msg_find_attr_as_uint(msg, "schemaFlagsEx", 0);
+ searchFlags = ldb_msg_find_attr_as_uint(msg, "searchFlags", 0);
+ rodc_filtered_flags = (SEARCH_FLAG_RODC_ATTRIBUTE
+ | SEARCH_FLAG_CONFIDENTIAL);
+
+ if ((schemaFlagsEx & SCHEMA_FLAG_ATTR_IS_CRITICAL) &&
+ ((searchFlags & rodc_filtered_flags) == rodc_filtered_flags)) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+static int samldb_fill_object(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ int ret;
+
+ /* Add information for the different account types */
+ switch(ac->type) {
+ case SAMLDB_TYPE_USER: {
+ struct ldb_control *rodc_control = ldb_request_get_control(ac->req,
+ LDB_CONTROL_RODC_DCPROMO_OID);
+ if (rodc_control != NULL) {
+ /* see [MS-ADTS] 3.1.1.3.4.1.23 LDAP_SERVER_RODC_DCPROMO_OID */
+ rodc_control->critical = false;
+ ret = samldb_add_step(ac, samldb_rodc_add);
+ if (ret != LDB_SUCCESS) return ret;
+ }
+
+ /* check if we have a valid sAMAccountName */
+ ret = samldb_add_step(ac, samldb_check_sAMAccountName);
+ if (ret != LDB_SUCCESS) return ret;
+
+ ret = samldb_add_step(ac, samldb_add_entry);
+ if (ret != LDB_SUCCESS) return ret;
+ break;
+ }
+
+ case SAMLDB_TYPE_GROUP: {
+ /* check if we have a valid sAMAccountName */
+ ret = samldb_add_step(ac, samldb_check_sAMAccountName);
+ if (ret != LDB_SUCCESS) return ret;
+
+ ret = samldb_add_step(ac, samldb_add_entry);
+ if (ret != LDB_SUCCESS) return ret;
+ break;
+ }
+
+ case SAMLDB_TYPE_CLASS: {
+ const char *lDAPDisplayName = NULL;
+ const struct ldb_val *rdn_value, *def_obj_cat_val;
+ unsigned int v = ldb_msg_find_attr_as_uint(ac->msg, "objectClassCategory", -2);
+
+ /* As discussed with Microsoft through dochelp in April 2012 this is the behavior of windows*/
+ if (!ldb_msg_find_element(ac->msg, "subClassOf")) {
+ ret = ldb_msg_add_string(ac->msg, "subClassOf", "top");
+ if (ret != LDB_SUCCESS) return ret;
+ }
+
+ ret = samdb_find_or_add_attribute(ldb, ac->msg,
+ "rdnAttId", "cn");
+ if (ret != LDB_SUCCESS) return ret;
+
+ /* do not allow one to mark an attributeSchema as RODC filtered if it
+ * is system-critical */
+ if (check_rodc_critical_attribute(ac->msg)) {
+ ldb_asprintf_errstring(ldb, "Refusing schema add of %s - cannot combine critical class with RODC filtering",
+ ldb_dn_get_linearized(ac->msg->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ rdn_value = ldb_dn_get_rdn_val(ac->msg->dn);
+ if (rdn_value == NULL) {
+ return ldb_operr(ldb);
+ }
+ if (!ldb_msg_find_element(ac->msg, "lDAPDisplayName")) {
+ /* the RDN has prefix "CN" */
+ ret = ldb_msg_add_string(ac->msg, "lDAPDisplayName",
+ samdb_cn_to_lDAPDisplayName(ac->msg,
+ (const char *) rdn_value->data));
+ if (ret != LDB_SUCCESS) {
+ ldb_oom(ldb);
+ return ret;
+ }
+ }
+
+ lDAPDisplayName = ldb_msg_find_attr_as_string(ac->msg,
+ "lDAPDisplayName",
+ NULL);
+ ret = ldb_valid_attr_name(lDAPDisplayName);
+ if (ret != 1 ||
+ lDAPDisplayName[0] == '*' ||
+ lDAPDisplayName[0] == '@')
+ {
+ return dsdb_module_werror(ac->module,
+ LDB_ERR_UNWILLING_TO_PERFORM,
+ WERR_DS_INVALID_LDAP_DISPLAY_NAME,
+ "lDAPDisplayName is invalid");
+ }
+
+ if (!ldb_msg_find_element(ac->msg, "schemaIDGUID")) {
+ struct GUID guid;
+ /* a new GUID */
+ guid = GUID_random();
+ ret = dsdb_msg_add_guid(ac->msg, &guid, "schemaIDGUID");
+ if (ret != LDB_SUCCESS) {
+ ldb_oom(ldb);
+ return ret;
+ }
+ }
+
+ def_obj_cat_val = ldb_msg_find_ldb_val(ac->msg,
+ "defaultObjectCategory");
+ if (def_obj_cat_val != NULL) {
+ /* "defaultObjectCategory" has been set by the caller.
+ * Do some checks for consistency.
+ * NOTE: The real constraint check (that
+ * 'defaultObjectCategory' is the DN of the new
+ * objectclass or any parent of it) is still incomplete.
+ * For now we say that 'defaultObjectCategory' is valid
+ * if it exists and it is of objectclass "classSchema".
+ */
+ ac->dn = ldb_dn_from_ldb_val(ac, ldb, def_obj_cat_val);
+ if (ac->dn == NULL) {
+ ldb_set_errstring(ldb,
+ "Invalid DN for 'defaultObjectCategory'!");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ } else {
+ /* "defaultObjectCategory" has not been set by the
+ * caller. Use the entry DN for it. */
+ ac->dn = ac->msg->dn;
+
+ ret = ldb_msg_add_string(ac->msg, "defaultObjectCategory",
+ ldb_dn_alloc_linearized(ac->msg, ac->dn));
+ if (ret != LDB_SUCCESS) {
+ ldb_oom(ldb);
+ return ret;
+ }
+ }
+
+ ret = samldb_add_step(ac, samldb_add_entry);
+ if (ret != LDB_SUCCESS) return ret;
+
+ /* Now perform the checks for the 'defaultObjectCategory'. The
+ * lookup DN was already saved in "ac->dn" */
+ ret = samldb_add_step(ac, samldb_find_for_defaultObjectCategory);
+ if (ret != LDB_SUCCESS) return ret;
+
+ /* -2 is not a valid objectClassCategory so it means the attribute wasn't present */
+ if (v == -2) {
+ /* Windows 2003 does this*/
+ ret = samdb_msg_add_uint(ldb, ac->msg, ac->msg, "objectClassCategory", 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ break;
+ }
+
+ case SAMLDB_TYPE_ATTRIBUTE: {
+ const char *lDAPDisplayName = NULL;
+ const struct ldb_val *rdn_value;
+ struct ldb_message_element *el;
+ rdn_value = ldb_dn_get_rdn_val(ac->msg->dn);
+ if (rdn_value == NULL) {
+ return ldb_operr(ldb);
+ }
+ if (!ldb_msg_find_element(ac->msg, "lDAPDisplayName")) {
+ /* the RDN has prefix "CN" */
+ ret = ldb_msg_add_string(ac->msg, "lDAPDisplayName",
+ samdb_cn_to_lDAPDisplayName(ac->msg,
+ (const char *) rdn_value->data));
+ if (ret != LDB_SUCCESS) {
+ ldb_oom(ldb);
+ return ret;
+ }
+ }
+
+ lDAPDisplayName = ldb_msg_find_attr_as_string(ac->msg,
+ "lDAPDisplayName",
+ NULL);
+ ret = ldb_valid_attr_name(lDAPDisplayName);
+ if (ret != 1 ||
+ lDAPDisplayName[0] == '*' ||
+ lDAPDisplayName[0] == '@')
+ {
+ return dsdb_module_werror(ac->module,
+ LDB_ERR_UNWILLING_TO_PERFORM,
+ WERR_DS_INVALID_LDAP_DISPLAY_NAME,
+ "lDAPDisplayName is invalid");
+ }
+
+ /* do not allow one to mark an attributeSchema as RODC filtered if it
+ * is system-critical */
+ if (check_rodc_critical_attribute(ac->msg)) {
+ ldb_asprintf_errstring(ldb,
+ "samldb: refusing schema add of %s - cannot combine critical attribute with RODC filtering",
+ ldb_dn_get_linearized(ac->msg->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ ret = samdb_find_or_add_attribute(ldb, ac->msg,
+ "isSingleValued", "FALSE");
+ if (ret != LDB_SUCCESS) return ret;
+
+ if (!ldb_msg_find_element(ac->msg, "schemaIDGUID")) {
+ struct GUID guid;
+ /* a new GUID */
+ guid = GUID_random();
+ ret = dsdb_msg_add_guid(ac->msg, &guid, "schemaIDGUID");
+ if (ret != LDB_SUCCESS) {
+ ldb_oom(ldb);
+ return ret;
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "attributeSyntax");
+ if (el) {
+ /*
+ * No need to scream if there isn't as we have code later on
+ * that will take care of it.
+ */
+ const struct dsdb_syntax *syntax = find_syntax_map_by_ad_oid((const char *)el->values[0].data);
+ if (!syntax) {
+ DEBUG(9, ("Can't find dsdb_syntax object for attributeSyntax %s\n",
+ (const char *)el->values[0].data));
+ } else {
+ unsigned int v = ldb_msg_find_attr_as_uint(ac->msg, "oMSyntax", 0);
+ const struct ldb_val *val = ldb_msg_find_ldb_val(ac->msg, "oMObjectClass");
+
+ if (v == 0) {
+ ret = samdb_msg_add_uint(ldb, ac->msg, ac->msg, "oMSyntax", syntax->oMSyntax);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ if (!val) {
+ struct ldb_val val2 = ldb_val_dup(ldb, &syntax->oMObjectClass);
+ if (val2.length > 0) {
+ ret = ldb_msg_add_value(ac->msg, "oMObjectClass", &val2, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+ }
+ }
+
+ /* handle msDS-IntID attribute */
+ ret = samldb_add_handle_msDS_IntId(ac);
+ if (ret != LDB_SUCCESS) return ret;
+
+ ret = samldb_add_step(ac, samldb_add_entry);
+ if (ret != LDB_SUCCESS) return ret;
+ break;
+ }
+
+ default:
+ ldb_asprintf_errstring(ldb, "Invalid entry type!");
+ return LDB_ERR_OPERATIONS_ERROR;
+ break;
+ }
+
+ return samldb_first_step(ac);
+}
+
+static int samldb_fill_foreignSecurityPrincipal_object(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = NULL;
+ const struct ldb_val *rdn_value = NULL;
+ struct ldb_message_element *sid_el = NULL;
+ struct dom_sid *sid = NULL;
+ struct ldb_control *as_system = NULL;
+ struct ldb_control *provision = NULL;
+ bool allowed = false;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ as_system = ldb_request_get_control(ac->req, LDB_CONTROL_AS_SYSTEM_OID);
+ if (as_system != NULL) {
+ allowed = true;
+ }
+
+ provision = ldb_request_get_control(ac->req, LDB_CONTROL_PROVISION_OID);
+ if (provision != NULL) {
+ allowed = true;
+ }
+
+ sid_el = ldb_msg_find_element(ac->msg, "objectSid");
+
+ if (!allowed && sid_el == NULL) {
+ return dsdb_module_werror(ac->module,
+ LDB_ERR_OBJECT_CLASS_VIOLATION,
+ WERR_DS_MISSING_REQUIRED_ATT,
+ "objectSid missing on foreignSecurityPrincipal");
+ }
+
+ if (!allowed) {
+ return dsdb_module_werror(ac->module,
+ LDB_ERR_UNWILLING_TO_PERFORM,
+ WERR_DS_ILLEGAL_MOD_OPERATION,
+ "foreignSecurityPrincipal object not allowed");
+ }
+
+ if (sid_el != NULL) {
+ sid = samdb_result_dom_sid(ac->msg, ac->msg, "objectSid");
+ if (sid == NULL) {
+ ldb_set_errstring(ldb,
+ "samldb: invalid objectSid!");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+
+ if (sid == NULL) {
+ rdn_value = ldb_dn_get_rdn_val(ac->msg->dn);
+ if (rdn_value == NULL) {
+ return ldb_operr(ldb);
+ }
+ sid = dom_sid_parse_talloc(ac->msg,
+ (const char *)rdn_value->data);
+ if (sid == NULL) {
+ ldb_set_errstring(ldb,
+ "samldb: No valid SID found in ForeignSecurityPrincipal CN!");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if (! samldb_msg_add_sid(ac->msg, "objectSid", sid)) {
+ return ldb_operr(ldb);
+ }
+ }
+
+ /* finally proceed with adding the entry */
+ ret = samldb_add_step(ac, samldb_add_entry);
+ if (ret != LDB_SUCCESS) return ret;
+
+ return samldb_first_step(ac);
+}
+
+static int samldb_schema_info_update(struct samldb_ctx *ac)
+{
+ int ret;
+ struct ldb_context *ldb;
+ struct dsdb_schema *schema;
+
+ /* replicated update should always go through */
+ if (ldb_request_get_control(ac->req,
+ DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
+ return LDB_SUCCESS;
+ }
+
+ /* do not update schemaInfo during provisioning */
+ if (ldb_request_get_control(ac->req, LDB_CONTROL_PROVISION_OID)) {
+ return LDB_SUCCESS;
+ }
+
+ ldb = ldb_module_get_ctx(ac->module);
+ schema = dsdb_get_schema(ldb, NULL);
+ if (!schema) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "samldb_schema_info_update: no dsdb_schema loaded");
+ DEBUG(0,(__location__ ": %s\n", ldb_errstring(ldb)));
+ return ldb_operr(ldb);
+ }
+
+ ret = dsdb_module_schema_info_update(ac->module, schema,
+ DSDB_FLAG_NEXT_MODULE|
+ DSDB_FLAG_AS_SYSTEM,
+ ac->req);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "samldb_schema_info_update: dsdb_module_schema_info_update failed with %s",
+ ldb_errstring(ldb));
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_prim_group_tester(struct samldb_ctx *ac, uint32_t rid);
+static int samldb_check_user_account_control_rules(struct samldb_ctx *ac,
+ struct dom_sid *sid,
+ uint32_t req_uac,
+ uint32_t user_account_control,
+ uint32_t user_account_control_old,
+ bool is_computer_objectclass);
+
+/*
+ * "Objectclass" trigger (MS-SAMR 3.1.1.8.1)
+ *
+ * Has to be invoked on "add" operations on "user", "computer" and
+ * "group" objects.
+ * ac->msg contains the "add"
+ * ac->type contains the object type (main objectclass)
+ */
+static int samldb_objectclass_trigger(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ void *skip_allocate_sids = ldb_get_opaque(ldb,
+ "skip_allocate_sids");
+ struct ldb_message_element *el;
+ struct dom_sid *sid;
+ int ret;
+
+ /* make sure that "sAMAccountType" is not specified */
+ el = ldb_msg_find_element(ac->msg, "sAMAccountType");
+ if (el != NULL) {
+ ldb_set_errstring(ldb,
+ "samldb: sAMAccountType must not be specified!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Step 1: objectSid assignment */
+
+ /* Don't allow the objectSid to be changed. But beside the RELAX
+ * control we have also to guarantee that it can always be set with
+ * SYSTEM permissions. This is needed for the "samba3sam" backend. */
+ sid = samdb_result_dom_sid(ac, ac->msg, "objectSid");
+ if ((sid != NULL) && (!dsdb_module_am_system(ac->module)) &&
+ (ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) == NULL)) {
+ ldb_set_errstring(ldb,
+ "samldb: objectSid must not be specified!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* but generate a new SID when we do have an add operations */
+ if ((sid == NULL) && (ac->req->operation == LDB_ADD) && !skip_allocate_sids) {
+ ret = samldb_add_step(ac, samldb_allocate_sid);
+ if (ret != LDB_SUCCESS) return ret;
+ }
+
+ switch(ac->type) {
+ case SAMLDB_TYPE_USER: {
+ uint32_t raw_uac;
+ uint32_t user_account_control;
+ bool is_computer_objectclass;
+ bool uac_generated = false, uac_add_flags = false;
+ uint32_t default_user_account_control = UF_NORMAL_ACCOUNT;
+ /* Step 1.2: Default values */
+ ret = dsdb_user_obj_set_defaults(ldb, ac->msg, ac->req);
+ if (ret != LDB_SUCCESS) return ret;
+
+ is_computer_objectclass
+ = (samdb_find_attribute(ldb,
+ ac->msg,
+ "objectclass",
+ "computer")
+ != NULL);
+
+ if (is_computer_objectclass) {
+ default_user_account_control
+ = UF_WORKSTATION_TRUST_ACCOUNT;
+ }
+
+
+ /* On add operations we might need to generate a
+ * "userAccountControl" (if it isn't specified). */
+ el = ldb_msg_find_element(ac->msg, "userAccountControl");
+ if (el == NULL) {
+ ret = samdb_msg_set_uint(ldb, ac->msg, ac->msg,
+ "userAccountControl",
+ default_user_account_control);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ uac_generated = true;
+ uac_add_flags = true;
+ }
+
+ el = ldb_msg_find_element(ac->msg, "userAccountControl");
+ SMB_ASSERT(el != NULL);
+
+ /* Step 1.3: "userAccountControl" -> "sAMAccountType" mapping */
+ user_account_control = ldb_msg_find_attr_as_uint(ac->msg,
+ "userAccountControl",
+ 0);
+ raw_uac = user_account_control;
+ /*
+ * "userAccountControl" = 0 or missing one of
+ * the types means "UF_NORMAL_ACCOUNT"
+ * or "UF_WORKSTATION_TRUST_ACCOUNT" (if a computer).
+ * See MS-SAMR 3.1.1.8.10 point 8
+ */
+ if ((user_account_control & UF_ACCOUNT_TYPE_MASK) == 0) {
+ user_account_control
+ = default_user_account_control
+ | user_account_control;
+ uac_generated = true;
+ }
+
+ /*
+ * As per MS-SAMR 3.1.1.8.10 these flags have not to be set
+ */
+ if ((user_account_control & UF_LOCKOUT) != 0) {
+ user_account_control &= ~UF_LOCKOUT;
+ uac_generated = true;
+ }
+ if ((user_account_control & UF_PASSWORD_EXPIRED) != 0) {
+ user_account_control &= ~UF_PASSWORD_EXPIRED;
+ uac_generated = true;
+ }
+
+ ret = samldb_check_user_account_control_rules(ac, NULL,
+ raw_uac,
+ user_account_control,
+ 0,
+ is_computer_objectclass);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /*
+ * Require, for non-admin modifications, a trailing $
+ * for either objectclass=computer or a trust account
+ * type in userAccountControl
+ */
+ if ((user_account_control
+ & UF_TRUST_ACCOUNT_MASK) != 0) {
+ ac->need_trailing_dollar = true;
+ }
+
+ if (is_computer_objectclass) {
+ ac->need_trailing_dollar = true;
+ }
+
+ /* add "sAMAccountType" attribute */
+ ret = dsdb_user_obj_set_account_type(ldb, ac->msg, user_account_control, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* "isCriticalSystemObject" might be set */
+ if (user_account_control &
+ (UF_SERVER_TRUST_ACCOUNT | UF_PARTIAL_SECRETS_ACCOUNT)) {
+ ret = ldb_msg_add_string_flags(ac->msg, "isCriticalSystemObject",
+ "TRUE", LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ } else if (user_account_control & UF_WORKSTATION_TRUST_ACCOUNT) {
+ ret = ldb_msg_add_string_flags(ac->msg, "isCriticalSystemObject",
+ "FALSE", LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* Step 1.4: "userAccountControl" -> "primaryGroupID" mapping */
+ if (!ldb_msg_find_element(ac->msg, "primaryGroupID")) {
+ uint32_t rid;
+
+ ret = dsdb_user_obj_set_primary_group_id(ldb, ac->msg, user_account_control, &rid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ /*
+ * Older AD deployments don't know about the
+ * RODC group
+ */
+ if (rid == DOMAIN_RID_READONLY_DCS) {
+ ret = samldb_prim_group_tester(ac, rid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ /* Step 1.5: Add additional flags when needed */
+ /* Obviously this is done when the "userAccountControl"
+ * has been generated here (tested against Windows
+ * Server) */
+ if (uac_generated) {
+ if (uac_add_flags) {
+ user_account_control |= UF_ACCOUNTDISABLE;
+ user_account_control |= UF_PASSWD_NOTREQD;
+ }
+
+ ret = samdb_msg_set_uint(ldb, ac->msg, ac->msg,
+ "userAccountControl",
+ user_account_control);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ break;
+ }
+
+ case SAMLDB_TYPE_GROUP: {
+ const char *tempstr;
+
+ /* Step 2.2: Default values */
+ tempstr = talloc_asprintf(ac->msg, "%d",
+ GTYPE_SECURITY_GLOBAL_GROUP);
+ if (tempstr == NULL) return ldb_operr(ldb);
+ ret = samdb_find_or_add_attribute(ldb, ac->msg,
+ "groupType", tempstr);
+ if (ret != LDB_SUCCESS) return ret;
+
+ /* Step 2.3: "groupType" -> "sAMAccountType" */
+ el = ldb_msg_find_element(ac->msg, "groupType");
+ if (el != NULL) {
+ uint32_t group_type, account_type;
+
+ group_type = ldb_msg_find_attr_as_uint(ac->msg,
+ "groupType", 0);
+
+ /* The creation of builtin groups requires the
+ * RELAX control */
+ if (group_type == GTYPE_SECURITY_BUILTIN_LOCAL_GROUP) {
+ if (ldb_request_get_control(ac->req,
+ LDB_CONTROL_RELAX_OID) == NULL) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+
+ account_type = ds_gtype2atype(group_type);
+ if (account_type == 0) {
+ ldb_set_errstring(ldb, "samldb: Unrecognized account type!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ ret = samdb_msg_add_uint_flags(ldb, ac->msg, ac->msg,
+ "sAMAccountType",
+ account_type,
+ LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ break;
+ }
+
+ default:
+ ldb_asprintf_errstring(ldb,
+ "Invalid entry type!");
+ return LDB_ERR_OPERATIONS_ERROR;
+ break;
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * "Primary group ID" trigger (MS-SAMR 3.1.1.8.2)
+ *
+ * Has to be invoked on "add" and "modify" operations on "user" and "computer"
+ * objects.
+ * ac->msg contains the "add"/"modify" message
+ */
+
+static int samldb_prim_group_tester(struct samldb_ctx *ac, uint32_t rid)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct dom_sid *sid;
+ struct ldb_result *res;
+ int ret;
+ const char * const noattrs[] = { NULL };
+
+ sid = dom_sid_add_rid(ac, samdb_domain_sid(ldb), rid);
+ if (sid == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ ret = dsdb_module_search(ac->module, ac, &res,
+ ldb_get_default_basedn(ldb),
+ LDB_SCOPE_SUBTREE,
+ noattrs, DSDB_FLAG_NEXT_MODULE,
+ ac->req,
+ "(objectSid=%s)",
+ ldap_encode_ndr_dom_sid(ac, sid));
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (res->count != 1) {
+ talloc_free(res);
+ ldb_asprintf_errstring(ldb,
+ "Failed to find primary group with RID %u!",
+ rid);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ talloc_free(res);
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_prim_group_set(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ uint32_t rid;
+
+ rid = ldb_msg_find_attr_as_uint(ac->msg, "primaryGroupID", (uint32_t) -1);
+ if (rid == (uint32_t) -1) {
+ /* we aren't affected of any primary group set */
+ return LDB_SUCCESS;
+
+ } else if (!ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID)) {
+ ldb_set_errstring(ldb,
+ "The primary group isn't settable on add operations!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ return samldb_prim_group_tester(ac, rid);
+}
+
+static int samldb_prim_group_change(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ const char * const attrs[] = {
+ "primaryGroupID",
+ "memberOf",
+ "userAccountControl",
+ NULL };
+ struct ldb_result *res, *group_res;
+ struct ldb_message_element *el;
+ struct ldb_message *msg;
+ uint32_t search_flags =
+ DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_EXTENDED_DN;
+ uint32_t prev_rid, new_rid, uac;
+ struct dom_sid *prev_sid, *new_sid;
+ struct ldb_dn *prev_prim_group_dn, *new_prim_group_dn;
+ const char *new_prim_group_dn_ext_str = NULL;
+ struct ldb_dn *user_dn = NULL;
+ const char *user_dn_ext_str = NULL;
+ int ret;
+ const char * const noattrs[] = { NULL };
+
+ ret = dsdb_get_expected_new_values(ac,
+ ac->msg,
+ "primaryGroupID",
+ &el,
+ ac->req->operation);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (el == NULL) {
+ /* we are not affected */
+ return LDB_SUCCESS;
+ }
+
+ /* Fetch information from the existing object */
+
+ ret = dsdb_module_search_dn(ac->module, ac, &res, ac->msg->dn, attrs,
+ search_flags, ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ user_dn = res->msgs[0]->dn;
+ user_dn_ext_str = ldb_dn_get_extended_linearized(ac, user_dn, 1);
+ if (user_dn_ext_str == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ uac = ldb_msg_find_attr_as_uint(res->msgs[0], "userAccountControl", 0);
+
+ /* Finds out the DN of the old primary group */
+
+ prev_rid = ldb_msg_find_attr_as_uint(res->msgs[0], "primaryGroupID",
+ (uint32_t) -1);
+ if (prev_rid == (uint32_t) -1) {
+ /* User objects do always have a mandatory "primaryGroupID"
+ * attribute. If this doesn't exist then the object is of the
+ * wrong type. This is the exact Windows error code */
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+
+ prev_sid = dom_sid_add_rid(ac, samdb_domain_sid(ldb), prev_rid);
+ if (prev_sid == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* Finds out the DN of the new primary group
+ * Notice: in order to parse the primary group ID correctly we create
+ * a temporary message here. */
+
+ msg = ldb_msg_new(ac->msg);
+ if (msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ ret = ldb_msg_add(msg, el, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ new_rid = ldb_msg_find_attr_as_uint(msg, "primaryGroupID", (uint32_t) -1);
+ talloc_free(msg);
+ if (new_rid == (uint32_t) -1) {
+ /* we aren't affected of any primary group change */
+ return LDB_SUCCESS;
+ }
+
+ if (prev_rid == new_rid) {
+ return LDB_SUCCESS;
+ }
+
+ if ((uac & UF_SERVER_TRUST_ACCOUNT) && new_rid != DOMAIN_RID_DCS) {
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: UF_SERVER_TRUST_ACCOUNT requires "
+ "primaryGroupID=%u!",
+ W_ERROR_V(WERR_DS_CANT_MOD_PRIMARYGROUPID),
+ DOMAIN_RID_DCS);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ if ((uac & UF_PARTIAL_SECRETS_ACCOUNT) && new_rid != DOMAIN_RID_READONLY_DCS) {
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: UF_PARTIAL_SECRETS_ACCOUNT requires "
+ "primaryGroupID=%u!",
+ W_ERROR_V(WERR_DS_CANT_MOD_PRIMARYGROUPID),
+ DOMAIN_RID_READONLY_DCS);
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ ret = dsdb_module_search(ac->module, ac, &group_res,
+ ldb_get_default_basedn(ldb),
+ LDB_SCOPE_SUBTREE,
+ noattrs, search_flags,
+ ac->req,
+ "(objectSid=%s)",
+ ldap_encode_ndr_dom_sid(ac, prev_sid));
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (group_res->count != 1) {
+ return ldb_operr(ldb);
+ }
+ prev_prim_group_dn = group_res->msgs[0]->dn;
+
+ new_sid = dom_sid_add_rid(ac, samdb_domain_sid(ldb), new_rid);
+ if (new_sid == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ ret = dsdb_module_search(ac->module, ac, &group_res,
+ ldb_get_default_basedn(ldb),
+ LDB_SCOPE_SUBTREE,
+ noattrs, search_flags,
+ ac->req,
+ "(objectSid=%s)",
+ ldap_encode_ndr_dom_sid(ac, new_sid));
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (group_res->count != 1) {
+ /* Here we know if the specified new primary group candidate is
+ * valid or not. */
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ new_prim_group_dn = group_res->msgs[0]->dn;
+ new_prim_group_dn_ext_str = ldb_dn_get_extended_linearized(ac,
+ new_prim_group_dn, 1);
+ if (new_prim_group_dn_ext_str == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* We need to be already a normal member of the new primary
+ * group in order to be successful. */
+ el = samdb_find_attribute(ldb, res->msgs[0], "memberOf",
+ new_prim_group_dn_ext_str);
+ if (el == NULL) {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Remove the "member" attribute on the new primary group */
+ msg = ldb_msg_new(ac->msg);
+ if (msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ msg->dn = new_prim_group_dn;
+
+ ret = samdb_msg_add_delval(ldb, msg, msg, "member", user_dn_ext_str);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = dsdb_module_modify(ac->module, msg, DSDB_FLAG_NEXT_MODULE, ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_free(msg);
+
+ /* Add a "member" attribute for the previous primary group */
+ msg = ldb_msg_new(ac->msg);
+ if (msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ msg->dn = prev_prim_group_dn;
+
+ ret = samdb_msg_add_addval(ldb, msg, msg, "member", user_dn_ext_str);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = dsdb_module_modify(ac->module, msg, DSDB_FLAG_NEXT_MODULE, ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_free(msg);
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_prim_group_trigger(struct samldb_ctx *ac)
+{
+ int ret;
+
+ if (ac->req->operation == LDB_ADD) {
+ ret = samldb_prim_group_set(ac);
+ } else {
+ ret = samldb_prim_group_change(ac);
+ }
+
+ return ret;
+}
+
+static int samldb_check_user_account_control_invariants(struct samldb_ctx *ac,
+ uint32_t user_account_control)
+{
+ size_t i;
+ int ret = 0;
+ bool need_check = false;
+ const struct uac_to_guid {
+ uint32_t uac;
+ bool never;
+ uint32_t needs;
+ uint32_t not_with;
+ const char *error_string;
+ } map[] = {
+ {
+ .uac = UF_TEMP_DUPLICATE_ACCOUNT,
+ .never = true,
+ .error_string = "Updating the UF_TEMP_DUPLICATE_ACCOUNT flag is never allowed"
+ },
+ {
+ .uac = UF_PARTIAL_SECRETS_ACCOUNT,
+ .needs = UF_WORKSTATION_TRUST_ACCOUNT,
+ .error_string = "Setting UF_PARTIAL_SECRETS_ACCOUNT only permitted with UF_WORKSTATION_TRUST_ACCOUNT"
+ },
+ {
+ .uac = UF_TRUSTED_FOR_DELEGATION,
+ .not_with = UF_PARTIAL_SECRETS_ACCOUNT,
+ .error_string = "Setting UF_TRUSTED_FOR_DELEGATION not allowed with UF_PARTIAL_SECRETS_ACCOUNT"
+ },
+ {
+ .uac = UF_NORMAL_ACCOUNT,
+ .not_with = UF_ACCOUNT_TYPE_MASK & ~UF_NORMAL_ACCOUNT,
+ .error_string = "Setting more than one account type not permitted"
+ },
+ {
+ .uac = UF_WORKSTATION_TRUST_ACCOUNT,
+ .not_with = UF_ACCOUNT_TYPE_MASK & ~UF_WORKSTATION_TRUST_ACCOUNT,
+ .error_string = "Setting more than one account type not permitted"
+ },
+ {
+ .uac = UF_INTERDOMAIN_TRUST_ACCOUNT,
+ .not_with = UF_ACCOUNT_TYPE_MASK & ~UF_INTERDOMAIN_TRUST_ACCOUNT,
+ .error_string = "Setting more than one account type not permitted"
+ },
+ {
+ .uac = UF_SERVER_TRUST_ACCOUNT,
+ .not_with = UF_ACCOUNT_TYPE_MASK & ~UF_SERVER_TRUST_ACCOUNT,
+ .error_string = "Setting more than one account type not permitted"
+ },
+ {
+ .uac = UF_TRUSTED_FOR_DELEGATION,
+ .not_with = UF_PARTIAL_SECRETS_ACCOUNT,
+ .error_string = "Setting UF_TRUSTED_FOR_DELEGATION not allowed with UF_PARTIAL_SECRETS_ACCOUNT"
+ }
+ };
+
+ for (i = 0; i < ARRAY_SIZE(map); i++) {
+ if (user_account_control & map[i].uac) {
+ need_check = true;
+ break;
+ }
+ }
+ if (need_check == false) {
+ return LDB_SUCCESS;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(map); i++) {
+ uint32_t this_uac = user_account_control & map[i].uac;
+ if (this_uac != 0) {
+ if (map[i].never) {
+ ret = LDB_ERR_OTHER;
+ break;
+ } else if (map[i].needs != 0) {
+ if ((map[i].needs & user_account_control) == 0) {
+ ret = LDB_ERR_OTHER;
+ break;
+ }
+ } else if (map[i].not_with != 0) {
+ if ((map[i].not_with & user_account_control) != 0) {
+ ret = LDB_ERR_OTHER;
+ break;
+ }
+ }
+ }
+ }
+ if (ret != LDB_SUCCESS) {
+ switch (ac->req->operation) {
+ case LDB_ADD:
+ ldb_asprintf_errstring(ldb_module_get_ctx(ac->module),
+ "Failed to add %s: %s",
+ ldb_dn_get_linearized(ac->msg->dn),
+ map[i].error_string);
+ break;
+ case LDB_MODIFY:
+ ldb_asprintf_errstring(ldb_module_get_ctx(ac->module),
+ "Failed to modify %s: %s",
+ ldb_dn_get_linearized(ac->msg->dn),
+ map[i].error_string);
+ break;
+ default:
+ return ldb_module_operr(ac->module);
+ }
+ }
+ return ret;
+}
+
+/*
+ * It would be best if these rules apply, always, but for now they
+ * apply only to non-admins
+ */
+static int samldb_check_user_account_control_objectclass_invariants(
+ struct samldb_ctx *ac,
+ uint32_t user_account_control,
+ uint32_t user_account_control_old,
+ bool is_computer_objectclass)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+
+ uint32_t old_ufa = user_account_control_old & UF_ACCOUNT_TYPE_MASK;
+ uint32_t new_ufa = user_account_control & UF_ACCOUNT_TYPE_MASK;
+
+ uint32_t old_rodc = user_account_control_old & UF_PARTIAL_SECRETS_ACCOUNT;
+ uint32_t new_rodc = user_account_control & UF_PARTIAL_SECRETS_ACCOUNT;
+
+ bool is_admin;
+ struct security_token *user_token
+ = acl_user_token(ac->module);
+ if (user_token == NULL) {
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+
+ is_admin
+ = security_token_has_builtin_administrators(user_token);
+
+
+ /*
+ * We want to allow changes to (eg) disable an account
+ * that was created wrong, only checking the
+ * objectclass if the account type changes.
+ */
+ if (old_ufa == new_ufa && old_rodc == new_rodc) {
+ return LDB_SUCCESS;
+ }
+
+ switch (new_ufa) {
+ case UF_NORMAL_ACCOUNT:
+ if (is_computer_objectclass && !is_admin) {
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: UF_NORMAL_ACCOUNT "
+ "requires objectclass 'user' not 'computer'!",
+ W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ break;
+
+ case UF_INTERDOMAIN_TRUST_ACCOUNT:
+ if (is_computer_objectclass) {
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: UF_INTERDOMAIN_TRUST_ACCOUNT "
+ "requires objectclass 'user' not 'computer'!",
+ W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ break;
+
+ case UF_WORKSTATION_TRUST_ACCOUNT:
+ if (!is_computer_objectclass) {
+ /*
+ * Modify of a user account account into a
+ * workstation without objectclass computer
+ * as an admin is still permitted, but not
+ * to make an RODC
+ */
+ if (is_admin
+ && ac->req->operation == LDB_MODIFY
+ && new_rodc == 0) {
+ break;
+ }
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: UF_WORKSTATION_TRUST_ACCOUNT "
+ "requires objectclass 'computer'!",
+ W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ break;
+
+ case UF_SERVER_TRUST_ACCOUNT:
+ if (!is_computer_objectclass) {
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: UF_SERVER_TRUST_ACCOUNT "
+ "requires objectclass 'computer'!",
+ W_ERROR_V(WERR_DS_MACHINE_ACCOUNT_CREATED_PRENT4));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ break;
+
+ default:
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: invalid userAccountControl[0x%08X]",
+ W_ERROR_V(WERR_INVALID_PARAMETER),
+ user_account_control);
+ return LDB_ERR_OTHER;
+ }
+ return LDB_SUCCESS;
+}
+
+static int samldb_get_domain_secdesc_and_oc(struct samldb_ctx *ac,
+ struct security_descriptor **domain_sd,
+ const struct dsdb_class **objectclass)
+{
+ const char * const sd_attrs[] = {"ntSecurityDescriptor", "objectClass", NULL};
+ struct ldb_result *res;
+ struct ldb_dn *domain_dn = ldb_get_default_basedn(ldb_module_get_ctx(ac->module));
+ const struct dsdb_schema *schema = NULL;
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ int ret = dsdb_module_search_dn(ac->module, ac, &res,
+ domain_dn,
+ sd_attrs,
+ DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED,
+ ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (res->count != 1) {
+ return ldb_module_operr(ac->module);
+ }
+
+ schema = dsdb_get_schema(ldb, ac->req);
+ if (!schema) {
+ return ldb_module_operr(ac->module);;
+ }
+ *objectclass = dsdb_get_structural_oc_from_msg(schema, res->msgs[0]);
+ return dsdb_get_sd_from_ldb_message(ldb_module_get_ctx(ac->module),
+ ac, res->msgs[0], domain_sd);
+
+}
+
+/**
+ * Validate that the restriction in point 5 of MS-SAMR 3.1.1.8.10 userAccountControl is honoured
+ *
+ */
+static int samldb_check_user_account_control_acl(struct samldb_ctx *ac,
+ struct dom_sid *sid,
+ uint32_t user_account_control,
+ uint32_t user_account_control_old)
+{
+ size_t i;
+ int ret = 0;
+ bool need_acl_check = false;
+ struct security_token *user_token;
+ struct security_descriptor *domain_sd;
+ const struct dsdb_class *objectclass = NULL;
+ const struct uac_to_guid {
+ uint32_t uac;
+ uint32_t priv_to_change_from;
+ const char *oid;
+ const char *guid;
+ enum sec_privilege privilege;
+ bool delete_is_privileged;
+ bool admin_required;
+ const char *error_string;
+ } map[] = {
+ {
+ .uac = UF_PASSWD_NOTREQD,
+ .guid = GUID_DRS_UPDATE_PASSWORD_NOT_REQUIRED_BIT,
+ .error_string = "Adding the UF_PASSWD_NOTREQD bit in userAccountControl requires the Update-Password-Not-Required-Bit right that was not given on the Domain object"
+ },
+ {
+ .uac = UF_DONT_EXPIRE_PASSWD,
+ .guid = GUID_DRS_UNEXPIRE_PASSWORD,
+ .error_string = "Adding the UF_DONT_EXPIRE_PASSWD bit in userAccountControl requires the Unexpire-Password right that was not given on the Domain object"
+ },
+ {
+ .uac = UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED,
+ .guid = GUID_DRS_ENABLE_PER_USER_REVERSIBLY_ENCRYPTED_PASSWORD,
+ .error_string = "Adding the UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED bit in userAccountControl requires the Enable-Per-User-Reversibly-Encrypted-Password right that was not given on the Domain object"
+ },
+ {
+ .uac = UF_SERVER_TRUST_ACCOUNT,
+ .guid = GUID_DRS_DS_INSTALL_REPLICA,
+ .error_string = "Adding the UF_SERVER_TRUST_ACCOUNT bit in userAccountControl requires the DS-Install-Replica right that was not given on the Domain object"
+ },
+ {
+ .uac = UF_PARTIAL_SECRETS_ACCOUNT,
+ .guid = GUID_DRS_DS_INSTALL_REPLICA,
+ .error_string = "Adding the UF_PARTIAL_SECRETS_ACCOUNT bit in userAccountControl requires the DS-Install-Replica right that was not given on the Domain object"
+ },
+ {
+ .uac = UF_WORKSTATION_TRUST_ACCOUNT,
+ .priv_to_change_from = UF_NORMAL_ACCOUNT,
+ .error_string = "Swapping UF_NORMAL_ACCOUNT to UF_WORKSTATION_TRUST_ACCOUNT requires the user to be a member of the domain admins group"
+ },
+ {
+ .uac = UF_NORMAL_ACCOUNT,
+ .priv_to_change_from = UF_WORKSTATION_TRUST_ACCOUNT,
+ .error_string = "Swapping UF_WORKSTATION_TRUST_ACCOUNT to UF_NORMAL_ACCOUNT requires the user to be a member of the domain admins group"
+ },
+ {
+ .uac = UF_INTERDOMAIN_TRUST_ACCOUNT,
+ .oid = DSDB_CONTROL_PERMIT_INTERDOMAIN_TRUST_UAC_OID,
+ .error_string = "Updating the UF_INTERDOMAIN_TRUST_ACCOUNT bit in userAccountControl is not permitted over LDAP. This bit is restricted to the LSA CreateTrustedDomain interface",
+ .delete_is_privileged = true
+ },
+ {
+ .uac = UF_TRUSTED_FOR_DELEGATION,
+ .privilege = SEC_PRIV_ENABLE_DELEGATION,
+ .delete_is_privileged = true,
+ .error_string = "Updating the UF_TRUSTED_FOR_DELEGATION bit in userAccountControl is not permitted without the SeEnableDelegationPrivilege"
+ },
+ {
+ .uac = UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION,
+ .privilege = SEC_PRIV_ENABLE_DELEGATION,
+ .delete_is_privileged = true,
+ .error_string = "Updating the UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION bit in userAccountControl is not permitted without the SeEnableDelegationPrivilege"
+ }
+
+ };
+
+ if (dsdb_module_am_system(ac->module)) {
+ return LDB_SUCCESS;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(map); i++) {
+ if (user_account_control & map[i].uac) {
+ need_acl_check = true;
+ break;
+ }
+ }
+ if (need_acl_check == false) {
+ return LDB_SUCCESS;
+ }
+
+ user_token = acl_user_token(ac->module);
+ if (user_token == NULL) {
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+
+ ret = samldb_get_domain_secdesc_and_oc(ac, &domain_sd, &objectclass);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(map); i++) {
+ uint32_t this_uac_new = user_account_control & map[i].uac;
+ uint32_t this_uac_old = user_account_control_old & map[i].uac;
+ if (this_uac_new != this_uac_old) {
+ if (this_uac_old != 0) {
+ if (map[i].delete_is_privileged == false) {
+ continue;
+ }
+ }
+ if (map[i].oid) {
+ struct ldb_control *control = ldb_request_get_control(ac->req, map[i].oid);
+ if (control == NULL) {
+ ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+ } else if (map[i].privilege != SEC_PRIV_INVALID) {
+ bool have_priv = security_token_has_privilege(user_token,
+ map[i].privilege);
+ if (have_priv == false) {
+ ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+ } else if (map[i].priv_to_change_from & user_account_control_old) {
+ bool is_admin = security_token_has_builtin_administrators(user_token);
+ if (is_admin == false) {
+ ret = LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+ } else if (map[i].guid) {
+ ret = acl_check_extended_right(ac,
+ ac->module,
+ ac->req,
+ objectclass,
+ domain_sd,
+ user_token,
+ map[i].guid,
+ SEC_ADS_CONTROL_ACCESS,
+ sid);
+ } else {
+ ret = LDB_SUCCESS;
+ }
+ if (ret != LDB_SUCCESS) {
+ break;
+ }
+ }
+ }
+ if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ switch (ac->req->operation) {
+ case LDB_ADD:
+ ldb_asprintf_errstring(ldb_module_get_ctx(ac->module),
+ "Failed to add %s: %s",
+ ldb_dn_get_linearized(ac->msg->dn),
+ map[i].error_string);
+ break;
+ case LDB_MODIFY:
+ ldb_asprintf_errstring(ldb_module_get_ctx(ac->module),
+ "Failed to modify %s: %s",
+ ldb_dn_get_linearized(ac->msg->dn),
+ map[i].error_string);
+ break;
+ default:
+ return ldb_module_operr(ac->module);
+ }
+ if (map[i].guid) {
+ struct ldb_dn *domain_dn
+ = ldb_get_default_basedn(ldb_module_get_ctx(ac->module));
+ dsdb_acl_debug(domain_sd, acl_user_token(ac->module),
+ domain_dn,
+ true,
+ 10);
+ }
+ }
+ return ret;
+}
+
+static int samldb_check_user_account_control_rules(struct samldb_ctx *ac,
+ struct dom_sid *sid,
+ uint32_t req_uac,
+ uint32_t user_account_control,
+ uint32_t user_account_control_old,
+ bool is_computer_objectclass)
+{
+ int ret;
+ struct dsdb_control_password_user_account_control *uac = NULL;
+
+ ret = samldb_check_user_account_control_invariants(ac, user_account_control);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = samldb_check_user_account_control_objectclass_invariants(ac,
+ user_account_control,
+ user_account_control_old,
+ is_computer_objectclass);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = samldb_check_user_account_control_acl(ac, sid, user_account_control, user_account_control_old);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ uac = talloc_zero(ac->req,
+ struct dsdb_control_password_user_account_control);
+ if (uac == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ uac->req_flags = req_uac;
+ uac->old_flags = user_account_control_old;
+ uac->new_flags = user_account_control;
+
+ ret = ldb_request_add_control(ac->req,
+ DSDB_CONTROL_PASSWORD_USER_ACCOUNT_CONTROL_OID,
+ false, uac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ret;
+}
+
+
+/**
+ * This function is called on LDB modify operations. It performs some additions/
+ * replaces on the current LDB message when "userAccountControl" changes.
+ */
+static int samldb_user_account_control_change(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ uint32_t old_uac;
+ uint32_t new_uac;
+ uint32_t raw_uac;
+ uint32_t old_ufa;
+ uint32_t new_ufa;
+ uint32_t old_uac_computed;
+ uint32_t clear_uac;
+ uint32_t old_atype;
+ uint32_t new_atype;
+ uint32_t old_pgrid;
+ uint32_t new_pgrid;
+ NTTIME old_lockoutTime;
+ struct ldb_message_element *el;
+ struct ldb_val *val;
+ struct ldb_val computer_val;
+ struct ldb_message *tmp_msg;
+ struct dom_sid *sid;
+ int ret;
+ struct ldb_result *res;
+ const char * const attrs[] = {
+ "objectClass",
+ "isCriticalSystemObject",
+ "userAccountControl",
+ "msDS-User-Account-Control-Computed",
+ "lockoutTime",
+ "objectSid",
+ NULL
+ };
+ bool is_computer_objectclass = false;
+ bool old_is_critical = false;
+ bool new_is_critical = false;
+
+ ret = dsdb_get_expected_new_values(ac,
+ ac->msg,
+ "userAccountControl",
+ &el,
+ ac->req->operation);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (el == NULL || el->num_values == 0) {
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: 'userAccountControl' can't be deleted!",
+ W_ERROR_V(WERR_DS_ILLEGAL_MOD_OPERATION));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Create a temporary message for fetching the "userAccountControl" */
+ tmp_msg = ldb_msg_new(ac->msg);
+ if (tmp_msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ ret = ldb_msg_add(tmp_msg, el, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ raw_uac = ldb_msg_find_attr_as_uint(tmp_msg,
+ "userAccountControl",
+ 0);
+ talloc_free(tmp_msg);
+ /*
+ * UF_LOCKOUT, UF_PASSWD_CANT_CHANGE and UF_PASSWORD_EXPIRED
+ * are only generated and not stored. We ignore them almost
+ * completely, along with unknown bits and UF_SCRIPT.
+ *
+ * The only exception is ACB_AUTOLOCK, which features in
+ * clear_acb when the bit is cleared in this modify operation.
+ *
+ * MS-SAMR 2.2.1.13 UF_FLAG Codes states that some bits are
+ * ignored by clients and servers
+ */
+ new_uac = raw_uac & UF_SETTABLE_BITS;
+
+ /* Fetch the old "userAccountControl" and "objectClass" */
+ ret = dsdb_module_search_dn(ac->module, ac, &res, ac->msg->dn, attrs,
+ DSDB_FLAG_NEXT_MODULE, ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ old_uac = ldb_msg_find_attr_as_uint(res->msgs[0], "userAccountControl", 0);
+ if (old_uac == 0) {
+ return ldb_operr(ldb);
+ }
+ old_uac_computed = ldb_msg_find_attr_as_uint(res->msgs[0],
+ "msDS-User-Account-Control-Computed", 0);
+ old_lockoutTime = ldb_msg_find_attr_as_int64(res->msgs[0],
+ "lockoutTime", 0);
+ old_is_critical = ldb_msg_find_attr_as_bool(res->msgs[0],
+ "isCriticalSystemObject", 0);
+ /*
+ * When we do not have objectclass "computer" we cannot
+ * switch to a workstation or (RO)DC
+ */
+ el = ldb_msg_find_element(res->msgs[0], "objectClass");
+ if (el == NULL) {
+ return ldb_operr(ldb);
+ }
+ computer_val = data_blob_string_const("computer");
+ val = ldb_msg_find_val(el, &computer_val);
+ if (val != NULL) {
+ is_computer_objectclass = true;
+ }
+
+ old_ufa = old_uac & UF_ACCOUNT_TYPE_MASK;
+ old_atype = ds_uf2atype(old_ufa);
+ old_pgrid = ds_uf2prim_group_rid(old_uac);
+
+ new_ufa = new_uac & UF_ACCOUNT_TYPE_MASK;
+ if (new_ufa == 0) {
+ /*
+ * "userAccountControl" = 0 or missing one of the
+ * types means "UF_NORMAL_ACCOUNT". See MS-SAMR
+ * 3.1.1.8.10 point 8
+ */
+ new_ufa = UF_NORMAL_ACCOUNT;
+ new_uac |= new_ufa;
+ }
+ sid = samdb_result_dom_sid(res, res->msgs[0], "objectSid");
+ if (sid == NULL) {
+ return ldb_module_operr(ac->module);
+ }
+
+ ret = samldb_check_user_account_control_rules(ac, sid,
+ raw_uac,
+ new_uac,
+ old_uac,
+ is_computer_objectclass);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ new_atype = ds_uf2atype(new_ufa);
+ new_pgrid = ds_uf2prim_group_rid(new_uac);
+
+ clear_uac = (old_uac | old_uac_computed) & ~raw_uac;
+
+ switch (new_ufa) {
+ case UF_NORMAL_ACCOUNT:
+ new_is_critical = old_is_critical;
+ break;
+
+ case UF_INTERDOMAIN_TRUST_ACCOUNT:
+ new_is_critical = true;
+ break;
+
+ case UF_WORKSTATION_TRUST_ACCOUNT:
+ new_is_critical = false;
+ if (new_uac & UF_PARTIAL_SECRETS_ACCOUNT) {
+ new_is_critical = true;
+ }
+ break;
+
+ case UF_SERVER_TRUST_ACCOUNT:
+ new_is_critical = true;
+ break;
+
+ default:
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: invalid userAccountControl[0x%08X]",
+ W_ERROR_V(WERR_INVALID_PARAMETER), raw_uac);
+ return LDB_ERR_OTHER;
+ }
+
+ if (old_atype != new_atype) {
+ ret = samdb_msg_append_uint(ldb, ac->msg, ac->msg,
+ "sAMAccountType", new_atype,
+ LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* As per MS-SAMR 3.1.1.8.10 these flags have not to be set */
+ if ((clear_uac & UF_LOCKOUT) && (old_lockoutTime != 0)) {
+ /* "lockoutTime" reset as per MS-SAMR 3.1.1.8.10 */
+ ldb_msg_remove_attr(ac->msg, "lockoutTime");
+ ret = samdb_msg_append_uint64(ldb, ac->msg, ac->msg, "lockoutTime",
+ (NTTIME)0, LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /*
+ * "isCriticalSystemObject" might be set/changed
+ *
+ * Even a change from UF_NORMAL_ACCOUNT (implicitly FALSE) to
+ * UF_WORKSTATION_TRUST_ACCOUNT (actually FALSE) triggers
+ * creating the attribute.
+ */
+ if (old_is_critical != new_is_critical || old_atype != new_atype) {
+ ret = ldb_msg_append_string(ac->msg, "isCriticalSystemObject",
+ new_is_critical ? "TRUE": "FALSE",
+ LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (!ldb_msg_find_element(ac->msg, "primaryGroupID") &&
+ (old_pgrid != new_pgrid)) {
+ /* Older AD deployments don't know about the RODC group */
+ if (new_pgrid == DOMAIN_RID_READONLY_DCS) {
+ ret = samldb_prim_group_tester(ac, new_pgrid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ ret = samdb_msg_append_uint(ldb, ac->msg, ac->msg,
+ "primaryGroupID", new_pgrid,
+ LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* Propagate eventual "userAccountControl" attribute changes */
+ if (old_uac != new_uac) {
+ char *tempstr = talloc_asprintf(ac->msg, "%d",
+ new_uac);
+ if (tempstr == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ ret = ldb_msg_add_empty(ac->msg,
+ "userAccountControl",
+ LDB_FLAG_MOD_REPLACE,
+ &el);
+ el->values = talloc(ac->msg, struct ldb_val);
+ el->num_values = 1;
+ el->values[0].data = (uint8_t *) tempstr;
+ el->values[0].length = strlen(tempstr);
+ } else {
+ ldb_msg_remove_attr(ac->msg, "userAccountControl");
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_check_pwd_last_set_acl(struct samldb_ctx *ac,
+ struct dom_sid *sid)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ int ret = 0;
+ struct security_token *user_token = NULL;
+ struct security_descriptor *domain_sd = NULL;
+ struct ldb_dn *domain_dn = ldb_get_default_basedn(ldb_module_get_ctx(ac->module));
+ const char *operation = "";
+ const struct dsdb_class *objectclass = NULL;
+
+ if (dsdb_module_am_system(ac->module)) {
+ return LDB_SUCCESS;
+ }
+
+ switch (ac->req->operation) {
+ case LDB_ADD:
+ operation = "add";
+ break;
+ case LDB_MODIFY:
+ operation = "modify";
+ break;
+ default:
+ return ldb_module_operr(ac->module);
+ }
+
+ user_token = acl_user_token(ac->module);
+ if (user_token == NULL) {
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+
+ ret = samldb_get_domain_secdesc_and_oc(ac, &domain_sd, &objectclass);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = acl_check_extended_right(ac,
+ ac->module,
+ ac->req,
+ objectclass,
+ domain_sd,
+ user_token,
+ GUID_DRS_UNEXPIRE_PASSWORD,
+ SEC_ADS_CONTROL_ACCESS,
+ sid);
+ if (ret != LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
+ return ret;
+ }
+
+ ldb_debug_set(ldb, LDB_DEBUG_WARNING,
+ "Failed to %s %s: "
+ "Setting pwdLastSet to -1 requires the "
+ "Unexpire-Password right that was not given "
+ "on the Domain object",
+ operation,
+ ldb_dn_get_linearized(ac->msg->dn));
+ dsdb_acl_debug(domain_sd, user_token,
+ domain_dn, true, 10);
+
+ return ret;
+}
+
+/**
+ * This function is called on LDB modify operations. It performs some additions/
+ * replaces on the current LDB message when "pwdLastSet" changes.
+ */
+static int samldb_pwd_last_set_change(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ NTTIME last_set = 0;
+ struct ldb_message_element *el = NULL;
+ struct ldb_message *tmp_msg = NULL;
+ struct dom_sid *self_sid = NULL;
+ int ret;
+ struct ldb_result *res = NULL;
+ const char * const attrs[] = {
+ "objectSid",
+ NULL
+ };
+
+ ret = dsdb_get_expected_new_values(ac,
+ ac->msg,
+ "pwdLastSet",
+ &el,
+ ac->req->operation);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (el == NULL || el->num_values == 0) {
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: 'pwdLastSet' can't be deleted!",
+ W_ERROR_V(WERR_DS_ILLEGAL_MOD_OPERATION));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Create a temporary message for fetching the "userAccountControl" */
+ tmp_msg = ldb_msg_new(ac->msg);
+ if (tmp_msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ ret = ldb_msg_add(tmp_msg, el, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ last_set = samdb_result_nttime(tmp_msg, "pwdLastSet", 0);
+ talloc_free(tmp_msg);
+
+ /*
+ * Setting -1 (0xFFFFFFFFFFFFFFFF) requires the Unexpire-Password right
+ */
+ if (last_set != UINT64_MAX) {
+ return LDB_SUCCESS;
+ }
+
+ /* Fetch the "objectSid" */
+ ret = dsdb_module_search_dn(ac->module, ac, &res, ac->msg->dn, attrs,
+ DSDB_FLAG_NEXT_MODULE, ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ self_sid = samdb_result_dom_sid(res, res->msgs[0], "objectSid");
+ if (self_sid == NULL) {
+ return ldb_module_operr(ac->module);
+ }
+
+ ret = samldb_check_pwd_last_set_acl(ac, self_sid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_lockout_time(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ NTTIME lockoutTime;
+ struct ldb_message_element *el;
+ struct ldb_message *tmp_msg;
+ int ret;
+
+ ret = dsdb_get_expected_new_values(ac,
+ ac->msg,
+ "lockoutTime",
+ &el,
+ ac->req->operation);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (el == NULL || el->num_values == 0) {
+ ldb_asprintf_errstring(ldb,
+ "%08X: samldb: 'lockoutTime' can't be deleted!",
+ W_ERROR_V(WERR_DS_ILLEGAL_MOD_OPERATION));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Create a temporary message for fetching the "lockoutTime" */
+ tmp_msg = ldb_msg_new(ac->msg);
+ if (tmp_msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ ret = ldb_msg_add(tmp_msg, el, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ lockoutTime = ldb_msg_find_attr_as_int64(tmp_msg,
+ "lockoutTime",
+ 0);
+ talloc_free(tmp_msg);
+
+ if (lockoutTime != 0) {
+ return LDB_SUCCESS;
+ }
+
+ /* lockoutTime == 0 resets badPwdCount */
+ ldb_msg_remove_attr(ac->msg, "badPwdCount");
+ ret = samdb_msg_append_int(ldb, ac->msg, ac->msg,
+ "badPwdCount", 0,
+ LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_group_type_change(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ uint32_t group_type, old_group_type, account_type;
+ struct ldb_message_element *el;
+ struct ldb_message *tmp_msg;
+ int ret;
+ struct ldb_result *res;
+ const char * const attrs[] = { "groupType", NULL };
+
+ ret = dsdb_get_expected_new_values(ac,
+ ac->msg,
+ "groupType",
+ &el,
+ ac->req->operation);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (el == NULL) {
+ /* we are not affected */
+ return LDB_SUCCESS;
+ }
+
+ /* Create a temporary message for fetching the "groupType" */
+ tmp_msg = ldb_msg_new(ac->msg);
+ if (tmp_msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ ret = ldb_msg_add(tmp_msg, el, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ group_type = ldb_msg_find_attr_as_uint(tmp_msg, "groupType", 0);
+ talloc_free(tmp_msg);
+
+ ret = dsdb_module_search_dn(ac->module, ac, &res, ac->msg->dn, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_DELETED, ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ old_group_type = ldb_msg_find_attr_as_uint(res->msgs[0], "groupType", 0);
+ if (old_group_type == 0) {
+ return ldb_operr(ldb);
+ }
+
+ /* Group type switching isn't so easy as it seems: We can only
+ * change in this directions: global <-> universal <-> local
+ * On each step also the group type itself
+ * (security/distribution) is variable. */
+
+ if (ldb_request_get_control(ac->req, LDB_CONTROL_PROVISION_OID) == NULL) {
+ switch (group_type) {
+ case GTYPE_SECURITY_GLOBAL_GROUP:
+ case GTYPE_DISTRIBUTION_GLOBAL_GROUP:
+ /* change to "universal" allowed */
+ if ((old_group_type == GTYPE_SECURITY_DOMAIN_LOCAL_GROUP) ||
+ (old_group_type == GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP)) {
+ ldb_set_errstring(ldb,
+ "samldb: Change from security/distribution local group forbidden!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ break;
+
+ case GTYPE_SECURITY_UNIVERSAL_GROUP:
+ case GTYPE_DISTRIBUTION_UNIVERSAL_GROUP:
+ /* each change allowed */
+ break;
+ case GTYPE_SECURITY_DOMAIN_LOCAL_GROUP:
+ case GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP:
+ /* change to "universal" allowed */
+ if ((old_group_type == GTYPE_SECURITY_GLOBAL_GROUP) ||
+ (old_group_type == GTYPE_DISTRIBUTION_GLOBAL_GROUP)) {
+ ldb_set_errstring(ldb,
+ "samldb: Change from security/distribution global group forbidden!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ break;
+
+ case GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
+ default:
+ /* we don't allow this "groupType" values */
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ break;
+ }
+ }
+
+ account_type = ds_gtype2atype(group_type);
+ if (account_type == 0) {
+ ldb_set_errstring(ldb, "samldb: Unrecognized account type!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ ret = samdb_msg_append_uint(ldb, ac->msg, ac->msg, "sAMAccountType",
+ account_type, LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_member_check(struct samldb_ctx *ac)
+{
+ const char * const attrs[] = { "objectSid", NULL };
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct ldb_message_element *el;
+ struct ldb_dn *member_dn;
+ struct dom_sid *sid;
+ struct ldb_result *res;
+ struct dom_sid *group_sid;
+ unsigned int i, j;
+ int ret;
+
+ /* Fetch information from the existing object */
+
+ ret = dsdb_module_search(ac->module, ac, &res, ac->msg->dn, LDB_SCOPE_BASE, attrs,
+ DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED, ac->req, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (res->count != 1) {
+ return ldb_operr(ldb);
+ }
+
+ group_sid = samdb_result_dom_sid(res, res->msgs[0], "objectSid");
+ if (group_sid == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* We've to walk over all modification entries and consider the "member"
+ * ones. */
+ for (i = 0; i < ac->msg->num_elements; i++) {
+ if (ldb_attr_cmp(ac->msg->elements[i].name, "member") != 0) {
+ continue;
+ }
+
+ el = &ac->msg->elements[i];
+ for (j = 0; j < el->num_values; j++) {
+ struct ldb_result *group_res;
+ const char *group_attrs[] = { "primaryGroupID" , NULL };
+ uint32_t prim_group_rid;
+
+ if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE) {
+ /* Deletes will be handled in
+ * repl_meta_data, and deletes not
+ * matching a member will return
+ * LDB_ERR_UNWILLING_TO_PERFORM
+ * there */
+ continue;
+ }
+
+ member_dn = ldb_dn_from_ldb_val(ac, ldb,
+ &el->values[j]);
+ if (!ldb_dn_validate(member_dn)) {
+ return ldb_operr(ldb);
+ }
+
+ /* Denies to add "member"s to groups which are primary
+ * ones for them - in this case return
+ * ERR_ENTRY_ALREADY_EXISTS. */
+
+ ret = dsdb_module_search_dn(ac->module, ac, &group_res,
+ member_dn, group_attrs,
+ DSDB_FLAG_NEXT_MODULE, ac->req);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ /* member DN doesn't exist yet */
+ continue;
+ }
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ prim_group_rid = ldb_msg_find_attr_as_uint(group_res->msgs[0], "primaryGroupID", (uint32_t)-1);
+ if (prim_group_rid == (uint32_t) -1) {
+ /* the member hasn't to be a user account ->
+ * therefore no check needed in this case. */
+ continue;
+ }
+
+ sid = dom_sid_add_rid(ac, samdb_domain_sid(ldb),
+ prim_group_rid);
+ if (sid == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ if (dom_sid_equal(group_sid, sid)) {
+ ldb_asprintf_errstring(ldb,
+ "samldb: member %s already set via primaryGroupID %u",
+ ldb_dn_get_linearized(member_dn), prim_group_rid);
+ return LDB_ERR_ENTRY_ALREADY_EXISTS;
+ }
+ }
+ }
+
+ talloc_free(res);
+
+ return LDB_SUCCESS;
+}
+
+/* SAM objects have special rules regarding the "description" attribute on
+ * modify operations. */
+static int samldb_description_check(struct samldb_ctx *ac, bool *modified)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ const char * const attrs[] = { "objectClass", "description", NULL };
+ struct ldb_result *res;
+ unsigned int i;
+ int ret;
+
+ /* Fetch information from the existing object */
+ ret = dsdb_module_search(ac->module, ac, &res, ac->msg->dn, LDB_SCOPE_BASE, attrs,
+ DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED, ac->req,
+ "(|(objectclass=user)(objectclass=group)(objectclass=samDomain)(objectclass=samServer))");
+ if (ret != LDB_SUCCESS) {
+ /* don't treat it specially ... let normal error codes
+ happen from other places */
+ ldb_reset_err_string(ldb);
+ return LDB_SUCCESS;
+ }
+ if (res->count == 0) {
+ /* we didn't match the filter */
+ talloc_free(res);
+ return LDB_SUCCESS;
+ }
+
+ /* We've to walk over all modification entries and consider the
+ * "description" ones. */
+ for (i = 0; i < ac->msg->num_elements; i++) {
+ if (ldb_attr_cmp(ac->msg->elements[i].name, "description") == 0) {
+ ac->msg->elements[i].flags |= LDB_FLAG_INTERNAL_FORCE_SINGLE_VALUE_CHECK;
+ *modified = true;
+ }
+ }
+
+ talloc_free(res);
+
+ return LDB_SUCCESS;
+}
+
+#define SPN_ALIAS_NONE 0
+#define SPN_ALIAS_LINK 1
+#define SPN_ALIAS_TARGET 2
+
+static int find_spn_aliases(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ const char *service_class,
+ char ***aliases,
+ size_t *n_aliases,
+ int *direction)
+{
+ /*
+ * If you change the way this works, you should also look at changing
+ * LDB_lookup_spn_alias() in source4/dsdb/samdb/cracknames.c, which
+ * does some of the same work.
+ *
+ * In particular, note that sPNMappings are resolved on a first come,
+ * first served basis. For example, if we have
+ *
+ * host=ldap,cifs
+ * foo=ldap
+ * cifs=host,alerter
+ *
+ * then 'ldap', 'cifs', and 'host' will resolve to 'host', and
+ * 'alerter' will resolve to 'cifs'.
+ *
+ * If this resolution method is made more complicated, then the
+ * cracknames function should also be changed.
+ */
+ size_t i, j;
+ int ret;
+ bool ok;
+ struct ldb_result *res = NULL;
+ struct ldb_message_element *spnmappings = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct ldb_dn *service_dn = NULL;
+
+ const char *attrs[] = {
+ "sPNMappings",
+ NULL
+ };
+
+ *direction = SPN_ALIAS_NONE;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ service_dn = ldb_dn_new(
+ tmp_ctx, ldb,
+ "CN=Directory Service,CN=Windows NT,CN=Services");
+ if (service_dn == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ ok = ldb_dn_add_base(service_dn, ldb_get_config_basedn(ldb));
+ if (! ok) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_search(ldb, tmp_ctx, &res, service_dn, LDB_SCOPE_BASE,
+ attrs, "(objectClass=nTDSService)");
+
+ if (ret != LDB_SUCCESS || res->count != 1) {
+ DBG_WARNING("sPNMappings not found.\n");
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ spnmappings = ldb_msg_find_element(res->msgs[0], "sPNMappings");
+ if (spnmappings == NULL || spnmappings->num_values == 0) {
+ DBG_WARNING("no sPNMappings attribute\n");
+ talloc_free(tmp_ctx);
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+ *n_aliases = 0;
+
+ for (i = 0; i < spnmappings->num_values; i++) {
+ char *p = NULL;
+ char *mapping = talloc_strndup(
+ tmp_ctx,
+ (char *)spnmappings->values[i].data,
+ spnmappings->values[i].length);
+ if (mapping == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ p = strchr(mapping, '=');
+ if (p == NULL) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_ALIAS_PROBLEM;
+ }
+ p[0] = '\0';
+ p++;
+
+ if (strcasecmp(mapping, service_class) == 0) {
+ /*
+ * We need to return the reverse aliases for this one.
+ *
+ * typically, this means the service_class is "host"
+ * and the mapping is "host=alerter,appmgmt,cisvc,..",
+ * so we get "alerter", "appmgmt", etc in the list of
+ * aliases.
+ */
+
+ /* There is one more field than there are commas */
+ size_t n = 1;
+
+ for (j = 0; p[j] != '\0'; j++) {
+ if (p[j] == ',') {
+ n++;
+ p[j] = '\0';
+ }
+ }
+ *aliases = talloc_array(mem_ctx, char*, n);
+ if (*aliases == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+ *n_aliases = n;
+ talloc_steal(mem_ctx, mapping);
+ for (j = 0; j < n; j++) {
+ (*aliases)[j] = p;
+ p += strlen(p) + 1;
+ }
+ talloc_free(tmp_ctx);
+ *direction = SPN_ALIAS_LINK;
+ return LDB_SUCCESS;
+ }
+ /*
+ * We need to look along the list to see if service_class is
+ * there; if so, we return a list of one item (probably "host").
+ */
+ do {
+ char *str = p;
+ p = strchr(p, ',');
+ if (p != NULL) {
+ p[0] = '\0';
+ p++;
+ }
+ if (strcasecmp(str, service_class) == 0) {
+ *aliases = talloc_array(mem_ctx, char*, 1);
+ if (*aliases == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+ *n_aliases = 1;
+ (*aliases)[0] = mapping;
+ talloc_steal(mem_ctx, mapping);
+ talloc_free(tmp_ctx);
+ *direction = SPN_ALIAS_TARGET;
+ return LDB_SUCCESS;
+ }
+ } while (p != NULL);
+ }
+ DBG_INFO("no sPNMappings alias for '%s'\n", service_class);
+ talloc_free(tmp_ctx);
+ *aliases = NULL;
+ *n_aliases = 0;
+ return LDB_SUCCESS;
+}
+
+
+static int get_spn_dn(struct ldb_context *ldb,
+ TALLOC_CTX *tmp_ctx,
+ const char *candidate,
+ struct ldb_dn **dn)
+{
+ int ret;
+ const char *empty_attrs[] = { NULL };
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *base_dn = ldb_get_default_basedn(ldb);
+
+ const char *enc_candidate = NULL;
+
+ *dn = NULL;
+
+ enc_candidate = ldb_binary_encode_string(tmp_ctx, candidate);
+ if (enc_candidate == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ ret = dsdb_search_one(ldb,
+ tmp_ctx,
+ &msg,
+ base_dn,
+ LDB_SCOPE_SUBTREE,
+ empty_attrs,
+ 0,
+ "(servicePrincipalName=%s)",
+ enc_candidate);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ *dn = msg->dn;
+ return LDB_SUCCESS;
+}
+
+
+static int check_spn_write_rights(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ const char *spn,
+ struct ldb_dn *dn)
+{
+ int ret;
+ struct ldb_message *msg = NULL;
+ struct ldb_message_element *del_el = NULL;
+ struct ldb_message_element *add_el = NULL;
+ struct ldb_val val = {
+ .data = discard_const_p(uint8_t, spn),
+ .length = strlen(spn)
+ };
+
+ msg = ldb_msg_new(mem_ctx);
+ if (msg == NULL) {
+ return ldb_oom(ldb);
+ }
+ msg->dn = dn;
+
+ ret = ldb_msg_add_empty(msg,
+ "servicePrincipalName",
+ LDB_FLAG_MOD_DELETE,
+ &del_el);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(msg);
+ return ret;
+ }
+
+ del_el->values = talloc_array(msg->elements, struct ldb_val, 1);
+ if (del_el->values == NULL) {
+ talloc_free(msg);
+ return ret;
+ }
+
+ del_el->values[0] = val;
+ del_el->num_values = 1;
+
+ ret = ldb_msg_add_empty(msg,
+ "servicePrincipalName",
+ LDB_FLAG_MOD_ADD,
+ &add_el);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(msg);
+ return ret;
+ }
+
+ add_el->values = talloc_array(msg->elements, struct ldb_val, 1);
+ if (add_el->values == NULL) {
+ talloc_free(msg);
+ return ret;
+ }
+
+ add_el->values[0] = val;
+ add_el->num_values = 1;
+
+ ret = ldb_modify(ldb, msg);
+ if (ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
+ DBG_ERR("hmm I think we're OK, but not sure\n");
+ } else if (ret != LDB_SUCCESS) {
+ DBG_ERR("SPN write rights check failed with %d\n", ret);
+ talloc_free(msg);
+ return ret;
+ }
+ talloc_free(msg);
+ return LDB_SUCCESS;
+}
+
+
+static int check_spn_alias_collision(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ const char *spn,
+ struct ldb_dn *target_dn)
+{
+ int ret;
+ char *service_class = NULL;
+ char *spn_tail = NULL;
+ char *p = NULL;
+ char **aliases = NULL;
+ size_t n_aliases = 0;
+ size_t i, len;
+ TALLOC_CTX *tmp_ctx = NULL;
+ const char *target_dnstr = ldb_dn_get_linearized(target_dn);
+ int link_direction;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ /*
+ * "dns/example.com/xxx" gives
+ * service_class = "dns"
+ * spn_tail = "example.com/xxx"
+ */
+ p = strchr(spn, '/');
+ if (p == NULL) {
+ /* bad SPN */
+ talloc_free(tmp_ctx);
+ return ldb_error(ldb,
+ LDB_ERR_OPERATIONS_ERROR,
+ "malformed servicePrincipalName");
+ }
+ len = p - spn;
+
+ service_class = talloc_strndup(tmp_ctx, spn, len);
+ if (service_class == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+ spn_tail = p + 1;
+
+ ret = find_spn_aliases(ldb,
+ tmp_ctx,
+ service_class,
+ &aliases,
+ &n_aliases,
+ &link_direction);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /*
+ * we have the list of aliases, and now we need to combined them with
+ * spn_tail and see if we can find the SPN.
+ */
+ for (i = 0; i < n_aliases; i++) {
+ struct ldb_dn *colliding_dn = NULL;
+ const char *colliding_dnstr = NULL;
+
+ char *candidate = talloc_asprintf(tmp_ctx,
+ "%s/%s",
+ aliases[i],
+ spn_tail);
+ if (candidate == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ ret = get_spn_dn(ldb, tmp_ctx, candidate, &colliding_dn);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ DBG_DEBUG("SPN alias '%s' not found (good)\n",
+ candidate);
+ talloc_free(candidate);
+ continue;
+ }
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("SPN '%s' search error %d\n", candidate, ret);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ target_dnstr = ldb_dn_get_linearized(target_dn);
+ /*
+ * We have found an existing SPN that matches the alias. That
+ * is OK only if it is on the object we are trying to add to,
+ * or if the SPN on the other side is a more generic alias for
+ * this one and we also have rights to modify it.
+ *
+ * That is, we can put "host/X" and "cifs/X" on the same
+ * object, but not on different objects, unless we put the
+ * host/X on first, and could also change that object when we
+ * add cifs/X. It is forbidden to add the objects in the other
+ * order.
+ *
+ * The rationale for this is that adding "cifs/X" effectively
+ * changes "host/X" by diverting traffic. If "host/X" can be
+ * added after "cifs/X", a sneaky person could get "cifs/X" in
+ * first, making "host/X" have less effect than intended.
+ *
+ * Note: we also can't have "host/X" and "Host/X" on the same
+ * object, but that is not relevant here.
+ */
+
+ ret = ldb_dn_compare(colliding_dn, target_dn);
+ if (ret != 0) {
+ colliding_dnstr = ldb_dn_get_linearized(colliding_dn);
+ DBG_ERR("trying to add SPN '%s' on '%s' when '%s' is "
+ "on '%s'\n",
+ spn,
+ target_dnstr,
+ candidate,
+ colliding_dnstr);
+
+ if (link_direction == SPN_ALIAS_LINK) {
+ /* we don't allow host/X if there is a
+ * cifs/X */
+ talloc_free(tmp_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ ret = check_spn_write_rights(ldb,
+ tmp_ctx,
+ candidate,
+ colliding_dn);
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("SPN '%s' is on '%s' so '%s' can't be "
+ "added to '%s'\n",
+ candidate,
+ colliding_dnstr,
+ spn,
+ target_dnstr);
+ talloc_free(tmp_ctx);
+ ldb_asprintf_errstring(ldb,
+ "samldb: spn[%s] would cause a conflict",
+ spn);
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+ } else {
+ DBG_INFO("SPNs '%s' and '%s' alias both on '%s'\n",
+ candidate, spn, target_dnstr);
+ }
+ talloc_free(candidate);
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+static int check_spn_direct_collision(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ const char *spn,
+ struct ldb_dn *target_dn)
+{
+ int ret;
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct ldb_dn *colliding_dn = NULL;
+ const char *target_dnstr = NULL;
+ const char *colliding_dnstr = NULL;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ret = get_spn_dn(ldb, tmp_ctx, spn, &colliding_dn);
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ DBG_DEBUG("SPN '%s' not found (good)\n", spn);
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ }
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("SPN '%s' search error %d\n", spn, ret);
+ talloc_free(tmp_ctx);
+ if (ret == LDB_ERR_COMPARE_TRUE) {
+ /*
+ * COMPARE_TRUE has special meaning here and we don't
+ * want to return it by mistake.
+ */
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ }
+ return ret;
+ }
+ /*
+ * We have found this exact SPN. This is mostly harmless (depend on
+ * ADD vs REPLACE) when the spn is being put on the object that
+ * already has, so we let it through to succeed or fail as some other
+ * module sees fit.
+ */
+ target_dnstr = ldb_dn_get_linearized(target_dn);
+ ret = ldb_dn_compare(colliding_dn, target_dn);
+ if (ret != 0) {
+ colliding_dnstr = ldb_dn_get_linearized(colliding_dn);
+ DBG_ERR("SPN '%s' is on '%s' so it can't be "
+ "added to '%s'\n",
+ spn,
+ colliding_dnstr,
+ target_dnstr);
+ ldb_asprintf_errstring(ldb,
+ "samldb: spn[%s] would cause a conflict",
+ spn);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ DBG_INFO("SPN '%s' is already on '%s'\n",
+ spn, target_dnstr);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_COMPARE_TRUE;
+}
+
+
+static int count_spn_components(struct ldb_val val)
+{
+ /*
+ * a 3 part servicePrincipalName has two slashes, like
+ * ldap/example.com/DomainDNSZones.example.com.
+ *
+ * In krb5_parse_name_flags() we don't count "\/" as a slash (i.e.
+ * escaped by a backslash), but this is not the behaviour of Windows
+ * on setting a servicePrincipalName -- slashes are counted regardless
+ * of backslashes.
+ *
+ * Accordingly, here we ignore backslashes. This will reject
+ * multi-slash SPNs that krb5_parse_name_flags() would accept, and
+ * allow ones in the form "a\/b" that it won't parse.
+ */
+ size_t i;
+ int slashes = 0;
+ for (i = 0; i < val.length; i++) {
+ char c = val.data[i];
+ if (c == '/') {
+ slashes++;
+ if (slashes == 3) {
+ /* at this point we don't care */
+ return 4;
+ }
+ }
+ }
+ return slashes + 1;
+}
+
+
+/* Check that "servicePrincipalName" changes do not introduce a collision
+ * globally. */
+static int samldb_spn_uniqueness_check(struct samldb_ctx *ac,
+ struct ldb_message_element *spn_el)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ int ret;
+ const char *spn = NULL;
+ size_t i;
+ TALLOC_CTX *tmp_ctx = talloc_new(ac->msg);
+ if (tmp_ctx == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ for (i = 0; i < spn_el->num_values; i++) {
+ int n_components;
+ spn = (char *)spn_el->values[i].data;
+
+ n_components = count_spn_components(spn_el->values[i]);
+ if (n_components > 3 || n_components < 2) {
+ ldb_asprintf_errstring(ldb,
+ "samldb: spn[%s] invalid with %u components",
+ spn, n_components);
+ talloc_free(tmp_ctx);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ ret = check_spn_direct_collision(ldb,
+ tmp_ctx,
+ spn,
+ ac->msg->dn);
+ if (ret == LDB_ERR_COMPARE_TRUE) {
+ DBG_INFO("SPN %s re-added to the same object\n", spn);
+ continue;
+ }
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("SPN %s failed direct uniqueness check\n", spn);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = check_spn_alias_collision(ldb,
+ tmp_ctx,
+ spn,
+ ac->msg->dn);
+
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ /* we have no sPNMappings, hence no aliases */
+ break;
+ }
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("SPN %s failed alias uniqueness check\n", spn);
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ DBG_INFO("SPN %s seems to be unique\n", spn);
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+
+
+/* This trigger adapts the "servicePrincipalName" attributes if the
+ * "dNSHostName" and/or "sAMAccountName" attribute change(s) */
+static int samldb_service_principal_names_change(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct ldb_message_element *el = NULL, *el2 = NULL;
+ struct ldb_message *msg;
+ const char * const attrs[] = { "servicePrincipalName", NULL };
+ struct ldb_result *res;
+ const char *dns_hostname = NULL, *old_dns_hostname = NULL,
+ *sam_accountname = NULL, *old_sam_accountname = NULL;
+ unsigned int i, j;
+ int ret;
+
+ ret = dsdb_get_expected_new_values(ac,
+ ac->msg,
+ "dNSHostName",
+ &el,
+ ac->req->operation);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = dsdb_get_expected_new_values(ac,
+ ac->msg,
+ "sAMAccountName",
+ &el2,
+ ac->req->operation);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if ((el == NULL) && (el2 == NULL)) {
+ /* we are not affected */
+ return LDB_SUCCESS;
+ }
+
+ /* Create a temporary message for fetching the "dNSHostName" */
+ if (el != NULL) {
+ const char *dns_attrs[] = { "dNSHostName", NULL };
+ msg = ldb_msg_new(ac->msg);
+ if (msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ ret = ldb_msg_add(msg, el, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ dns_hostname = talloc_strdup(ac,
+ ldb_msg_find_attr_as_string(msg, "dNSHostName", NULL));
+ if (dns_hostname == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ talloc_free(msg);
+
+ ret = dsdb_module_search_dn(ac->module, ac, &res, ac->msg->dn,
+ dns_attrs, DSDB_FLAG_NEXT_MODULE, ac->req);
+ if (ret == LDB_SUCCESS) {
+ old_dns_hostname = ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", NULL);
+ }
+ }
+
+ /* Create a temporary message for fetching the "sAMAccountName" */
+ if (el2 != NULL) {
+ char *tempstr, *tempstr2 = NULL;
+ const char *acct_attrs[] = { "sAMAccountName", NULL };
+
+ msg = ldb_msg_new(ac->msg);
+ if (msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ ret = ldb_msg_add(msg, el2, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ tempstr = talloc_strdup(ac,
+ ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL));
+ talloc_free(msg);
+
+ ret = dsdb_module_search_dn(ac->module, ac, &res, ac->msg->dn, acct_attrs,
+ DSDB_FLAG_NEXT_MODULE, ac->req);
+ if (ret == LDB_SUCCESS) {
+ tempstr2 = talloc_strdup(ac,
+ ldb_msg_find_attr_as_string(res->msgs[0],
+ "sAMAccountName", NULL));
+ }
+
+
+ /* The "sAMAccountName" needs some additional trimming: we need
+ * to remove the trailing "$"s if they exist. */
+ if ((tempstr != NULL) && (tempstr[0] != '\0') &&
+ (tempstr[strlen(tempstr) - 1] == '$')) {
+ tempstr[strlen(tempstr) - 1] = '\0';
+ }
+ if ((tempstr2 != NULL) && (tempstr2[0] != '\0') &&
+ (tempstr2[strlen(tempstr2) - 1] == '$')) {
+ tempstr2[strlen(tempstr2) - 1] = '\0';
+ }
+ sam_accountname = tempstr;
+ old_sam_accountname = tempstr2;
+ }
+
+ if (old_dns_hostname == NULL) {
+ /* we cannot change when the old name is unknown */
+ dns_hostname = NULL;
+ }
+ if ((old_dns_hostname != NULL) && (dns_hostname != NULL) &&
+ (strcasecmp_m(old_dns_hostname, dns_hostname) == 0)) {
+ /* The "dNSHostName" didn't change */
+ dns_hostname = NULL;
+ }
+
+ if (old_sam_accountname == NULL) {
+ /* we cannot change when the old name is unknown */
+ sam_accountname = NULL;
+ }
+ if ((old_sam_accountname != NULL) && (sam_accountname != NULL) &&
+ (strcasecmp_m(old_sam_accountname, sam_accountname) == 0)) {
+ /* The "sAMAccountName" didn't change */
+ sam_accountname = NULL;
+ }
+
+ if ((dns_hostname == NULL) && (sam_accountname == NULL)) {
+ /* Well, there are information missing (old name(s)) or the
+ * names didn't change. We've nothing to do and can exit here */
+ return LDB_SUCCESS;
+ }
+
+ /*
+ * Potential "servicePrincipalName" changes in the same request have
+ * to be handled before the update (Windows behaviour).
+ *
+ * We extract the SPN changes into a new message and run it through
+ * the stack from this module, so that it subjects them to the SPN
+ * checks we have here.
+ */
+ el = ldb_msg_find_element(ac->msg, "servicePrincipalName");
+ if (el != NULL) {
+ msg = ldb_msg_new(ac->msg);
+ if (msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ msg->dn = ac->msg->dn;
+
+ do {
+ ret = ldb_msg_add(msg, el, el->flags);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ldb_msg_remove_element(ac->msg, el);
+
+ el = ldb_msg_find_element(ac->msg,
+ "servicePrincipalName");
+ } while (el != NULL);
+
+ ret = dsdb_module_modify(ac->module, msg,
+ DSDB_FLAG_OWN_MODULE, ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ talloc_free(msg);
+ }
+
+ /* Fetch the "servicePrincipalName"s if any */
+ ret = dsdb_module_search(ac->module, ac, &res, ac->msg->dn, LDB_SCOPE_BASE, attrs,
+ DSDB_FLAG_NEXT_MODULE, ac->req, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if ((res->count != 1) || (res->msgs[0]->num_elements > 1)) {
+ return ldb_operr(ldb);
+ }
+
+ if (res->msgs[0]->num_elements == 1) {
+ /*
+ * Yes, we do have "servicePrincipalName"s. First we update them
+ * locally, that means we do always substitute the current
+ * "dNSHostName" with the new one and/or "sAMAccountName"
+ * without "$" with the new one and then we append the
+ * modified "servicePrincipalName"s as a message element
+ * replace to the modification request (Windows behaviour). We
+ * need also to make sure that the values remain case-
+ * insensitively unique.
+ */
+
+ ret = ldb_msg_add_empty(ac->msg, "servicePrincipalName",
+ LDB_FLAG_MOD_REPLACE, &el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ for (i = 0; i < res->msgs[0]->elements[0].num_values; i++) {
+ char *old_str, *new_str;
+ char *pos = NULL;
+ const char *tok;
+ struct ldb_val *vals;
+ bool found = false;
+
+ old_str = (char *)
+ res->msgs[0]->elements[0].values[i].data;
+
+ new_str = talloc_strdup(ac->msg,
+ strtok_r(old_str, "/", &pos));
+ if (new_str == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ while ((tok = strtok_r(NULL, "/", &pos)) != NULL) {
+ if ((dns_hostname != NULL) &&
+ (strcasecmp_m(tok, old_dns_hostname) == 0)) {
+ tok = dns_hostname;
+ }
+ if ((sam_accountname != NULL) &&
+ (strcasecmp_m(tok, old_sam_accountname) == 0)) {
+ tok = sam_accountname;
+ }
+
+ new_str = talloc_asprintf(ac->msg, "%s/%s",
+ new_str, tok);
+ if (new_str == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ }
+
+ /* Uniqueness check */
+ for (j = 0; (!found) && (j < el->num_values); j++) {
+ if (strcasecmp_m((char *)el->values[j].data,
+ new_str) == 0) {
+ found = true;
+ }
+ }
+ if (found) {
+ continue;
+ }
+
+ /*
+ * append the new "servicePrincipalName" -
+ * code derived from ldb_msg_add_value().
+ *
+ * Open coded to make it clear that we must
+ * append to the MOD_REPLACE el created above.
+ */
+ vals = talloc_realloc(ac->msg, el->values,
+ struct ldb_val,
+ el->num_values + 1);
+ if (vals == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ el->values = vals;
+ el->values[el->num_values] = data_blob_string_const(new_str);
+ ++(el->num_values);
+ }
+ }
+
+ talloc_free(res);
+
+ return LDB_SUCCESS;
+}
+
+/* This checks the "fSMORoleOwner" attributes */
+static int samldb_fsmo_role_owner_check(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ const char * const no_attrs[] = { NULL };
+ struct ldb_message_element *el;
+ struct ldb_message *tmp_msg;
+ struct ldb_dn *res_dn;
+ struct ldb_result *res;
+ int ret;
+ ret = dsdb_get_expected_new_values(ac,
+ ac->msg,
+ "fSMORoleOwner",
+ &el,
+ ac->req->operation);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (el == NULL) {
+ /* we are not affected */
+ return LDB_SUCCESS;
+ }
+ if (el->num_values != 1) {
+ goto choose_error_code;
+ }
+
+ /* Create a temporary message for fetching the "fSMORoleOwner" */
+ tmp_msg = ldb_msg_new(ac->msg);
+ if (tmp_msg == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+ ret = ldb_msg_add(tmp_msg, el, 0);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ res_dn = ldb_msg_find_attr_as_dn(ldb, ac, tmp_msg, "fSMORoleOwner");
+ talloc_free(tmp_msg);
+
+ if (res_dn == NULL) {
+ ldb_set_errstring(ldb,
+ "samldb: 'fSMORoleOwner' attributes have to reference 'nTDSDSA' entries!");
+ goto choose_error_code;
+ }
+
+ /* Fetched DN has to reference a "nTDSDSA" entry */
+ ret = dsdb_module_search(ac->module, ac, &res, res_dn, LDB_SCOPE_BASE,
+ no_attrs,
+ DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED,
+ ac->req, "(objectClass=nTDSDSA)");
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (res->count != 1) {
+ ldb_set_errstring(ldb,
+ "samldb: 'fSMORoleOwner' attributes have to reference 'nTDSDSA' entries!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ talloc_free(res);
+
+ return LDB_SUCCESS;
+
+choose_error_code:
+ /* this is just how it is */
+ if (ac->req->operation == LDB_ADD) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ } else {
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+}
+
+/*
+ * Return zero if the number of zero bits in the address (looking from low to
+ * high) is equal to or greater than the length minus the mask. Otherwise it
+ * returns -1.
+ */
+static int check_cidr_zero_bits(uint8_t *address, unsigned int len,
+ unsigned int mask)
+{
+ /* <address> is an integer in big-endian form, <len> bits long. All
+ bits between <mask> and <len> must be zero. */
+ int i;
+ unsigned int byte_len;
+ unsigned int byte_mask;
+ unsigned int bit_mask;
+ if (len == 32) {
+ DBG_INFO("Looking at address %02x%02x%02x%02x, mask %u\n",
+ address[0], address[1], address[2], address[3],
+ mask);
+ } else if (len == 128){
+ DBG_INFO("Looking at address "
+ "%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
+ "%02x%02x-%02x%02x-%02x%02x-%02x%02x, mask %u\n",
+ address[0], address[1], address[2], address[3],
+ address[4], address[5], address[6], address[7],
+ address[8], address[9], address[10], address[11],
+ address[12], address[13], address[14], address[15],
+ mask);
+ }
+
+ if (mask > len){
+ DBG_INFO("mask %u is too big (> %u)\n", mask, len);
+ return -1;
+ }
+ if (mask == len){
+ /* single address subnet.
+ * In IPv4 all 255s is invalid by the bitmask != address rule
+ * in MS-ADTS. IPv6 does not suffer.
+ */
+ if (len == 32){
+ if (address[0] == 255 &&
+ address[1] == 255 &&
+ address[2] == 255 &&
+ address[3] == 255){
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ byte_len = len / 8;
+ byte_mask = mask / 8;
+
+ for (i = byte_len - 1; i > byte_mask; i--){
+ DBG_DEBUG("checking byte %d %02x\n", i, address[i]);
+ if (address[i] != 0){
+ return -1;
+ }
+ }
+ bit_mask = (1 << (8 - (mask & 7))) - 1;
+ DBG_DEBUG("checking bitmask %02x & %02x overlap %02x\n", bit_mask, address[byte_mask],
+ bit_mask & address[byte_mask]);
+ if (address[byte_mask] & bit_mask){
+ return -1;
+ }
+
+ /* According to MS-ADTS, the mask can't exactly equal the bitmask for
+ * IPv4 (but this is fine for v6). That is 255.255.80.0/17 is bad,
+ * because the bitmask implied by "/17" is 255.255.80.0.
+ *
+ * The bit_mask used in the previous check is the complement of what
+ * we want here.
+ */
+ if (len == 32 && address[byte_mask] == (uint8_t)~bit_mask){
+ bool ok = false;
+ for (i = 0; i < byte_mask; i++){
+ if (address[i] != 255){
+ ok = true;
+ break;
+ }
+ }
+ if (ok == false){
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+
+static int check_address_roundtrip(const char *address, int family,
+ const uint8_t *address_bytes,
+ char *buffer, int buffer_len)
+{
+ /*
+ * Check that the address is in the canonical RFC5952 format for IPv6,
+ * and lacks extra leading zeros for each dotted decimal for IPv4.
+ * Handily this is what inet_ntop() gives you.
+ */
+ const char *address_redux = inet_ntop(family, address_bytes,
+ buffer, buffer_len);
+ if (address_redux == NULL){
+ DBG_INFO("Address round trip %s failed unexpectedly"
+ " with errno %d\n", address, errno);
+ return -1;
+ }
+ if (strcasecmp(address, address_redux) != 0){
+ DBG_INFO("Address %s round trips to %s; fail!\n",
+ address, address_redux);
+ /* If the address family is IPv6, and the address is in a
+ certain range
+
+ */
+ if (strchr(address_redux, '.') != NULL){
+ DEBUG(0, ("The IPv6 address '%s' has the misfortune of "
+ "lying in a range that was once used for "
+ "IPv4 embedding (that is, it might also be "
+ "represented as '%s').\n", address,
+ address_redux));
+ }
+ return -1;
+ }
+ return 0;
+}
+
+
+
+/*
+ * MS-ADTS v20150630 6.1.1.2.2.2.1 Subnet Object, refers to RFC1166 and
+ * RFC2373. It specifies something seemingly indistinguishable from an RFC4632
+ * CIDR address range without saying so explicitly. Here we follow the CIDR
+ * spec.
+ *
+ * Return 0 on success, -1 on error.
+ */
+static int verify_cidr(const char *cidr)
+{
+ char *address = NULL, *slash = NULL;
+ bool has_colon, has_dot;
+ int res, ret;
+ unsigned long mask;
+ uint8_t *address_bytes = NULL;
+ char *address_redux = NULL;
+ unsigned int address_len;
+ TALLOC_CTX *frame = NULL;
+ int error = 0;
+
+ DBG_DEBUG("CIDR is %s\n", cidr);
+ frame = talloc_stackframe();
+ address = talloc_strdup(frame, cidr);
+ if (address == NULL){
+ goto error;
+ }
+
+ /* there must be a '/' */
+ slash = strchr(address, '/');
+ if (slash == NULL){
+ goto error;
+ }
+ /* terminate the address for strchr, inet_pton */
+ *slash = '\0';
+
+ mask = smb_strtoul(slash + 1, NULL, 10, &error, SMB_STR_FULL_STR_CONV);
+ if (mask == 0){
+ DBG_INFO("Windows does not like the zero mask, "
+ "so nor do we: %s\n", cidr);
+ goto error;
+ }
+
+ if (error != 0){
+ DBG_INFO("CIDR mask is not a proper integer: %s\n", cidr);
+ goto error;
+ }
+
+ address_bytes = talloc_size(frame, sizeof(struct in6_addr));
+ if (address_bytes == NULL){
+ goto error;
+ }
+
+ address_redux = talloc_size(frame, INET6_ADDRSTRLEN);
+ if (address_redux == NULL){
+ goto error;
+ }
+
+ DBG_INFO("found address %s, mask %lu\n", address, mask);
+ has_colon = (strchr(address, ':') == NULL) ? false : true;
+ has_dot = (strchr(address, '.') == NULL) ? false : true;
+ if (has_dot && has_colon){
+ /* This seems to be an IPv4 address embedded in IPv6, which is
+ icky. We don't support it. */
+ DBG_INFO("Refusing to consider cidr '%s' with dots and colons\n",
+ cidr);
+ goto error;
+ } else if (has_colon){ /* looks like IPv6 */
+ res = inet_pton(AF_INET6, address, address_bytes);
+ if (res != 1) {
+ DBG_INFO("Address in %s fails to parse as IPv6\n", cidr);
+ goto error;
+ }
+ address_len = 128;
+ if (check_address_roundtrip(address, AF_INET6, address_bytes,
+ address_redux, INET6_ADDRSTRLEN)){
+ goto error;
+ }
+ } else if (has_dot) {
+ /* looks like IPv4 */
+ if (strcmp(address, "0.0.0.0") == 0){
+ DBG_INFO("Windows does not like the zero IPv4 address, "
+ "so nor do we.\n");
+ goto error;
+ }
+ res = inet_pton(AF_INET, address, address_bytes);
+ if (res != 1) {
+ DBG_INFO("Address in %s fails to parse as IPv4\n", cidr);
+ goto error;
+ }
+ address_len = 32;
+
+ if (check_address_roundtrip(address, AF_INET, address_bytes,
+ address_redux, INET_ADDRSTRLEN)){
+ goto error;
+ }
+ } else {
+ /* This doesn't look like an IP address at all. */
+ goto error;
+ }
+
+ ret = check_cidr_zero_bits(address_bytes, address_len, mask);
+ talloc_free(frame);
+ return ret;
+ error:
+ talloc_free(frame);
+ return -1;
+}
+
+
+static int samldb_verify_subnet(struct samldb_ctx *ac, struct ldb_dn *dn)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ const char *cidr = NULL;
+ const struct ldb_val *rdn_value = NULL;
+
+ rdn_value = ldb_dn_get_rdn_val(dn);
+ if (rdn_value == NULL) {
+ ldb_set_errstring(ldb, "samldb: ldb_dn_get_rdn_val "
+ "failed");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ cidr = ldb_dn_escape_value(ac, *rdn_value);
+ DBG_INFO("looking at cidr '%s'\n", cidr);
+ if (cidr == NULL) {
+ ldb_set_errstring(ldb,
+ "samldb: adding an empty subnet cidr seems wrong");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ if (verify_cidr(cidr)){
+ ldb_set_errstring(ldb,
+ "samldb: subnet value is invalid");
+ return LDB_ERR_INVALID_DN_SYNTAX;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static char *refer_if_rodc(struct ldb_context *ldb, struct ldb_request *req,
+ struct ldb_dn *dn)
+{
+ bool rodc = false;
+ struct loadparm_context *lp_ctx;
+ char *referral;
+ int ret;
+ WERROR err;
+
+ if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID) ||
+ ldb_request_get_control(req, DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA)) {
+ return NULL;
+ }
+
+ ret = samdb_rodc(ldb, &rodc);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(4, (__location__ ": unable to tell if we are an RODC\n"));
+ return NULL;
+ }
+
+ if (rodc) {
+ const char *domain = NULL;
+ struct ldb_dn *fsmo_role_dn;
+ struct ldb_dn *role_owner_dn;
+ ldb_set_errstring(ldb, "RODC modify is forbidden!");
+ lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+
+ err = dsdb_get_fsmo_role_info(req, ldb, DREPL_PDC_MASTER,
+ &fsmo_role_dn, &role_owner_dn);
+ if (W_ERROR_IS_OK(err)) {
+ struct ldb_dn *server_dn = ldb_dn_copy(req, role_owner_dn);
+ if (server_dn != NULL) {
+ ldb_dn_remove_child_components(server_dn, 1);
+
+ domain = samdb_dn_to_dnshostname(ldb, req,
+ server_dn);
+ }
+ }
+ if (domain == NULL) {
+ domain = lpcfg_dnsdomain(lp_ctx);
+ }
+ referral = talloc_asprintf(req,
+ "ldap://%s/%s",
+ domain,
+ ldb_dn_get_linearized(dn));
+ return referral;
+ }
+
+ return NULL;
+}
+
+/*
+ * Restrict all access to sensitive attributes.
+ *
+ * We don't want to even inspect the values, so we can use the same
+ * routine for ADD and MODIFY.
+ *
+ */
+
+static int samldb_check_sensitive_attributes(struct samldb_ctx *ac)
+{
+ struct ldb_message_element *el = NULL;
+ struct security_token *user_token = NULL;
+ int ret;
+
+ if (dsdb_module_am_system(ac->module)) {
+ return LDB_SUCCESS;
+ }
+
+ user_token = acl_user_token(ac->module);
+ if (user_token == NULL) {
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+
+ el = ldb_msg_find_element(ac->msg, "sidHistory");
+ if (el) {
+ /*
+ * sidHistory is restricted to the (not implemented
+ * yet in Samba) DsAddSidHistory call (direct LDB access is
+ * as SYSTEM so will bypass this).
+ *
+ * If you want to modify this, say to merge domains,
+ * directly modify the sam.ldb as root.
+ */
+ ldb_asprintf_errstring(ldb_module_get_ctx(ac->module),
+ "sidHistory "
+ "(entry %s) cannot be created "
+ "or changed over LDAP!",
+ ldb_dn_get_linearized(ac->msg->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ el = ldb_msg_find_element(ac->msg, "msDS-SecondaryKrbTgtNumber");
+ if (el) {
+ struct security_descriptor *domain_sd;
+ const struct dsdb_class *objectclass = NULL;
+ /*
+ * msDS-SecondaryKrbTgtNumber allows the creator to
+ * become an RODC, this is trusted as an RODC
+ * account
+ */
+ ret = samldb_get_domain_secdesc_and_oc(ac, &domain_sd, &objectclass);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ ret = acl_check_extended_right(ac,
+ ac->module,
+ ac->req,
+ objectclass,
+ domain_sd,
+ user_token,
+ GUID_DRS_DS_INSTALL_REPLICA,
+ SEC_ADS_CONTROL_ACCESS,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ac->module),
+ "msDS-SecondaryKrbTgtNumber "
+ "(entry %s) cannot be created "
+ "or changed without "
+ "DS-Install-Replica extended right!",
+ ldb_dn_get_linearized(ac->msg->dn));
+ return ret;
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "msDS-AllowedToDelegateTo");
+ if (el) {
+ /*
+ * msDS-AllowedToDelegateTo is incredibly powerful,
+ * given that it allows a server to become ANY USER on
+ * the target server only listed by SPN so needs to be
+ * protected just as the userAccountControl
+ * UF_TRUSTED_FOR_DELEGATION is.
+ */
+
+ bool have_priv = security_token_has_privilege(user_token,
+ SEC_PRIV_ENABLE_DELEGATION);
+ if (have_priv == false) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ac->module),
+ "msDS-AllowedToDelegateTo "
+ "(entry %s) cannot be created "
+ "or changed without SePrivEnableDelegation!",
+ ldb_dn_get_linearized(ac->msg->dn));
+ return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+ }
+ }
+ return LDB_SUCCESS;
+}
+/* add */
+static int samldb_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct samldb_ctx *ac;
+ struct ldb_message_element *el;
+ int ret;
+ char *referral = NULL;
+
+ ldb = ldb_module_get_ctx(module);
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "samldb_add\n");
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ referral = refer_if_rodc(ldb, req, req->op.add.message->dn);
+ if (referral != NULL) {
+ ret = ldb_module_send_referral(req, referral);
+ return ret;
+ }
+
+ el = ldb_msg_find_element(req->op.add.message, "userParameters");
+ if (el != NULL && ldb_req_is_untrusted(req)) {
+ const char *reason = "samldb_add: "
+ "setting userParameters is not supported over LDAP, "
+ "see https://bugzilla.samba.org/show_bug.cgi?id=8077";
+ ldb_debug(ldb, LDB_DEBUG_WARNING, "%s", reason);
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION, reason);
+ }
+
+ ac = samldb_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* build the new msg */
+ ac->msg = ldb_msg_copy_shallow(ac, req->op.add.message);
+ if (ac->msg == NULL) {
+ talloc_free(ac);
+ ldb_debug(ldb, LDB_DEBUG_FATAL,
+ "samldb_add: ldb_msg_copy_shallow failed!\n");
+ return ldb_operr(ldb);
+ }
+
+ ret = samldb_check_sensitive_attributes(ac);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+
+ el = ldb_msg_find_element(ac->msg, "fSMORoleOwner");
+ if (el != NULL) {
+ ret = samldb_fsmo_role_owner_check(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "servicePrincipalName");
+ if ((el != NULL)) {
+ /*
+ * We need to check whether the SPN collides with an existing
+ * one (anywhere) including via aliases.
+ */
+ ret = samldb_spn_uniqueness_check(ac, el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (samdb_find_attribute(ldb, ac->msg,
+ "objectclass", "user") != NULL) {
+ ac->type = SAMLDB_TYPE_USER;
+
+ ret = samldb_prim_group_trigger(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = samldb_objectclass_trigger(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return samldb_fill_object(ac);
+ }
+
+ if (samdb_find_attribute(ldb, ac->msg,
+ "objectclass", "group") != NULL) {
+ ac->type = SAMLDB_TYPE_GROUP;
+
+ ret = samldb_objectclass_trigger(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return samldb_fill_object(ac);
+ }
+
+ /* perhaps a foreignSecurityPrincipal? */
+ if (samdb_find_attribute(ldb, ac->msg,
+ "objectclass",
+ "foreignSecurityPrincipal") != NULL) {
+ return samldb_fill_foreignSecurityPrincipal_object(ac);
+ }
+
+ if (samdb_find_attribute(ldb, ac->msg,
+ "objectclass", "classSchema") != NULL) {
+ ac->type = SAMLDB_TYPE_CLASS;
+
+ /* If in provision, these checks are too slow to do */
+ if (!ldb_request_get_control(req, DSDB_CONTROL_SKIP_DUPLICATES_CHECK_OID)) {
+ ret = samldb_schema_governsid_valid_check(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ ret = samldb_schema_ldapdisplayname_valid_check(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = samldb_schema_info_update(ac);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+
+ return samldb_fill_object(ac);
+ }
+
+ if (samdb_find_attribute(ldb, ac->msg,
+ "objectclass", "attributeSchema") != NULL) {
+ ac->type = SAMLDB_TYPE_ATTRIBUTE;
+
+ /* If in provision, these checks are too slow to do */
+ if (!ldb_request_get_control(req, DSDB_CONTROL_SKIP_DUPLICATES_CHECK_OID)) {
+ ret = samldb_schema_attributeid_valid_check(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = samldb_schema_add_handle_linkid(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = samldb_schema_add_handle_mapiid(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ ret = samldb_schema_ldapdisplayname_valid_check(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = samldb_schema_info_update(ac);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+
+ return samldb_fill_object(ac);
+ }
+
+ if (samdb_find_attribute(ldb, ac->msg,
+ "objectclass", "subnet") != NULL) {
+ ret = samldb_verify_subnet(ac, ac->msg->dn);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ /* We are just checking the value is valid, and there are no
+ values to fill in. */
+ }
+
+ talloc_free(ac);
+
+ /* nothing matched, go on */
+ return ldb_next_request(module, req);
+}
+
+/* modify */
+static int samldb_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct samldb_ctx *ac;
+ struct ldb_message_element *el, *el2;
+ struct ldb_control *is_undelete;
+ bool modified = false;
+ int ret;
+
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ /*
+ * we are going to need some special handling if in Undelete call.
+ * Since tombstone_reanimate module will restore certain attributes,
+ * we need to relax checks for: sAMAccountType, primaryGroupID
+ */
+ is_undelete = ldb_request_get_control(req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID);
+
+ /* make sure that "objectSid" is not specified */
+ el = ldb_msg_find_element(req->op.mod.message, "objectSid");
+ if (el != NULL) {
+ if (ldb_request_get_control(req, LDB_CONTROL_PROVISION_OID) == NULL) {
+ ldb_set_errstring(ldb,
+ "samldb: objectSid must not be specified!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+ if (is_undelete == NULL) {
+ /* make sure that "sAMAccountType" is not specified */
+ el = ldb_msg_find_element(req->op.mod.message, "sAMAccountType");
+ if (el != NULL) {
+ ldb_set_errstring(ldb,
+ "samldb: sAMAccountType must not be specified!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+ /* make sure that "isCriticalSystemObject" is not specified */
+ el = ldb_msg_find_element(req->op.mod.message, "isCriticalSystemObject");
+ if (el != NULL) {
+ if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID) == NULL) {
+ ldb_set_errstring(ldb,
+ "samldb: isCriticalSystemObject must not be specified!");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+
+ /* msDS-IntId is not allowed to be modified
+ * except when modification comes from replication */
+ if (ldb_msg_find_element(req->op.mod.message, "msDS-IntId")) {
+ if (!ldb_request_get_control(req,
+ DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+
+ el = ldb_msg_find_element(req->op.mod.message, "userParameters");
+ if (el != NULL && ldb_req_is_untrusted(req)) {
+ const char *reason = "samldb: "
+ "setting userParameters is not supported over LDAP, "
+ "see https://bugzilla.samba.org/show_bug.cgi?id=8077";
+ ldb_debug(ldb, LDB_DEBUG_WARNING, "%s", reason);
+ return ldb_error(ldb, LDB_ERR_CONSTRAINT_VIOLATION, reason);
+ }
+
+ ac = samldb_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* build the new msg */
+ ac->msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
+ if (ac->msg == NULL) {
+ talloc_free(ac);
+ ldb_debug(ldb, LDB_DEBUG_FATAL,
+ "samldb_modify: ldb_msg_copy_shallow failed!\n");
+ return ldb_operr(ldb);
+ }
+
+ ret = samldb_check_sensitive_attributes(ac);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+
+ if (is_undelete == NULL) {
+ el = ldb_msg_find_element(ac->msg, "primaryGroupID");
+ if (el != NULL) {
+ ret = samldb_prim_group_trigger(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "userAccountControl");
+ if (el != NULL) {
+ modified = true;
+ ret = samldb_user_account_control_change(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "pwdLastSet");
+ if (el != NULL) {
+ modified = true;
+ ret = samldb_pwd_last_set_change(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "lockoutTime");
+ if (el != NULL) {
+ modified = true;
+ ret = samldb_lockout_time(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "groupType");
+ if (el != NULL) {
+ modified = true;
+ ret = samldb_group_type_change(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "sAMAccountName");
+ if (el != NULL) {
+ uint32_t user_account_control;
+ struct ldb_result *res = NULL;
+ const char * const attrs[] = { "userAccountControl",
+ "objectclass",
+ NULL };
+ ret = dsdb_module_search_dn(ac->module,
+ ac,
+ &res,
+ ac->msg->dn,
+ attrs,
+ DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED,
+ ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ user_account_control
+ = ldb_msg_find_attr_as_uint(res->msgs[0],
+ "userAccountControl",
+ 0);
+
+ if ((user_account_control
+ & UF_TRUST_ACCOUNT_MASK) != 0) {
+ ac->need_trailing_dollar = true;
+
+ } else if (samdb_find_attribute(ldb,
+ res->msgs[0],
+ "objectclass",
+ "computer")
+ != NULL) {
+ ac->need_trailing_dollar = true;
+ }
+
+ ret = samldb_sam_accountname_valid_check(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "userPrincipalName");
+ if (el != NULL) {
+ ret = samldb_sam_account_upn_clash(ac);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(ac);
+ return ret;
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "ldapDisplayName");
+ if (el != NULL) {
+ ret = samldb_schema_ldapdisplayname_valid_check(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "attributeID");
+ if (el != NULL) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ac->module),
+ "Once set, attributeID values may not be modified");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ el = ldb_msg_find_element(ac->msg, "governsID");
+ if (el != NULL) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ac->module),
+ "Once set, governsID values may not be modified");
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ el = ldb_msg_find_element(ac->msg, "member");
+ if (el != NULL) {
+ struct ldb_control *fix_link_sid_ctrl = NULL;
+
+ fix_link_sid_ctrl = ldb_request_get_control(ac->req,
+ DSDB_CONTROL_DBCHECK_FIX_LINK_DN_SID);
+ if (fix_link_sid_ctrl == NULL) {
+ ret = samldb_member_check(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "description");
+ if (el != NULL) {
+ ret = samldb_description_check(ac, &modified);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "dNSHostName");
+ el2 = ldb_msg_find_element(ac->msg, "sAMAccountName");
+ if ((el != NULL) || (el2 != NULL)) {
+ modified = true;
+ /*
+ * samldb_service_principal_names_change() might add SPN
+ * changes to the request, so this must come before the SPN
+ * uniqueness check below.
+ *
+ * Note we ALSO have to do the SPN uniqueness check inside
+ * samldb_service_principal_names_change(), because it does a
+ * subrequest to do requested SPN modifications *before* its
+ * automatic ones are added.
+ */
+ ret = samldb_service_principal_names_change(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "servicePrincipalName");
+ if ((el != NULL)) {
+ /*
+ * We need to check whether the SPN collides with an existing
+ * one (anywhere) including via aliases.
+ */
+ modified = true;
+ ret = samldb_spn_uniqueness_check(ac, el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ el = ldb_msg_find_element(ac->msg, "fSMORoleOwner");
+ if (el != NULL) {
+ ret = samldb_fsmo_role_owner_check(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ if (modified) {
+ struct ldb_request *child_req;
+
+ /* Now perform the real modifications as a child request */
+ ret = ldb_build_mod_req(&child_req, ldb, ac,
+ ac->msg,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(child_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, child_req);
+ }
+
+ talloc_free(ac);
+
+ /* no change which interests us, go on */
+ return ldb_next_request(module, req);
+}
+
+/* delete */
+
+static int samldb_prim_group_users_check(struct samldb_ctx *ac)
+{
+ struct ldb_context *ldb;
+ struct dom_sid *sid;
+ uint32_t rid;
+ NTSTATUS status;
+ int ret;
+ struct ldb_result *res = NULL;
+ struct ldb_result *res_users = NULL;
+ const char * const attrs[] = { "objectSid", "isDeleted", NULL };
+ const char * const noattrs[] = { NULL };
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ /* Finds out the SID/RID of the SAM object */
+ ret = dsdb_module_search_dn(ac->module, ac, &res, ac->req->op.del.dn,
+ attrs,
+ DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_DELETED,
+ ac->req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (ldb_msg_check_string_attribute(res->msgs[0], "isDeleted", "TRUE")) {
+ return LDB_SUCCESS;
+ }
+
+ sid = samdb_result_dom_sid(ac, res->msgs[0], "objectSid");
+ if (sid == NULL) {
+ /* No SID - it might not be a SAM object - therefore ok */
+ return LDB_SUCCESS;
+ }
+ status = dom_sid_split_rid(ac, sid, NULL, &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ldb_operr(ldb);
+ }
+ if (rid == 0) {
+ /* Special object (security principal?) */
+ return LDB_SUCCESS;
+ }
+ /* do not allow deletion of well-known sids */
+ if (rid < DSDB_SAMDB_MINIMUM_ALLOWED_RID &&
+ (ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) == NULL)) {
+ return LDB_ERR_OTHER;
+ }
+
+ /* Deny delete requests from groups which are primary ones */
+ ret = dsdb_module_search(ac->module, ac, &res_users,
+ ldb_get_default_basedn(ldb),
+ LDB_SCOPE_SUBTREE, noattrs,
+ DSDB_FLAG_NEXT_MODULE,
+ ac->req,
+ "(&(primaryGroupID=%u)(objectClass=user))", rid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ if (res_users->count > 0) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ac->module),
+ "Refusing to delete %s, as it "
+ "is still the primaryGroupID "
+ "for %u users",
+ ldb_dn_get_linearized(res->msgs[0]->dn),
+ res_users->count);
+
+ /*
+ * Yes, this seems very wrong, but we have a test
+ * for this exact error code in sam.py
+ */
+ return LDB_ERR_ENTRY_ALREADY_EXISTS;
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int samldb_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ struct samldb_ctx *ac;
+ char *referral = NULL;
+ int ret;
+ struct ldb_context *ldb;
+
+ if (ldb_dn_is_special(req->op.del.dn)) {
+ /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ referral = refer_if_rodc(ldb, req, req->op.del.dn);
+ if (referral != NULL) {
+ ret = ldb_module_send_referral(req, referral);
+ return ret;
+ }
+
+ ac = samldb_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ ret = samldb_prim_group_users_check(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ talloc_free(ac);
+
+ return ldb_next_request(module, req);
+}
+
+/* rename */
+
+static int check_rename_constraints(struct ldb_message *msg,
+ struct samldb_ctx *ac,
+ struct ldb_dn *olddn, struct ldb_dn *newdn)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ struct ldb_dn *dn1, *dn2, *nc_root;
+ int32_t systemFlags;
+ bool move_op = false;
+ bool rename_op = false;
+ int ret;
+
+ /* Skip the checks if old and new DN are the same, or if we have the
+ * relax control specified or if the returned objects is already
+ * deleted and needs only to be moved for consistency. */
+
+ if (ldb_dn_compare(olddn, newdn) == 0) {
+ return LDB_SUCCESS;
+ }
+ if (ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) != NULL) {
+ return LDB_SUCCESS;
+ }
+
+ if (ldb_msg_find_attr_as_bool(msg, "isDeleted", false)) {
+ /*
+ * check originating request if we are supposed
+ * to "see" this record in first place.
+ */
+ if (ldb_request_get_control(ac->req, LDB_CONTROL_SHOW_DELETED_OID) == NULL) {
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Objects under CN=System */
+
+ dn1 = samdb_system_container_dn(ldb, ac);
+ if (dn1 == NULL) return ldb_oom(ldb);
+
+ if ((ldb_dn_compare_base(dn1, olddn) == 0) &&
+ (ldb_dn_compare_base(dn1, newdn) != 0)) {
+ talloc_free(dn1);
+ ldb_asprintf_errstring(ldb,
+ "subtree_rename: Cannot move/rename %s. Objects under CN=System have to stay under it!",
+ ldb_dn_get_linearized(olddn));
+ return LDB_ERR_OTHER;
+ }
+
+ talloc_free(dn1);
+
+ /* LSA objects */
+
+ if ((samdb_find_attribute(ldb, msg, "objectClass", "secret") != NULL) ||
+ (samdb_find_attribute(ldb, msg, "objectClass", "trustedDomain") != NULL)) {
+ ldb_asprintf_errstring(ldb,
+ "subtree_rename: Cannot move/rename %s. It's an LSA-specific object!",
+ ldb_dn_get_linearized(olddn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* subnet objects */
+ if (samdb_find_attribute(ldb, msg, "objectclass", "subnet") != NULL) {
+ ret = samldb_verify_subnet(ac, newdn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ /* systemFlags */
+
+ dn1 = ldb_dn_get_parent(ac, olddn);
+ if (dn1 == NULL) return ldb_oom(ldb);
+ dn2 = ldb_dn_get_parent(ac, newdn);
+ if (dn2 == NULL) return ldb_oom(ldb);
+
+ if (ldb_dn_compare(dn1, dn2) == 0) {
+ rename_op = true;
+ } else {
+ move_op = true;
+ }
+
+ talloc_free(dn1);
+ talloc_free(dn2);
+
+ systemFlags = ldb_msg_find_attr_as_int(msg, "systemFlags", 0);
+
+ /* Fetch name context */
+
+ ret = dsdb_find_nc_root(ldb, ac, olddn, &nc_root);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (ldb_dn_compare(nc_root, ldb_get_schema_basedn(ldb)) == 0) {
+ if (move_op) {
+ ldb_asprintf_errstring(ldb,
+ "subtree_rename: Cannot move %s within schema partition",
+ ldb_dn_get_linearized(olddn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (rename_op &&
+ (systemFlags & SYSTEM_FLAG_SCHEMA_BASE_OBJECT) != 0) {
+ ldb_asprintf_errstring(ldb,
+ "subtree_rename: Cannot rename %s within schema partition",
+ ldb_dn_get_linearized(olddn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ } else if (ldb_dn_compare(nc_root, ldb_get_config_basedn(ldb)) == 0) {
+ if (move_op &&
+ (systemFlags & SYSTEM_FLAG_CONFIG_ALLOW_MOVE) == 0) {
+ /* Here we have to do more: control the
+ * "ALLOW_LIMITED_MOVE" flag. This means that the
+ * grand-grand-parents of two objects have to be equal
+ * in order to perform the move (this is used for
+ * moving "server" objects in the "sites" container). */
+ bool limited_move =
+ systemFlags & SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE;
+
+ if (limited_move) {
+ dn1 = ldb_dn_copy(ac, olddn);
+ if (dn1 == NULL) return ldb_oom(ldb);
+ dn2 = ldb_dn_copy(ac, newdn);
+ if (dn2 == NULL) return ldb_oom(ldb);
+
+ limited_move &= ldb_dn_remove_child_components(dn1, 3);
+ limited_move &= ldb_dn_remove_child_components(dn2, 3);
+ limited_move &= ldb_dn_compare(dn1, dn2) == 0;
+
+ talloc_free(dn1);
+ talloc_free(dn2);
+ }
+
+ if (!limited_move
+ && ldb_request_get_control(ac->req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID) == NULL) {
+ ldb_asprintf_errstring(ldb,
+ "subtree_rename: Cannot move %s to %s in config partition",
+ ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+ if (rename_op &&
+ (systemFlags & SYSTEM_FLAG_CONFIG_ALLOW_RENAME) == 0) {
+ ldb_asprintf_errstring(ldb,
+ "subtree_rename: Cannot rename %s to %s within config partition",
+ ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ } else if (ldb_dn_compare(nc_root, ldb_get_default_basedn(ldb)) == 0) {
+ if (move_op &&
+ (systemFlags & SYSTEM_FLAG_DOMAIN_DISALLOW_MOVE) != 0) {
+ ldb_asprintf_errstring(ldb,
+ "subtree_rename: Cannot move %s to %s - DISALLOW_MOVE set",
+ ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ if (rename_op &&
+ (systemFlags & SYSTEM_FLAG_DOMAIN_DISALLOW_RENAME) != 0) {
+ ldb_asprintf_errstring(ldb,
+ "subtree_rename: Cannot rename %s to %s - DISALLOW_RENAME set",
+ ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+
+ talloc_free(nc_root);
+
+ return LDB_SUCCESS;
+}
+
+
+static int samldb_rename_search_base_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct samldb_ctx *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct samldb_ctx);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ /*
+ * This is the root entry of the originating move
+ * respectively rename request. It has been already
+ * stored in the list using "subtree_rename_search()".
+ * Only this one is subject to constraint checking.
+ */
+ ret = check_rename_constraints(ares->message, ac,
+ ac->req->op.rename.olddn,
+ ac->req->op.rename.newdn);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ ret);
+ }
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+
+ /*
+ * Great, no problem with the rename, so go ahead as
+ * if we never were here
+ */
+ ret = ldb_next_request(ac->module, ac->req);
+ talloc_free(ares);
+ return ret;
+ }
+
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+
+/* rename */
+static int samldb_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ static const char * const attrs[] = { "objectClass", "systemFlags",
+ "isDeleted", NULL };
+ struct ldb_request *search_req;
+ struct samldb_ctx *ac;
+ int ret;
+
+ if (ldb_dn_is_special(req->op.rename.olddn)) { /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = samldb_ctx_init(module, req);
+ if (!ac) {
+ return ldb_oom(ldb);
+ }
+
+ ret = ldb_build_search_req(&search_req, ldb, ac,
+ req->op.rename.olddn,
+ LDB_SCOPE_BASE,
+ "(objectClass=*)",
+ attrs,
+ NULL,
+ ac,
+ samldb_rename_search_base_callback,
+ req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_request_add_control(search_req, LDB_CONTROL_SHOW_RECYCLED_OID,
+ true, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ac->module, search_req);
+}
+
+/* extended */
+
+static int samldb_extended_allocate_rid_pool(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct dsdb_fsmo_extended_op *exop;
+ int ret;
+
+ exop = talloc_get_type(req->op.extended.data,
+ struct dsdb_fsmo_extended_op);
+ if (!exop) {
+ ldb_set_errstring(ldb,
+ "samldb_extended_allocate_rid_pool: invalid extended data");
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ ret = ridalloc_allocate_rid_pool_fsmo(module, exop, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
+}
+
+static int samldb_extended_allocate_rid(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct dsdb_extended_allocate_rid *exop;
+ int ret;
+
+ exop = talloc_get_type(req->op.extended.data,
+ struct dsdb_extended_allocate_rid);
+ if (!exop) {
+ ldb_set_errstring(ldb,
+ "samldb_extended_allocate_rid: invalid extended data");
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ ret = ridalloc_allocate_rid(module, &exop->rid, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
+}
+
+static int samldb_extended_create_own_rid_set(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ int ret;
+ struct ldb_dn *dn;
+
+ if (req->op.extended.data != NULL) {
+ ldb_set_errstring(ldb,
+ "samldb_extended_create_own_rid_set: invalid extended data (should be NULL)");
+ return LDB_ERR_PROTOCOL_ERROR;
+ }
+
+ ret = ridalloc_create_own_rid_set(module, req,
+ &dn, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
+}
+
+static int samldb_extended(struct ldb_module *module, struct ldb_request *req)
+{
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_ALLOCATE_RID_POOL) == 0) {
+ return samldb_extended_allocate_rid_pool(module, req);
+ }
+
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_ALLOCATE_RID) == 0) {
+ return samldb_extended_allocate_rid(module, req);
+ }
+
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_CREATE_OWN_RID_SET) == 0) {
+ return samldb_extended_create_own_rid_set(module, req);
+ }
+
+ return ldb_next_request(module, req);
+}
+
+
+static const struct ldb_module_ops ldb_samldb_module_ops = {
+ .name = "samldb",
+ .add = samldb_add,
+ .modify = samldb_modify,
+ .del = samldb_delete,
+ .rename = samldb_rename,
+ .extended = samldb_extended
+};
+
+
+int ldb_samldb_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_samldb_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/schema_data.c b/source4/dsdb/samdb/ldb_modules/schema_data.c
new file mode 100644
index 0000000..697ce21
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/schema_data.c
@@ -0,0 +1,691 @@
+/*
+ Unix SMB/CIFS Implementation.
+
+ The module that handles the Schema checkings and dynamic attributes
+
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+#undef strcasecmp
+
+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 int generate_extendedAttributeInfo(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema);
+static int generate_extendedClassInfo(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema);
+static int generate_possibleInferiors(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 *);
+ bool aggregate;
+} generated_attrs[] = {
+ {
+ .attr = "objectClasses",
+ .fn = generate_objectClasses,
+ .aggregate = true,
+ },
+ {
+ .attr = "attributeTypes",
+ .fn = generate_attributeTypes,
+ .aggregate = true,
+ },
+ {
+ .attr = "dITContentRules",
+ .fn = generate_dITContentRules,
+ .aggregate = true,
+ },
+ {
+ .attr = "extendedAttributeInfo",
+ .fn = generate_extendedAttributeInfo,
+ .aggregate = true,
+ },
+ {
+ .attr = "extendedClassInfo",
+ .fn = generate_extendedClassInfo,
+ .aggregate = true,
+ },
+ {
+ .attr = "possibleInferiors",
+ .fn = generate_possibleInferiors,
+ .aggregate = false,
+ }
+};
+
+struct schema_data_private_data {
+ struct ldb_dn *aggregate_dn;
+ struct ldb_dn *schema_dn;
+};
+
+struct schema_data_search_data {
+ struct ldb_module *module;
+ struct ldb_request *req;
+
+ const struct dsdb_schema *schema;
+};
+
+static int schema_data_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb;
+ struct ldb_dn *schema_dn;
+ int ret;
+ struct schema_data_private_data *data;
+
+ ret = ldb_next_init(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ldb = ldb_module_get_ctx(module);
+ schema_dn = ldb_get_schema_basedn(ldb);
+ if (!schema_dn) {
+ ldb_reset_err_string(ldb);
+ ldb_debug(ldb, LDB_DEBUG_WARNING,
+ "schema_data_init: no schema dn present: (skip schema loading)\n");
+ return LDB_SUCCESS;
+ }
+
+ data = talloc(module, struct schema_data_private_data);
+ if (data == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ data->schema_dn = schema_dn;
+
+ /* Used to check to see if this is a result on the CN=Aggregate schema */
+ data->aggregate_dn = samdb_aggregate_schema_dn(ldb, data);
+ if (!data->aggregate_dn) {
+ ldb_asprintf_errstring(ldb, "schema_data_init: Could not build aggregate schema DN for schema in %s", ldb_dn_get_linearized(schema_dn));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ldb_module_set_private(module, data);
+ return LDB_SUCCESS;
+}
+
+static int schema_data_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct dsdb_schema *schema;
+ const struct ldb_val *attributeID = NULL;
+ const struct ldb_val *governsID = NULL;
+ const char *oid_attr = NULL;
+ const char *oid = NULL;
+ struct ldb_dn *parent_dn = NULL;
+ int cmp;
+ WERROR status;
+ bool rodc = false;
+ int ret;
+ struct schema_data_private_data *mc;
+ mc = talloc_get_type(ldb_module_get_private(module), struct schema_data_private_data);
+
+ ldb = ldb_module_get_ctx(module);
+
+ /* special objects should always go through */
+ if (ldb_dn_is_special(req->op.add.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* replicated update should always go through */
+ if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
+ return ldb_next_request(module, req);
+ }
+
+ schema = dsdb_get_schema(ldb, req);
+ if (!schema) {
+ return ldb_next_request(module, req);
+ }
+
+ ret = samdb_rodc(ldb, &rodc);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(4, (__location__ ": unable to tell if we are an RODC \n"));
+ }
+
+ if (!schema->fsmo.we_are_master && !rodc) {
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ "schema_data_add: we are not master: reject add request\n");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ if (!schema->fsmo.update_allowed && !rodc) {
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ "schema_data_add: updates are not allowed: reject add request\n");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) {
+ /*
+ * the provision code needs to create
+ * the schema root object.
+ */
+ cmp = ldb_dn_compare(req->op.add.message->dn, mc->schema_dn);
+ if (cmp == 0) {
+ return ldb_next_request(module, req);
+ }
+ }
+
+ parent_dn = ldb_dn_get_parent(req, req->op.add.message->dn);
+ if (!parent_dn) {
+ return ldb_oom(ldb);
+ }
+
+ cmp = ldb_dn_compare(parent_dn, mc->schema_dn);
+ if (cmp != 0) {
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ "schema_data_add: no direct child :%s\n",
+ ldb_dn_get_linearized(req->op.add.message->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ attributeID = ldb_msg_find_ldb_val(req->op.add.message, "attributeID");
+ governsID = ldb_msg_find_ldb_val(req->op.add.message, "governsID");
+
+ if (attributeID) {
+ oid_attr = "attributeID";
+ oid = talloc_strndup(req, (const char *)attributeID->data, attributeID->length);
+ } else if (governsID) {
+ oid_attr = "governsID";
+ oid = talloc_strndup(req, (const char *)governsID->data, governsID->length);
+ } else {
+ return ldb_next_request(module, req);
+ }
+
+ if (!oid) {
+ return ldb_oom(ldb);
+ }
+
+ status = dsdb_schema_pfm_find_oid(schema->prefixmap, oid, NULL);
+ if (!W_ERROR_IS_OK(status)) {
+ /* check for internal errors */
+ if (!W_ERROR_EQUAL(status, WERR_NOT_FOUND)) {
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ "schema_data_add: failed to map %s[%s]: %s\n",
+ oid_attr, oid, win_errstr(status));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /* Update prefixMap and save it */
+ status = dsdb_create_prefix_mapping(ldb, schema, oid);
+ if (!W_ERROR_IS_OK(status)) {
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ "schema_data_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_data_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct dsdb_schema *schema;
+ int cmp;
+ bool rodc = false;
+ int ret;
+ struct ldb_control *sd_propagation_control;
+ struct schema_data_private_data *mc;
+ mc = talloc_get_type(ldb_module_get_private(module), struct schema_data_private_data);
+
+ ldb = ldb_module_get_ctx(module);
+
+ /* special objects should always go through */
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* replicated update should always go through */
+ if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* dbcheck should be able to fix things */
+ if (ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) {
+ return ldb_next_request(module, req);
+ }
+
+ sd_propagation_control = ldb_request_get_control(req,
+ DSDB_CONTROL_SEC_DESC_PROPAGATION_OID);
+ if (sd_propagation_control != NULL) {
+ if (req->op.mod.message->num_elements != 1) {
+ return ldb_module_operr(module);
+ }
+ ret = strcmp(req->op.mod.message->elements[0].name,
+ "nTSecurityDescriptor");
+ if (ret != 0) {
+ return ldb_module_operr(module);
+ }
+
+ return ldb_next_request(module, req);
+ }
+
+ schema = dsdb_get_schema(ldb, req);
+ if (!schema) {
+ return ldb_next_request(module, req);
+ }
+
+ cmp = ldb_dn_compare(req->op.mod.message->dn, mc->schema_dn);
+ if (cmp == 0) {
+ static const char * const constrained_attrs[] = {
+ "schemaInfo",
+ "prefixMap",
+ "msDs-Schema-Extensions",
+ "msDS-IntId",
+ NULL
+ };
+ size_t i;
+ struct ldb_message_element *el;
+
+ if (ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID)) {
+ return ldb_next_request(module, req);
+ }
+
+ for (i=0; constrained_attrs[i]; i++) {
+ el = ldb_msg_find_element(req->op.mod.message,
+ constrained_attrs[i]);
+ if (el == NULL) {
+ continue;
+ }
+
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ "schema_data_modify: reject update "
+ "of attribute[%s]\n",
+ constrained_attrs[i]);
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ return ldb_next_request(module, req);
+ }
+
+ ret = samdb_rodc(ldb, &rodc);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(4, (__location__ ": unable to tell if we are an RODC \n"));
+ }
+
+ if (!schema->fsmo.we_are_master && !rodc) {
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ "schema_data_modify: we are not master: reject modify request\n");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ if (!schema->fsmo.update_allowed && !rodc) {
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ "schema_data_modify: updates are not allowed: reject modify request\n");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ return ldb_next_request(module, req);
+}
+
+static int schema_data_del(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct dsdb_schema *schema;
+ bool rodc = false;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ /* special objects should always go through */
+ if (ldb_dn_is_special(req->op.del.dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* replicated update should always go through */
+ if (ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* dbcheck should be able to fix things */
+ if (ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) {
+ return ldb_next_request(module, req);
+ }
+
+ schema = dsdb_get_schema(ldb, req);
+ if (!schema) {
+ return ldb_next_request(module, req);
+ }
+
+ ret = samdb_rodc(ldb, &rodc);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(4, (__location__ ": unable to tell if we are an RODC \n"));
+ }
+
+ if (!schema->fsmo.we_are_master && !rodc) {
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ "schema_data_modify: we are not master: reject request\n");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ /*
+ * normaly the DACL will prevent delete
+ * with LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS
+ * above us.
+ */
+ ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+ "schema_data_del: delete is not allowed in the schema\n");
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+}
+
+static int generate_objectClasses(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema)
+{
+ const struct dsdb_class *sclass;
+ int ret;
+
+ for (sclass = schema->classes; sclass; sclass = sclass->next) {
+ char *v = schema_class_to_description(msg, sclass);
+ if (v == NULL) {
+ return ldb_oom(ldb);
+ }
+ ret = ldb_msg_add_steal_string(msg, "objectClasses", v);
+ 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) {
+ char *v = schema_attribute_to_description(msg, attribute);
+ if (v == NULL) {
+ return ldb_oom(ldb);
+ }
+ ret = ldb_msg_add_steal_string(msg, "attributeTypes", v);
+ 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 *sclass;
+ int ret;
+
+ for (sclass = schema->classes; sclass; sclass = sclass->next) {
+ if (sclass->auxiliaryClass || sclass->systemAuxiliaryClass) {
+ char *ditcontentrule = schema_class_to_dITContentRule(msg, sclass, schema);
+ if (!ditcontentrule) {
+ return ldb_oom(ldb);
+ }
+ ret = ldb_msg_add_steal_string(msg, "dITContentRules", ditcontentrule);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+ return LDB_SUCCESS;
+}
+
+static int generate_extendedAttributeInfo(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) {
+ char *val = schema_attribute_to_extendedInfo(msg, attribute);
+ if (!val) {
+ return ldb_oom(ldb);
+ }
+
+ ret = ldb_msg_add_steal_string(msg, "extendedAttributeInfo", val);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int generate_extendedClassInfo(struct ldb_context *ldb,
+ struct ldb_message *msg,
+ const struct dsdb_schema *schema)
+{
+ const struct dsdb_class *sclass;
+ int ret;
+
+ for (sclass = schema->classes; sclass; sclass = sclass->next) {
+ char *val = schema_class_to_extendedInfo(msg, sclass);
+ if (!val) {
+ return ldb_oom(ldb);
+ }
+
+ ret = ldb_msg_add_steal_string(msg, "extendedClassInfo", val);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+static int generate_possibleInferiors(struct ldb_context *ldb, struct ldb_message *msg,
+ const struct dsdb_schema *schema)
+{
+ struct ldb_dn *dn = msg->dn;
+ unsigned int i;
+ int ret;
+ const char *first_component_name = ldb_dn_get_component_name(dn, 0);
+ const struct ldb_val *first_component_val;
+ const struct dsdb_class *schema_class;
+ const char **possibleInferiors;
+
+ if (strcasecmp(first_component_name, "cn") != 0) {
+ return LDB_SUCCESS;
+ }
+
+ first_component_val = ldb_dn_get_component_val(dn, 0);
+
+ schema_class = dsdb_class_by_cn_ldb_val(schema, first_component_val);
+ if (schema_class == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ possibleInferiors = schema_class->possibleInferiors;
+ if (possibleInferiors == NULL) {
+ return LDB_SUCCESS;
+ }
+
+ for (i=0;possibleInferiors[i];i++) {
+ char *v = talloc_strdup(msg, possibleInferiors[i]);
+ if (v == NULL) {
+ return ldb_oom(ldb);
+ }
+ ret = ldb_msg_add_steal_string(msg, "possibleInferiors", v);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+/* Add objectClasses, attributeTypes and dITContentRules from the
+ schema object (they are not stored in the database)
+ */
+static int schema_data_search_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct schema_data_search_data *ac;
+ struct schema_data_private_data *mc;
+ unsigned int i;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct schema_data_search_data);
+ mc = talloc_get_type(ldb_module_get_private(ac->module), struct schema_data_private_data);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+ /* Only entries are interesting, and we handle the case of the parent seperatly */
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+
+ if (ldb_dn_compare(ares->message->dn, mc->aggregate_dn) == 0) {
+ for (i=0; i < ARRAY_SIZE(generated_attrs); i++) {
+ if (generated_attrs[i].aggregate &&
+ ldb_attr_in_list(ac->req->op.search.attrs, generated_attrs[i].attr)) {
+ ret = generated_attrs[i].fn(ldb, ares->message, ac->schema);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+ } else if ((ldb_dn_compare_base(mc->schema_dn, ares->message->dn) == 0)
+ && (ldb_dn_compare(mc->schema_dn, ares->message->dn) != 0)) {
+ for (i=0; i < ARRAY_SIZE(generated_attrs); i++) {
+ if (!generated_attrs[i].aggregate &&
+ ldb_attr_in_list(ac->req->op.search.attrs, generated_attrs[i].attr)) {
+ ret = generated_attrs[i].fn(ldb, ares->message, ac->schema);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+ }
+ }
+
+
+ return ldb_module_send_entry(ac->req, ares->message, ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+
+ return ldb_module_send_referral(ac->req, ares->referral);
+
+ case LDB_REPLY_DONE:
+
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ return LDB_SUCCESS;
+}
+
+/* search */
+static int schema_data_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ unsigned int i;
+ int ret;
+ struct schema_data_search_data *search_context;
+ struct ldb_request *down_req;
+ const struct dsdb_schema *schema;
+ if (!ldb_module_get_private(module)) {
+ /* If there is no module data, there is little we can do */
+ return ldb_next_request(module, req);
+ }
+
+ /* The schema manipulation does not apply to special DNs */
+ if (ldb_dn_is_special(req->op.search.base)) {
+ 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);
+ }
+
+ schema = dsdb_get_schema(ldb, NULL);
+ if (!schema || !ldb_module_get_private(module)) {
+ /* If there is no schema, there is little we can do */
+ return ldb_next_request(module, req);
+ }
+
+ search_context = talloc(req, struct schema_data_search_data);
+ if (!search_context) {
+ return ldb_oom(ldb);
+ }
+
+ search_context->module = module;
+ search_context->req = req;
+ search_context->schema = talloc_reference(search_context, schema);
+ if (!search_context->schema) {
+ return ldb_oom(ldb);
+ }
+
+ ret = ldb_build_search_req_ex(&down_req, ldb, search_context,
+ req->op.search.base,
+ req->op.search.scope,
+ req->op.search.tree,
+ req->op.search.attrs,
+ req->controls,
+ search_context, schema_data_search_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+
+ return ldb_next_request(module, down_req);
+}
+
+
+static const struct ldb_module_ops ldb_schema_data_module_ops = {
+ .name = "schema_data",
+ .init_context = schema_data_init,
+ .add = schema_data_add,
+ .modify = schema_data_modify,
+ .del = schema_data_del,
+ .search = schema_data_search
+};
+
+int ldb_schema_data_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_schema_data_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/schema_load.c b/source4/dsdb/samdb/ldb_modules/schema_load.c
new file mode 100644
index 0000000..13bad83
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/schema_load.c
@@ -0,0 +1,657 @@
+/*
+ Unix SMB/CIFS Implementation.
+
+ The module that handles the Schema FSMO Role Owner
+ checkings, it also loads the dsdb_schema.
+
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009-2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/samdb.h"
+#include "librpc/gen_ndr/ndr_misc.h"
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+#include <tdb.h>
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "lib/ldb-samba/ldb_wrap.h"
+#include "lib/util/smb_strtox.h"
+
+#include "system/filesys.h"
+struct schema_load_private_data {
+ struct ldb_module *module;
+ uint64_t in_transaction;
+ uint64_t in_read_transaction;
+ struct tdb_wrap *metadata;
+ uint64_t schema_seq_num_cache;
+ int tdb_seqnum;
+
+ /*
+ * Please write out the updated schema on the next transaction
+ * start
+ */
+ bool need_write;
+};
+
+static int dsdb_schema_from_db(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ uint64_t schema_seq_num,
+ struct dsdb_schema **schema);
+
+/*
+ * Open sam.ldb.d/metadata.tdb.
+ */
+static int schema_metadata_open(struct ldb_module *module)
+{
+ struct schema_load_private_data *data = talloc_get_type(ldb_module_get_private(module), struct schema_load_private_data);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx;
+ struct loadparm_context *lp_ctx;
+ char *filename;
+ int open_flags;
+ struct stat statbuf;
+
+ if (!data) {
+ return ldb_module_error(module, LDB_ERR_OPERATIONS_ERROR,
+ "schema_load: metadata not initialized");
+ }
+ data->metadata = NULL;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ filename = ldb_relative_path(ldb,
+ tmp_ctx,
+ "sam.ldb.d/metadata.tdb");
+ if (filename == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+
+ open_flags = O_RDWR;
+ if (stat(filename, &statbuf) != 0) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ lp_ctx = talloc_get_type_abort(ldb_get_opaque(ldb, "loadparm"),
+ struct loadparm_context);
+
+ data->metadata = tdb_wrap_open(data, filename, 10,
+ lpcfg_tdb_flags(lp_ctx, TDB_DEFAULT|TDB_SEQNUM),
+ open_flags, 0660);
+ if (data->metadata == NULL) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+static int schema_metadata_get_uint64(struct schema_load_private_data *data,
+ const char *key, uint64_t *value,
+ uint64_t default_value)
+{
+ struct tdb_context *tdb;
+ TDB_DATA tdb_key, tdb_data;
+ char *value_str;
+ TALLOC_CTX *tmp_ctx;
+ int tdb_seqnum;
+ int error = 0;
+
+ if (!data) {
+ *value = default_value;
+ return LDB_SUCCESS;
+ }
+
+ if (!data->metadata) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ tdb_seqnum = tdb_get_seqnum(data->metadata->tdb);
+ if (tdb_seqnum == data->tdb_seqnum) {
+ *value = data->schema_seq_num_cache;
+ return LDB_SUCCESS;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return ldb_module_oom(data->module);
+ }
+
+ tdb = data->metadata->tdb;
+
+ tdb_key.dptr = (uint8_t *)discard_const_p(char, key);
+ tdb_key.dsize = strlen(key);
+
+ tdb_data = tdb_fetch(tdb, tdb_key);
+ if (!tdb_data.dptr) {
+ if (tdb_error(tdb) == TDB_ERR_NOEXIST) {
+ *value = default_value;
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+ } else {
+ talloc_free(tmp_ctx);
+ return ldb_module_error(data->module, LDB_ERR_OPERATIONS_ERROR,
+ tdb_errorstr(tdb));
+ }
+ }
+
+ value_str = talloc_strndup(tmp_ctx, (char *)tdb_data.dptr, tdb_data.dsize);
+ if (value_str == NULL) {
+ SAFE_FREE(tdb_data.dptr);
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(data->module);
+ }
+
+ /*
+ * Now store it in the cache. We don't mind that tdb_seqnum
+ * may be stale now, that just means the cache won't be used
+ * next time
+ */
+ data->tdb_seqnum = tdb_seqnum;
+ data->schema_seq_num_cache = smb_strtoull(value_str,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ talloc_free(tmp_ctx);
+ return ldb_module_error(data->module, LDB_ERR_OPERATIONS_ERROR,
+ "Failed to convert value");
+ }
+
+ *value = data->schema_seq_num_cache;
+
+ SAFE_FREE(tdb_data.dptr);
+ talloc_free(tmp_ctx);
+
+ return LDB_SUCCESS;
+}
+
+static struct dsdb_schema *dsdb_schema_refresh(struct ldb_module *module, struct tevent_context *ev,
+ struct dsdb_schema *schema, bool is_global_schema)
+{
+ TALLOC_CTX *mem_ctx;
+ uint64_t schema_seq_num = 0;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct dsdb_schema *new_schema;
+
+ struct schema_load_private_data *private_data = talloc_get_type(ldb_module_get_private(module), struct schema_load_private_data);
+ if (!private_data) {
+ /* We can't refresh until the init function has run */
+ return schema;
+ }
+
+ if (schema != NULL) {
+ /*
+ * If we have a schema already (not in the startup)
+ * and we are in a read or write transaction, then
+ * avoid a schema reload, it can't have changed
+ */
+ if (private_data->in_transaction > 0
+ || private_data->in_read_transaction > 0 ) {
+ /*
+ * If the refresh is not an expected part of a
+ * larger transaction, then we don't allow a
+ * schema reload during a transaction. This
+ * stops others from modifying our schema
+ * behind our backs
+ */
+ if (ldb_get_opaque(ldb,
+ "dsdb_schema_refresh_expected")
+ != (void *)1) {
+ return schema;
+ }
+ }
+ }
+
+ SMB_ASSERT(ev == ldb_get_event_context(ldb));
+
+ mem_ctx = talloc_new(module);
+ if (mem_ctx == NULL) {
+ return NULL;
+ }
+
+ /*
+ * We update right now the last refresh timestamp so that if
+ * the schema partition hasn't change we don't keep on retrying.
+ * Otherwise if the timestamp was update only when the schema has
+ * actually changed (and therefor completely reloaded) we would
+ * continue to hit the database to get the highest USN.
+ */
+
+ ret = schema_metadata_get_uint64(private_data,
+ DSDB_METADATA_SCHEMA_SEQ_NUM,
+ &schema_seq_num, 0);
+
+ if (schema != NULL) {
+ if (ret == LDB_SUCCESS) {
+ if (schema->metadata_usn == schema_seq_num) {
+ TALLOC_FREE(mem_ctx);
+ return schema;
+ } else {
+ DEBUG(3, ("Schema refresh needed %lld != %lld\n",
+ (unsigned long long)schema->metadata_usn,
+ (unsigned long long)schema_seq_num));
+ }
+ } else {
+ /* From an old provision it can happen that the tdb didn't exists yet */
+ DEBUG(0, ("Error while searching for the schema usn in the metadata ignoring: %d:%s:%s\n",
+ ret, ldb_strerror(ret), ldb_errstring(ldb)));
+ TALLOC_FREE(mem_ctx);
+ return schema;
+ }
+ } else {
+ DEBUG(10, ("Initial schema load needed, as we have no existing schema, seq_num: %lld\n",
+ (unsigned long long)schema_seq_num));
+ }
+
+ ret = dsdb_schema_from_db(module, mem_ctx, schema_seq_num, &new_schema);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "dsdb_schema_from_db() failed: %d:%s: %s",
+ ret, ldb_strerror(ret), ldb_errstring(ldb));
+ TALLOC_FREE(mem_ctx);
+ return schema;
+ }
+
+ ret = dsdb_set_schema(ldb, new_schema, SCHEMA_MEMORY_ONLY);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "dsdb_set_schema() failed: %d:%s: %s",
+ ret, ldb_strerror(ret), ldb_errstring(ldb));
+ TALLOC_FREE(mem_ctx);
+ return schema;
+ }
+ if (is_global_schema) {
+ dsdb_make_schema_global(ldb, new_schema);
+ }
+ TALLOC_FREE(mem_ctx);
+ return new_schema;
+}
+
+
+/*
+ Given an LDB module (pointing at the schema DB), and the DN, set the populated schema
+*/
+
+static int dsdb_schema_from_db(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ uint64_t schema_seq_num,
+ struct dsdb_schema **schema)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx;
+ char *error_string;
+ int ret, i;
+ struct ldb_dn *schema_dn = ldb_get_schema_basedn(ldb);
+ struct ldb_result *res;
+ struct ldb_message *schema_msg = NULL;
+ static const char *schema_attrs[] = {
+ DSDB_SCHEMA_COMMON_ATTRS,
+ DSDB_SCHEMA_ATTR_ATTRS,
+ DSDB_SCHEMA_CLASS_ATTRS,
+ "prefixMap",
+ "schemaInfo",
+ "fSMORoleOwner",
+ NULL
+ };
+ unsigned flags;
+
+ tmp_ctx = talloc_new(module);
+ if (!tmp_ctx) {
+ return ldb_oom(ldb);
+ }
+
+ /* we don't want to trace the schema load */
+ flags = ldb_get_flags(ldb);
+ ldb_set_flags(ldb, flags & ~LDB_FLG_ENABLE_TRACING);
+
+ /*
+ * Load the attribute and class definitions, as well as
+ * the schema object. We do this in one search and then
+ * split it so that there isn't a race condition when
+ * the schema is changed between two searches.
+ */
+ ret = dsdb_module_search(module, tmp_ctx, &res,
+ schema_dn, LDB_SCOPE_SUBTREE,
+ schema_attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ NULL,
+ "(|(objectClass=attributeSchema)"
+ "(objectClass=classSchema)"
+ "(objectClass=dMD))");
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "dsdb_schema: failed to search attributeSchema and classSchema objects: %s",
+ ldb_errstring(ldb));
+ goto failed;
+ }
+
+ /*
+ * Separate the schema object from the attribute and
+ * class objects.
+ */
+ for (i = 0; i < res->count; i++) {
+ if (ldb_msg_find_element(res->msgs[i], "prefixMap")) {
+ schema_msg = res->msgs[i];
+ break;
+ }
+ }
+
+ if (schema_msg == NULL) {
+ ldb_asprintf_errstring(ldb,
+ "dsdb_schema load failed: failed to find prefixMap");
+ ret = LDB_ERR_NO_SUCH_ATTRIBUTE;
+ goto failed;
+ }
+
+ ret = dsdb_schema_from_ldb_results(tmp_ctx, ldb,
+ schema_msg, res, schema, &error_string);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "dsdb_schema load failed: %s",
+ error_string);
+ goto failed;
+ }
+
+ (*schema)->metadata_usn = schema_seq_num;
+
+ talloc_steal(mem_ctx, *schema);
+
+failed:
+ if (flags & LDB_FLG_ENABLE_TRACING) {
+ flags = ldb_get_flags(ldb);
+ ldb_set_flags(ldb, flags | LDB_FLG_ENABLE_TRACING);
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static int schema_load(struct ldb_context *ldb,
+ struct ldb_module *module,
+ bool *need_write)
+{
+ struct dsdb_schema *schema;
+ int ret, metadata_ret;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ schema = dsdb_get_schema(ldb, frame);
+
+ metadata_ret = schema_metadata_open(module);
+
+ /* We might already have a schema */
+ if (schema != NULL) {
+ /* If we have the metadata.tdb, then hook up the refresh function */
+ if (metadata_ret == LDB_SUCCESS && dsdb_uses_global_schema(ldb)) {
+ ret = dsdb_set_schema_refresh_function(ldb, dsdb_schema_refresh, module);
+
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_init: dsdb_set_schema_refresh_fns() failed: %d:%s: %s",
+ ret, ldb_strerror(ret), ldb_errstring(ldb));
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ }
+
+ TALLOC_FREE(frame);
+ return LDB_SUCCESS;
+ }
+
+ if (metadata_ret == LDB_SUCCESS) {
+ ret = dsdb_set_schema_refresh_function(ldb, dsdb_schema_refresh, module);
+
+ if (ret != LDB_SUCCESS) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_init: dsdb_set_schema_refresh_fns() failed: %d:%s: %s",
+ ret, ldb_strerror(ret), ldb_errstring(ldb));
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ } else {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_init: failed to open metadata.tdb");
+ TALLOC_FREE(frame);
+ return metadata_ret;
+ }
+
+ schema = dsdb_get_schema(ldb, frame);
+
+ /* We do this, invoking the refresh handler, so we know that it works */
+ if (schema == NULL) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_init: dsdb_get_schema failed");
+ TALLOC_FREE(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* Now check the @INDEXLIST is correct, or fix it up */
+ ret = dsdb_schema_set_indices_and_attributes(ldb, schema,
+ SCHEMA_COMPARE);
+ if (ret == LDB_ERR_BUSY) {
+ *need_write = true;
+ ret = LDB_SUCCESS;
+ } else {
+ *need_write = false;
+ }
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to update "
+ "@INDEXLIST and @ATTRIBUTES "
+ "records to match database schema: %s",
+ ldb_errstring(ldb));
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ TALLOC_FREE(frame);
+ return LDB_SUCCESS;
+}
+
+static int schema_load_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct schema_load_private_data *private_data =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct schema_load_private_data);
+ int ret;
+
+ ret = ldb_next_init(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return schema_load(ldb, module, &private_data->need_write);
+}
+
+static int schema_load_start_transaction(struct ldb_module *module)
+{
+ struct schema_load_private_data *private_data =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct schema_load_private_data);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct dsdb_schema *schema;
+ int ret;
+
+ ret = ldb_next_start_trans(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Try the schema refresh now */
+ schema = dsdb_get_schema(ldb, NULL);
+ if (schema == NULL) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_init: dsdb_get_schema failed");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (private_data->need_write) {
+ ret = dsdb_schema_set_indices_and_attributes(ldb,
+ schema,
+ SCHEMA_WRITE);
+ private_data->need_write = false;
+ }
+
+ private_data->in_transaction++;
+
+ return ret;
+}
+
+static int schema_load_end_transaction(struct ldb_module *module)
+{
+ struct schema_load_private_data *private_data =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct schema_load_private_data);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ if (private_data->in_transaction == 0) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_end_transaction: transaction mismatch");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ private_data->in_transaction--;
+
+ return ldb_next_end_trans(module);
+}
+
+static int schema_load_del_transaction(struct ldb_module *module)
+{
+ struct schema_load_private_data *private_data =
+ talloc_get_type(ldb_module_get_private(module), struct schema_load_private_data);
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ if (private_data->in_transaction == 0) {
+ ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+ "schema_load_del_transaction: transaction mismatch");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ private_data->in_transaction--;
+
+ return ldb_next_del_trans(module);
+}
+
+/* This is called in a transaction held by the callers */
+static int schema_load_extended(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct dsdb_schema *schema;
+ int ret;
+
+ if (strcmp(req->op.extended.oid, DSDB_EXTENDED_SCHEMA_LOAD) == 0) {
+
+ ret = dsdb_schema_from_db(module, req, 0, &schema);
+ if (ret == LDB_SUCCESS) {
+ return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
+ }
+ return ret;
+
+ } else if (strcmp(req->op.extended.oid, DSDB_EXTENDED_SCHEMA_UPDATE_NOW_OID) == 0) {
+ /* Force a refresh */
+ schema = dsdb_get_schema(ldb, NULL);
+
+ ret = dsdb_schema_set_indices_and_attributes(ldb,
+ schema,
+ SCHEMA_WRITE);
+
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to write new "
+ "@INDEXLIST and @ATTRIBUTES "
+ "records for updated schema: %s",
+ ldb_errstring(ldb));
+ return ret;
+ }
+
+ return ldb_next_request(module, req);
+ } else {
+ /* Pass to next module, the partition one should finish the chain */
+ return ldb_next_request(module, req);
+ }
+}
+
+static int schema_read_lock(struct ldb_module *module)
+{
+ struct schema_load_private_data *private_data =
+ talloc_get_type(ldb_module_get_private(module), struct schema_load_private_data);
+ int ret;
+
+ if (private_data == NULL) {
+ private_data = talloc_zero(module, struct schema_load_private_data);
+ if (private_data == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ private_data->module = module;
+
+ ldb_module_set_private(module, private_data);
+ }
+
+ ret = ldb_next_read_lock(module);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (private_data->in_transaction == 0 &&
+ private_data->in_read_transaction == 0) {
+ /* Try the schema refresh now */
+ dsdb_get_schema(ldb_module_get_ctx(module), NULL);
+ }
+
+ private_data->in_read_transaction++;
+
+ return LDB_SUCCESS;
+}
+
+static int schema_read_unlock(struct ldb_module *module)
+{
+ struct schema_load_private_data *private_data =
+ talloc_get_type_abort(ldb_module_get_private(module),
+ struct schema_load_private_data);
+
+ private_data->in_read_transaction--;
+
+ return ldb_next_read_unlock(module);
+}
+
+
+static const struct ldb_module_ops ldb_schema_load_module_ops = {
+ .name = "schema_load",
+ .init_context = schema_load_init,
+ .extended = schema_load_extended,
+ .start_transaction = schema_load_start_transaction,
+ .end_transaction = schema_load_end_transaction,
+ .del_transaction = schema_load_del_transaction,
+ .read_lock = schema_read_lock,
+ .read_unlock = schema_read_unlock,
+};
+
+int ldb_schema_load_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_schema_load_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/schema_util.c b/source4/dsdb/samdb/ldb_modules/schema_util.c
new file mode 100644
index 0000000..47d2411
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/schema_util.c
@@ -0,0 +1,345 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ dsdb module schema utility functions
+
+ Copyright (C) Kamen Mazdrashki <kamenim@samba.org> 2010
+ Copyright (C) Andrew Tridgell 2010
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "dsdb/common/util.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include <ldb_module.h>
+#include "librpc/gen_ndr/ndr_drsuapi.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "param/param.h"
+
+/**
+ * Reads schema_info structure from schemaInfo
+ * attribute on SCHEMA partition
+ *
+ * @param dsdb_flags DSDB_FLAG_... flag of 0
+ */
+int dsdb_module_schema_info_blob_read(struct ldb_module *ldb_module,
+ uint32_t dsdb_flags,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_val *schema_info_blob,
+ struct ldb_request *parent)
+{
+ int ldb_err;
+ const struct ldb_val *blob_val;
+ struct ldb_dn *schema_dn;
+ struct ldb_result *schema_res = NULL;
+ static const char *schema_attrs[] = {
+ "schemaInfo",
+ NULL
+ };
+
+ schema_dn = ldb_get_schema_basedn(ldb_module_get_ctx(ldb_module));
+ if (!schema_dn) {
+ DEBUG(0,("dsdb_module_schema_info_blob_read: no schema dn present!\n"));
+ return ldb_operr(ldb_module_get_ctx(ldb_module));
+ }
+
+ ldb_err = dsdb_module_search(ldb_module, mem_ctx, &schema_res, schema_dn,
+ LDB_SCOPE_BASE, schema_attrs, dsdb_flags, parent,
+ NULL);
+ if (ldb_err == LDB_ERR_NO_SUCH_OBJECT) {
+ DEBUG(0,("dsdb_module_schema_info_blob_read: Schema DN not found!\n"));
+ talloc_free(schema_res);
+ return ldb_err;
+ } else if (ldb_err != LDB_SUCCESS) {
+ DEBUG(0,("dsdb_module_schema_info_blob_read: failed to find schemaInfo attribute\n"));
+ talloc_free(schema_res);
+ return ldb_err;
+ }
+
+ blob_val = ldb_msg_find_ldb_val(schema_res->msgs[0], "schemaInfo");
+ if (!blob_val) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ldb_module),
+ "dsdb_module_schema_info_blob_read: no schemaInfo attribute found");
+ talloc_free(schema_res);
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+
+ /* transfer .data ownership to mem_ctx */
+ schema_info_blob->length = blob_val->length;
+ schema_info_blob->data = talloc_steal(mem_ctx, blob_val->data);
+
+ talloc_free(schema_res);
+
+ return LDB_SUCCESS;
+}
+
+/**
+ * Prepares ldb_msg to be used for updating schemaInfo value in DB
+ */
+static int dsdb_schema_info_write_prepare(struct ldb_context *ldb,
+ struct ldb_val *schema_info_blob,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message **_msg)
+{
+ int ldb_err;
+ struct ldb_message *msg;
+ struct ldb_dn *schema_dn;
+ struct ldb_message_element *return_el;
+
+ schema_dn = ldb_get_schema_basedn(ldb);
+ if (!schema_dn) {
+ DEBUG(0,("dsdb_schema_info_write_prepare: no schema dn present\n"));
+ return ldb_operr(ldb);
+ }
+
+ /* prepare ldb_msg to update schemaInfo */
+ msg = ldb_msg_new(mem_ctx);
+ if (msg == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ msg->dn = schema_dn;
+ ldb_err = ldb_msg_add_value(msg, "schemaInfo", schema_info_blob, &return_el);
+ if (ldb_err != 0) {
+ ldb_asprintf_errstring(ldb, "dsdb_schema_info_write_prepare: ldb_msg_add_value failed - %s\n",
+ ldb_strerror(ldb_err));
+ talloc_free(msg);
+ return ldb_err;
+ }
+
+ /* mark schemaInfo element for replacement */
+ return_el->flags = LDB_FLAG_MOD_REPLACE;
+
+ *_msg = msg;
+
+ return LDB_SUCCESS;
+}
+
+
+
+/**
+ * Writes schema_info structure into schemaInfo
+ * attribute on SCHEMA partition
+ *
+ * @param dsdb_flags DSDB_FLAG_... flag of 0
+ */
+int dsdb_module_schema_info_blob_write(struct ldb_module *ldb_module,
+ uint32_t dsdb_flags,
+ struct ldb_val *schema_info_blob,
+ struct ldb_request *parent)
+{
+ int ldb_err;
+ struct ldb_message *msg = NULL;
+ TALLOC_CTX *temp_ctx;
+
+ temp_ctx = talloc_new(ldb_module);
+ if (temp_ctx == NULL) {
+ return ldb_module_oom(ldb_module);
+ }
+
+ /* write serialized schemaInfo into LDB */
+ ldb_err = dsdb_schema_info_write_prepare(ldb_module_get_ctx(ldb_module),
+ schema_info_blob,
+ temp_ctx, &msg);
+ if (ldb_err != LDB_SUCCESS) {
+ talloc_free(temp_ctx);
+ return ldb_err;
+ }
+
+
+ ldb_err = dsdb_module_modify(ldb_module, msg, dsdb_flags, parent);
+
+ talloc_free(temp_ctx);
+
+ if (ldb_err != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ldb_module),
+ "dsdb_module_schema_info_blob_write: dsdb_replace failed: %s (%s)\n",
+ ldb_strerror(ldb_err),
+ ldb_errstring(ldb_module_get_ctx(ldb_module)));
+ return ldb_err;
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+/**
+ * Reads schema_info structure from schemaInfo
+ * attribute on SCHEMA partition
+ */
+static int dsdb_module_schema_info_read(struct ldb_module *ldb_module,
+ uint32_t dsdb_flags,
+ TALLOC_CTX *mem_ctx,
+ struct dsdb_schema_info **_schema_info,
+ struct ldb_request *parent)
+{
+ int ret;
+ DATA_BLOB ndr_blob;
+ TALLOC_CTX *temp_ctx;
+ WERROR werr;
+
+ temp_ctx = talloc_new(mem_ctx);
+ if (temp_ctx == NULL) {
+ return ldb_module_oom(ldb_module);
+ }
+
+ /* read serialized schemaInfo from LDB */
+ ret = dsdb_module_schema_info_blob_read(ldb_module, dsdb_flags, temp_ctx, &ndr_blob, parent);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(temp_ctx);
+ return ret;
+ }
+
+ /* convert NDR blob to dsdb_schema_info object */
+ werr = dsdb_schema_info_from_blob(&ndr_blob,
+ mem_ctx,
+ _schema_info);
+ talloc_free(temp_ctx);
+
+ if (W_ERROR_EQUAL(werr, WERR_DS_NO_ATTRIBUTE_OR_VALUE)) {
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ldb_module), __location__ ": failed to get schema_info");
+ return ldb_operr(ldb_module_get_ctx(ldb_module));
+ }
+
+ return LDB_SUCCESS;
+}
+
+/**
+ * Writes schema_info structure into schemaInfo
+ * attribute on SCHEMA partition
+ *
+ * @param dsdb_flags DSDB_FLAG_... flag of 0
+ */
+static int dsdb_module_schema_info_write(struct ldb_module *ldb_module,
+ uint32_t dsdb_flags,
+ const struct dsdb_schema_info *schema_info,
+ struct ldb_request *parent)
+{
+ WERROR werr;
+ int ret;
+ DATA_BLOB ndr_blob;
+ TALLOC_CTX *temp_ctx;
+
+ temp_ctx = talloc_new(ldb_module);
+ if (temp_ctx == NULL) {
+ return ldb_module_oom(ldb_module);
+ }
+
+ /* convert schema_info to a blob */
+ werr = dsdb_blob_from_schema_info(schema_info, temp_ctx, &ndr_blob);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(temp_ctx);
+ ldb_asprintf_errstring(ldb_module_get_ctx(ldb_module), __location__ ": failed to get schema_info");
+ return ldb_operr(ldb_module_get_ctx(ldb_module));
+ }
+
+ /* write serialized schemaInfo into LDB */
+ ret = dsdb_module_schema_info_blob_write(ldb_module, dsdb_flags, &ndr_blob, parent);
+
+ talloc_free(temp_ctx);
+
+ return ret;
+}
+
+
+/**
+ * Increments schemaInfo revision and save it to DB
+ * setting our invocationID in the process
+ * NOTE: this function should be called in a transaction
+ * much in the same way prefixMap update function is called
+ *
+ * @param ldb_module current module
+ * @param schema schema cache
+ * @param dsdb_flags DSDB_FLAG_... flag of 0
+ */
+int dsdb_module_schema_info_update(struct ldb_module *ldb_module,
+ struct dsdb_schema *schema,
+ int dsdb_flags, struct ldb_request *parent)
+{
+ int ret;
+ const struct GUID *invocation_id;
+ struct dsdb_schema_info *schema_info;
+ WERROR werr;
+ TALLOC_CTX *temp_ctx = talloc_new(schema);
+ if (temp_ctx == NULL) {
+ return ldb_module_oom(ldb_module);
+ }
+
+ invocation_id = samdb_ntds_invocation_id(ldb_module_get_ctx(ldb_module));
+ if (!invocation_id) {
+ talloc_free(temp_ctx);
+ return ldb_operr(ldb_module_get_ctx(ldb_module));
+ }
+
+ /* read serialized schemaInfo from LDB */
+ ret = dsdb_module_schema_info_read(ldb_module, dsdb_flags, temp_ctx, &schema_info, parent);
+ if (ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
+ /* make default value in case
+ * we have no schemaInfo value yet */
+ werr = dsdb_schema_info_new(temp_ctx, &schema_info);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(temp_ctx);
+ return ldb_module_oom(ldb_module);
+ }
+ ret = LDB_SUCCESS;
+ }
+ if (ret != LDB_SUCCESS) {
+ talloc_free(temp_ctx);
+ return ret;
+ }
+
+ /* update schemaInfo */
+ schema_info->revision++;
+ schema_info->invocation_id = *invocation_id;
+
+ ret = dsdb_module_schema_info_write(ldb_module, dsdb_flags, schema_info, parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(ldb_module),
+ "dsdb_module_schema_info_update: failed to save schemaInfo - %s\n",
+ ldb_strerror(ret));
+ talloc_free(temp_ctx);
+ return ret;
+ }
+
+ /*
+ * We don't update the schema->schema_info!
+ * as that would not represent the other information
+ * in schema->*
+ *
+ * We're not sure if the current transaction will go through!
+ * E.g. schema changes are only allowed on the schema master,
+ * otherwise they result in a UNWILLING_TO_PERFORM and a
+ *
+ * Note that schema might a global variable shared between
+ * multiple ldb_contexts. With process model "single" it
+ * means the drsuapi server also uses it.
+ *
+ * We keep it simple and just try to update the
+ * stored value.
+ *
+ * The next schema reload will pick it up, which
+ * then works for originating and replicated changes
+ * in the same way.
+ */
+
+ talloc_free(temp_ctx);
+ return LDB_SUCCESS;
+}
diff --git a/source4/dsdb/samdb/ldb_modules/secrets_tdb_sync.c b/source4/dsdb/samdb/ldb_modules/secrets_tdb_sync.c
new file mode 100644
index 0000000..52c8aad
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/secrets_tdb_sync.c
@@ -0,0 +1,532 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2007-2012
+
+ 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 secrets_tdb_sync module
+ *
+ * Description: Update secrets.tdb whenever the matching secret record changes
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "lib/util/dlinklist.h"
+#include "auth/credentials/credentials.h"
+#include "auth/credentials/credentials_krb5.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "auth/kerberos/kerberos_srv_keytab.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "param/secrets.h"
+#include "source3/include/secrets.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "dsdb/samdb/samdb.h"
+
+struct dn_list {
+ struct ldb_message *msg;
+ bool do_delete;
+ struct dn_list *prev, *next;
+};
+
+struct secrets_tdb_sync_private {
+ struct dn_list *changed_dns;
+ struct db_context *secrets_tdb;
+};
+
+struct secrets_tdb_sync_ctx {
+ struct ldb_module *module;
+ struct ldb_request *req;
+
+ struct ldb_dn *dn;
+ bool do_delete;
+
+ struct ldb_reply *op_reply;
+ bool found;
+};
+
+static struct secrets_tdb_sync_ctx *secrets_tdb_sync_ctx_init(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct secrets_tdb_sync_ctx *ac;
+
+ ac = talloc_zero(req, struct secrets_tdb_sync_ctx);
+ if (ac == NULL) {
+ ldb_oom(ldb_module_get_ctx(module));
+ return NULL;
+ }
+
+ ac->module = module;
+ ac->req = req;
+
+ return ac;
+}
+
+/* FIXME: too many semi-async searches here for my taste, direct and indirect as
+ * cli_credentials_set_secrets() performs a sync ldb search.
+ * Just hope we are lucky and nothing breaks (using the tdb backend masks a lot
+ * of async issues). -SSS
+ */
+static int add_modified(struct ldb_module *module, struct ldb_dn *dn, bool do_delete,
+ struct ldb_request *parent)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct secrets_tdb_sync_private *data = talloc_get_type(ldb_module_get_private(module), struct secrets_tdb_sync_private);
+ struct dn_list *item;
+ char *filter;
+ struct ldb_result *res;
+ int ret;
+
+ filter = talloc_asprintf(data,
+ "(&(objectClass=primaryDomain)(flatname=*))");
+ if (!filter) {
+ return ldb_oom(ldb);
+ }
+
+ ret = dsdb_module_search(module, data, &res,
+ dn, LDB_SCOPE_BASE, NULL,
+ DSDB_FLAG_NEXT_MODULE, parent,
+ "%s", filter);
+ talloc_free(filter);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (res->count != 1) {
+ /* if it's not a primaryDomain then we don't have anything to update */
+ talloc_free(res);
+ return LDB_SUCCESS;
+ }
+
+ item = talloc(data->changed_dns? (void *)data->changed_dns: (void *)data, struct dn_list);
+ if (!item) {
+ talloc_free(res);
+ return ldb_oom(ldb);
+ }
+
+ item->msg = talloc_steal(item, res->msgs[0]);
+ item->do_delete = do_delete;
+ talloc_free(res);
+
+ DLIST_ADD_END(data->changed_dns, item);
+ return LDB_SUCCESS;
+}
+
+static int ust_search_modified(struct secrets_tdb_sync_ctx *ac);
+
+static int secrets_tdb_sync_op_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct secrets_tdb_sync_ctx *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct secrets_tdb_sync_ctx);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ ldb_set_errstring(ldb, "Invalid request type!\n");
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ac->do_delete) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, LDB_SUCCESS);
+ }
+
+ ac->op_reply = talloc_steal(ac, ares);
+
+ ret = ust_search_modified(ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int ust_del_op(struct secrets_tdb_sync_ctx *ac)
+{
+ struct ldb_context *ldb;
+ struct ldb_request *down_req;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ ret = ldb_build_del_req(&down_req, ldb, ac,
+ ac->dn,
+ ac->req->controls,
+ ac, secrets_tdb_sync_op_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(ac->module, down_req);
+}
+
+static int ust_search_modified_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct secrets_tdb_sync_ctx *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct secrets_tdb_sync_ctx);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+
+ ac->found = true;
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+
+ if (ac->found) {
+ /* do the dirty sync job here :/ */
+ ret = add_modified(ac->module, ac->dn, ac->do_delete, ac->req);
+ }
+
+ if (ac->do_delete) {
+ ret = ust_del_op(ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req,
+ NULL, NULL, ret);
+ }
+ break;
+ }
+
+ return ldb_module_done(ac->req, ac->op_reply->controls,
+ ac->op_reply->response, LDB_SUCCESS);
+ }
+
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+static int ust_search_modified(struct secrets_tdb_sync_ctx *ac)
+{
+ struct ldb_context *ldb;
+ static const char * const no_attrs[] = { NULL };
+ struct ldb_request *search_req;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ ret = ldb_build_search_req(&search_req, ldb, ac,
+ ac->dn, LDB_SCOPE_BASE,
+ "(&(objectClass=kerberosSecret)"
+ "(privateKeytab=*))", no_attrs,
+ NULL,
+ ac, ust_search_modified_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(ac->module, search_req);
+}
+
+
+/* add */
+static int secrets_tdb_sync_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct secrets_tdb_sync_ctx *ac;
+ struct ldb_request *down_req;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = secrets_tdb_sync_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ ac->dn = req->op.add.message->dn;
+
+ ret = ldb_build_add_req(&down_req, ldb, ac,
+ req->op.add.message,
+ req->controls,
+ ac, secrets_tdb_sync_op_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, down_req);
+}
+
+/* modify */
+static int secrets_tdb_sync_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct secrets_tdb_sync_ctx *ac;
+ struct ldb_request *down_req;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = secrets_tdb_sync_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ ac->dn = req->op.mod.message->dn;
+
+ ret = ldb_build_mod_req(&down_req, ldb, ac,
+ req->op.mod.message,
+ req->controls,
+ ac, secrets_tdb_sync_op_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, down_req);
+}
+
+/* delete */
+static int secrets_tdb_sync_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ struct secrets_tdb_sync_ctx *ac;
+
+ ac = secrets_tdb_sync_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ ac->dn = req->op.del.dn;
+ ac->do_delete = true;
+
+ return ust_search_modified(ac);
+}
+
+/* rename */
+static int secrets_tdb_sync_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct secrets_tdb_sync_ctx *ac;
+ struct ldb_request *down_req;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = secrets_tdb_sync_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ ac->dn = req->op.rename.newdn;
+
+ ret = ldb_build_rename_req(&down_req, ldb, ac,
+ req->op.rename.olddn,
+ req->op.rename.newdn,
+ req->controls,
+ ac, secrets_tdb_sync_op_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, down_req);
+}
+
+/* prepare for a commit */
+static int secrets_tdb_sync_prepare_commit(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct secrets_tdb_sync_private *data = talloc_get_type(ldb_module_get_private(module),
+ struct secrets_tdb_sync_private);
+ struct dn_list *p;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(data);
+ if (!tmp_ctx) {
+ ldb_oom(ldb);
+ goto fail;
+ }
+
+ for (p=data->changed_dns; p; p = p->next) {
+ const struct ldb_val *whenChanged = ldb_msg_find_ldb_val(p->msg, "whenChanged");
+ time_t lct = 0;
+ bool ret;
+
+ if (whenChanged) {
+ ldb_val_to_time(whenChanged, &lct);
+ }
+
+ ret = secrets_store_machine_pw_sync(ldb_msg_find_attr_as_string(p->msg, "secret", NULL),
+ ldb_msg_find_attr_as_string(p->msg, "priorSecret", NULL),
+
+ ldb_msg_find_attr_as_string(p->msg, "flatname", NULL),
+ ldb_msg_find_attr_as_string(p->msg, "realm", NULL),
+ ldb_msg_find_attr_as_string(p->msg, "saltPrincipal", NULL),
+ (uint32_t)ldb_msg_find_attr_as_int(p->msg, "msDS-SupportedEncryptionTypes", ENC_ALL_TYPES),
+ samdb_result_dom_sid(tmp_ctx, p->msg, "objectSid"),
+
+ lct,
+ (uint32_t)ldb_msg_find_attr_as_int(p->msg, "secureChannelType", 0),
+ p->do_delete);
+ if (ret == false) {
+ ldb_asprintf_errstring(ldb, "Failed to update secrets.tdb from entry %s in %s",
+ ldb_dn_get_linearized(p->msg->dn),
+ (const char *)ldb_get_opaque(ldb, "ldb_url"));
+ goto fail;
+ }
+ }
+
+ talloc_free(data->changed_dns);
+ data->changed_dns = NULL;
+ talloc_free(tmp_ctx);
+
+ return ldb_next_prepare_commit(module);
+
+fail:
+ dbwrap_transaction_cancel(data->secrets_tdb);
+ talloc_free(data->changed_dns);
+ data->changed_dns = NULL;
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+}
+
+/* start a transaction */
+static int secrets_tdb_sync_start_transaction(struct ldb_module *module)
+{
+ struct secrets_tdb_sync_private *data = talloc_get_type(ldb_module_get_private(module), struct secrets_tdb_sync_private);
+
+ if (dbwrap_transaction_start(data->secrets_tdb) != 0) {
+ return ldb_module_operr(module);
+ }
+
+ return ldb_next_start_trans(module);
+}
+
+/* end a transaction */
+static int secrets_tdb_sync_end_transaction(struct ldb_module *module)
+{
+ struct secrets_tdb_sync_private *data = talloc_get_type(ldb_module_get_private(module), struct secrets_tdb_sync_private);
+
+ if (dbwrap_transaction_commit(data->secrets_tdb) != 0) {
+ return ldb_module_operr(module);
+ }
+
+ return ldb_next_end_trans(module);
+}
+
+/* abandon a transaction */
+static int secrets_tdb_sync_del_transaction(struct ldb_module *module)
+{
+ struct secrets_tdb_sync_private *data = talloc_get_type(ldb_module_get_private(module), struct secrets_tdb_sync_private);
+
+ talloc_free(data->changed_dns);
+ data->changed_dns = NULL;
+ if (dbwrap_transaction_cancel(data->secrets_tdb) != 0) {
+ return ldb_module_operr(module);
+ }
+
+ return ldb_next_del_trans(module);
+}
+
+static int secrets_tdb_sync_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb;
+ struct secrets_tdb_sync_private *data;
+ char *private_dir, *p;
+ const char *secrets_ldb;
+
+ ldb = ldb_module_get_ctx(module);
+
+ data = talloc(module, struct secrets_tdb_sync_private);
+ if (data == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ data->changed_dns = NULL;
+
+ ldb_module_set_private(module, data);
+
+ secrets_ldb = (const char *)ldb_get_opaque(ldb, "ldb_url");
+ if (!secrets_ldb) {
+ return ldb_operr(ldb);
+ }
+ if (strncmp("tdb://", secrets_ldb, 6) == 0) {
+ secrets_ldb += 6;
+ }
+ private_dir = talloc_strdup(data, secrets_ldb);
+ p = strrchr(private_dir, '/');
+ if (p) {
+ *p = '\0';
+ } else {
+ private_dir = talloc_strdup(data, ".");
+ }
+
+ secrets_init_path(private_dir);
+
+ TALLOC_FREE(private_dir);
+
+ data->secrets_tdb = secrets_db_ctx();
+
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_secrets_tdb_sync_module_ops = {
+ .name = "secrets_tdb_sync",
+ .init_context = secrets_tdb_sync_init,
+ .add = secrets_tdb_sync_add,
+ .modify = secrets_tdb_sync_modify,
+ .rename = secrets_tdb_sync_rename,
+ .del = secrets_tdb_sync_delete,
+ .start_transaction = secrets_tdb_sync_start_transaction,
+ .prepare_commit = secrets_tdb_sync_prepare_commit,
+ .end_transaction = secrets_tdb_sync_end_transaction,
+ .del_transaction = secrets_tdb_sync_del_transaction,
+};
+
+int ldb_secrets_tdb_sync_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_secrets_tdb_sync_module_ops);
+}
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 0000000..e3dcad5
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/show_deleted.c
@@ -0,0 +1,220 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2005
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009
+ Copyright (C) Matthias Dieter Wallnöfer 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb deleted objects control module
+ *
+ * Description: this module hides deleted and recylced objects, and returns
+ * them if the right control is there
+ *
+ * Author: Stefan Metzmacher
+ */
+
+#include "includes.h"
+#include <ldb_module.h>
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+struct show_deleted_state {
+ bool need_refresh;
+ bool recycle_bin_enabled;
+};
+
+static int show_deleted_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct ldb_control *show_del, *show_rec;
+ struct ldb_request *down_req;
+ struct ldb_parse_tree *new_tree = req->op.search.tree;
+ struct show_deleted_state *state;
+ int ret;
+ const char *exclude_filter = NULL;
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.search.base)) {
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ /* This is the logic from MS-ADTS 3.1.1.3.4.1.14 that
+ determines if objects are visible
+
+ Extended control name Deleted-objects Tombstones Recycled-objects
+ LDAP_SERVER_SHOW_DELETED_OID Visible Visible Not Visible
+ LDAP_SERVER_SHOW_RECYCLED_OID Visible Visible Visible
+
+ Note that if the recycle bin is disabled, then the
+ isRecycled attribute is ignored, and objects are either
+ "normal" or "tombstone".
+
+ When the recycle bin is enabled, then objects are in one of
+ 3 states, "normal", "deleted" or "recycled"
+ */
+
+ /* check if there's a show deleted control */
+ show_del = ldb_request_get_control(req, LDB_CONTROL_SHOW_DELETED_OID);
+ /* check if there's a show recycled control */
+ show_rec = ldb_request_get_control(req, LDB_CONTROL_SHOW_RECYCLED_OID);
+
+ /*
+ * When recycle bin is not enabled, then all we look
+ * at is the isDeleted attribute. We hide objects with this
+ * attribute set to TRUE when the client has not specified either
+ * SHOW_DELETED or SHOW_RECYCLED
+ */
+ if (show_rec == NULL && show_del == NULL) {
+ /* We don't want deleted or recycled objects,
+ * which we get by filtering on isDeleted */
+ exclude_filter = "isDeleted";
+ } else {
+ state = talloc_get_type(ldb_module_get_private(module), struct show_deleted_state);
+
+ /* Note that state may be NULL during initialisation */
+ if (state != NULL && state->need_refresh) {
+ /* Do not move this assignment, it can cause recursion loops! */
+ state->need_refresh = false;
+ ret = dsdb_recyclebin_enabled(module, &state->recycle_bin_enabled);
+ if (ret != LDB_SUCCESS) {
+ state->recycle_bin_enabled = false;
+ /*
+ * We can fail to find the feature object
+ * during provision. Ignore any such error and
+ * assume the recycle bin cannot be enabled at
+ * this point in time.
+ */
+ if (ret != LDB_ERR_NO_SUCH_OBJECT) {
+ state->need_refresh = true;
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+ }
+ }
+
+ if (state != NULL && state->recycle_bin_enabled) {
+ /*
+ * The recycle bin is enabled, so we want deleted not
+ * recycled.
+ */
+ if (show_rec == NULL) {
+ exclude_filter = "isRecycled";
+ }
+ }
+ }
+
+ if (exclude_filter != NULL) {
+ new_tree = talloc(req, struct ldb_parse_tree);
+ if (!new_tree) {
+ return ldb_oom(ldb);
+ }
+ new_tree->operation = LDB_OP_AND;
+ new_tree->u.list.num_elements = 2;
+ new_tree->u.list.elements = talloc_array(new_tree, struct ldb_parse_tree *, 2);
+ if (!new_tree->u.list.elements) {
+ return ldb_oom(ldb);
+ }
+
+ new_tree->u.list.elements[0] = talloc(new_tree->u.list.elements, struct ldb_parse_tree);
+ new_tree->u.list.elements[0]->operation = LDB_OP_NOT;
+ new_tree->u.list.elements[0]->u.isnot.child =
+ talloc(new_tree->u.list.elements, struct ldb_parse_tree);
+ if (!new_tree->u.list.elements[0]->u.isnot.child) {
+ return ldb_oom(ldb);
+ }
+ new_tree->u.list.elements[0]->u.isnot.child->operation = LDB_OP_EQUALITY;
+ new_tree->u.list.elements[0]->u.isnot.child->u.equality.attr = exclude_filter;
+ new_tree->u.list.elements[0]->u.isnot.child->u.equality.value = data_blob_string_const("TRUE");
+ new_tree->u.list.elements[1] = req->op.search.tree;
+ }
+
+ ret = ldb_build_search_req_ex(&down_req, ldb, req,
+ req->op.search.base,
+ req->op.search.scope,
+ new_tree,
+ req->op.search.attrs,
+ req->controls,
+ req, dsdb_next_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* mark the controls as done */
+ if (show_del != NULL) {
+ show_del->critical = 0;
+ }
+ if (show_rec != NULL) {
+ show_rec->critical = 0;
+ }
+
+ /* perform the search */
+ return ldb_next_request(module, down_req);
+}
+
+static int show_deleted_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb;
+ int ret;
+ struct show_deleted_state *state;
+
+ state = talloc_zero(module, struct show_deleted_state);
+ if (state == NULL) {
+ return ldb_module_oom(module);
+ }
+ state->need_refresh = true;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_SHOW_DELETED_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "show_deleted: Unable to register control with rootdse!\n");
+ return ldb_operr(ldb);
+ }
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_SHOW_RECYCLED_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "show_deleted: Unable to register control with rootdse!\n");
+ return ldb_operr(ldb);
+ }
+
+ ret = ldb_next_init(module);
+
+ ldb_module_set_private(module, state);
+
+ return ret;
+}
+
+static const struct ldb_module_ops ldb_show_deleted_module_ops = {
+ .name = "show_deleted",
+ .search = show_deleted_search,
+ .init_context = show_deleted_init
+};
+
+int ldb_show_deleted_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_show_deleted_module_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 0000000..24211b6
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/subtree_delete.c
@@ -0,0 +1,142 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2007
+ Copyright (C) Andrew Tridgell <tridge@samba.org> 2009
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+ Copyright (C) Simo Sorce <idra@samba.org> 2008
+ Copyright (C) Matthias Dieter Wallnöfer 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Name: ldb
+ *
+ * Component: ldb subtree delete module
+ *
+ * Description: Delete of a subtree in LDB
+ *
+ * Author: Andrew Bartlett
+ */
+
+#include "includes.h"
+#include <ldb.h>
+#include <ldb_module.h>
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "dsdb/common/util.h"
+
+
+static int subtree_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ static const char * const attrs[] = { NULL };
+ struct ldb_result *res = NULL;
+ uint32_t flags;
+ unsigned int i;
+ int ret;
+
+ if (ldb_dn_is_special(req->op.del.dn)) {
+ /* do not manipulate our control entries */
+ return ldb_next_request(module, req);
+ }
+
+ /* see if we have any children */
+ ret = dsdb_module_search(module, req, &res, req->op.del.dn,
+ LDB_SCOPE_ONELEVEL, attrs,
+ DSDB_FLAG_NEXT_MODULE,
+ req,
+ "(objectClass=*)");
+ if (ret != LDB_SUCCESS) {
+ talloc_free(res);
+ return ret;
+ }
+ if (res->count == 0) {
+ talloc_free(res);
+ return ldb_next_request(module, req);
+ }
+
+ if (ldb_request_get_control(req, LDB_CONTROL_TREE_DELETE_OID) == NULL) {
+ /* Do not add any DN outputs to this error string!
+ * Some MMC consoles (eg release 2000) have a strange
+ * bug and prevent subtree deletes afterwards. */
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "subtree_delete: Unable to "
+ "delete a non-leaf node "
+ "(it has %u children)!",
+ res->count);
+ talloc_free(res);
+ return LDB_ERR_NOT_ALLOWED_ON_NON_LEAF;
+ }
+
+ /*
+ * we need to start from the top since other LDB modules could
+ * enforce constraints (eg "objectclass" and "samldb" do so).
+ *
+ * We pass DSDB_FLAG_AS_SYSTEM as the acl module above us
+ * has already checked for SEC_ADS_DELETE_TREE.
+ */
+ flags = DSDB_FLAG_TOP_MODULE |
+ DSDB_FLAG_AS_SYSTEM |
+ DSDB_FLAG_TRUSTED |
+ DSDB_TREE_DELETE;
+ if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID) != NULL) {
+ flags |= DSDB_MODIFY_RELAX;
+ }
+
+ /*
+ * The net result of this code is that the leaf nodes are
+ * deleted first, as the parent is only deleted once these
+ * calls (and the delete calls recursive within these)
+ * complete.
+ */
+ for (i = 0; i < res->count; i++) {
+ ret = dsdb_module_del(module, res->msgs[i]->dn, flags, req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ talloc_free(res);
+
+ return ldb_next_request(module, req);
+}
+
+static int subtree_delete_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_TREE_DELETE_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR,
+ "subtree_delete: Unable to register control with rootdse!\n");
+ return ldb_operr(ldb);
+ }
+
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_subtree_delete_module_ops = {
+ .name = "subtree_delete",
+ .init_context = subtree_delete_init,
+ .del = subtree_delete
+};
+
+int ldb_subtree_delete_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_subtree_delete_module_ops);
+}
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 0000000..be02e92
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/subtree_rename.c
@@ -0,0 +1,202 @@
+/*
+ ldb database library
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2007
+ Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+ Copyright (C) Matthias Dieter Wallnöfer <mdw@samba.org> 2010-2011
+
+ 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 "includes.h"
+#include <ldb.h>
+#include <ldb_module.h>
+#include "libds/common/flags.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+struct subtree_rename_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+ bool base_renamed;
+};
+
+static struct subtree_rename_context *subren_ctx_init(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct subtree_rename_context *ac;
+
+
+ ac = talloc_zero(req, struct subtree_rename_context);
+ if (ac == NULL) {
+ return NULL;
+ }
+
+ ac->module = module;
+ ac->req = req;
+ ac->base_renamed = false;
+
+ return ac;
+}
+
+static int subtree_rename_search_onelevel_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct subtree_rename_context *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct subtree_rename_context);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ac->base_renamed == false) {
+ ac->base_renamed = true;
+
+ ret = dsdb_module_rename(ac->module,
+ ac->req->op.rename.olddn,
+ ac->req->op.rename.newdn,
+ DSDB_FLAG_NEXT_MODULE, req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ {
+ struct ldb_dn *old_dn = NULL;
+ struct ldb_dn *new_dn = NULL;
+
+ old_dn = ares->message->dn;
+ new_dn = ldb_dn_copy(ares, old_dn);
+ if (!new_dn) {
+ return ldb_module_oom(ac->module);
+ }
+
+ if ( ! ldb_dn_remove_base_components(new_dn,
+ ldb_dn_get_comp_num(ac->req->op.rename.olddn))) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if ( ! ldb_dn_add_base(new_dn, ac->req->op.rename.newdn)) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ ret = dsdb_module_rename(ac->module, old_dn, new_dn, DSDB_FLAG_OWN_MODULE, req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ talloc_free(ares);
+
+ return LDB_SUCCESS;
+ }
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+ case LDB_REPLY_DONE:
+ talloc_free(ares);
+ return ldb_module_done(ac->req, NULL, NULL, LDB_SUCCESS);
+ default:
+ {
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+
+ ldb_asprintf_errstring(ldb, "Invalid LDB reply type %d", ares->type);
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+/* rename */
+static int subtree_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ static const char * const no_attrs[] = {NULL};
+ struct ldb_request *search_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);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ /*
+ * This gets complex: We need to:
+ * - Do a search for all entries under this entry
+ * - Wait for these results to appear
+ * - Do our own rename (in first callback)
+ * - In the callback for each result, issue a dsdb_module_rename()
+ */
+
+ ac = subren_ctx_init(module, req);
+ if (!ac) {
+ return ldb_oom(ldb);
+ }
+
+ ret = ldb_build_search_req(&search_req, ldb_module_get_ctx(ac->module), ac,
+ ac->req->op.rename.olddn,
+ LDB_SCOPE_ONELEVEL,
+ "(objectClass=*)",
+ no_attrs,
+ NULL,
+ ac,
+ subtree_rename_search_onelevel_callback,
+ req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_request_add_control(search_req, LDB_CONTROL_SHOW_RECYCLED_OID,
+ true, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(ac->module, search_req);
+}
+
+static const struct ldb_module_ops ldb_subtree_rename_module_ops = {
+ .name = "subtree_rename",
+ .rename = subtree_rename
+};
+
+int ldb_subtree_rename_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_subtree_rename_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py b/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py
new file mode 100755
index 0000000..d28be8f
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/possibleinferiors.py
@@ -0,0 +1,265 @@
+#!/usr/bin/env python3
+
+# Unix SMB/CIFS implementation.
+# Copyright (C) Andrew Tridgell 2009
+#
+# 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 the possibleInferiors generation in the schema_fsmo ldb module"""
+
+import optparse
+import sys
+
+
+# Find right directory when running from source tree
+sys.path.insert(0, "bin/python")
+
+from samba import getopt as options, Ldb
+import ldb
+
+parser = optparse.OptionParser("possibleinferiors.py <URL> [<CLASS>]")
+sambaopts = options.SambaOptions(parser)
+parser.add_option_group(sambaopts)
+credopts = options.CredentialsOptions(parser)
+parser.add_option_group(credopts)
+parser.add_option_group(options.VersionOptions(parser))
+parser.add_option("--wspp", action="store_true")
+
+opts, args = parser.parse_args()
+
+if len(args) < 1:
+ parser.print_usage()
+ sys.exit(1)
+
+url = args[0]
+if (len(args) > 1):
+ objectclass = args[1]
+else:
+ objectclass = None
+
+
+def uniq_list(alist):
+ """return a unique list"""
+ set = {}
+ return [set.setdefault(e, e) for e in alist if e not in set]
+
+
+lp_ctx = sambaopts.get_loadparm()
+
+creds = credopts.get_credentials(lp_ctx)
+
+ldb_options = []
+# use 'paged_search' module when connecting remotely
+if url.lower().startswith("ldap://"):
+ ldb_options = ["modules:paged_searches"]
+
+db = Ldb(url, credentials=creds, lp=lp_ctx, options=ldb_options)
+
+# get the rootDSE
+res = db.search(base="", expression="",
+ scope=ldb.SCOPE_BASE,
+ attrs=["schemaNamingContext"])
+rootDse = res[0]
+
+schema_base = rootDse["schemaNamingContext"][0]
+
+
+def possible_inferiors_search(db, oc):
+ """return the possible inferiors via a search for the possibleInferiors attribute"""
+ res = db.search(base=schema_base,
+ expression=("ldapDisplayName=%s" % oc),
+ attrs=["possibleInferiors"])
+
+ poss = []
+ if len(res) == 0 or res[0].get("possibleInferiors") is None:
+ return poss
+ for item in res[0]["possibleInferiors"]:
+ poss.append(str(item))
+ poss = uniq_list(poss)
+ poss.sort()
+ return poss
+
+
+# see [MS-ADTS] section 3.1.1.4.5.21
+# and section 3.1.1.4.2 for this algorithm
+
+# !systemOnly=TRUE
+# !objectClassCategory=2
+# !objectClassCategory=3
+
+def supclasses(classinfo, oc):
+ list = []
+ if oc == "top":
+ return list
+ if classinfo[oc].get("SUPCLASSES") is not None:
+ return classinfo[oc]["SUPCLASSES"]
+ res = classinfo[oc]["subClassOf"]
+ for r in res:
+ list.append(r)
+ list.extend(supclasses(classinfo, r))
+ classinfo[oc]["SUPCLASSES"] = list
+ return list
+
+
+def auxclasses(classinfo, oclist):
+ list = []
+ if oclist == []:
+ return list
+ for oc in oclist:
+ if classinfo[oc].get("AUXCLASSES") is not None:
+ list.extend(classinfo[oc]["AUXCLASSES"])
+ else:
+ list2 = []
+ list2.extend(classinfo[oc]["systemAuxiliaryClass"])
+ list2.extend(auxclasses(classinfo, classinfo[oc]["systemAuxiliaryClass"]))
+ list2.extend(classinfo[oc]["auxiliaryClass"])
+ list2.extend(auxclasses(classinfo, classinfo[oc]["auxiliaryClass"]))
+ list2.extend(auxclasses(classinfo, supclasses(classinfo, oc)))
+ classinfo[oc]["AUXCLASSES"] = list2
+ list.extend(list2)
+ return list
+
+
+def subclasses(classinfo, oclist):
+ list = []
+ for oc in oclist:
+ list.extend(classinfo[oc]["SUBCLASSES"])
+ return list
+
+
+def posssuperiors(classinfo, oclist):
+ list = []
+ for oc in oclist:
+ if classinfo[oc].get("POSSSUPERIORS") is not None:
+ list.extend(classinfo[oc]["POSSSUPERIORS"])
+ else:
+ list2 = []
+ list2.extend(classinfo[oc]["systemPossSuperiors"])
+ list2.extend(classinfo[oc]["possSuperiors"])
+ list2.extend(posssuperiors(classinfo, supclasses(classinfo, oc)))
+ if opts.wspp:
+ # the WSPP docs suggest we should do this:
+ list2.extend(posssuperiors(classinfo, auxclasses(classinfo, [oc])))
+ else:
+ # but testing against w2k3 and w2k8 shows that we need to do this instead
+ list2.extend(subclasses(classinfo, list2))
+ classinfo[oc]["POSSSUPERIORS"] = list2
+ list.extend(list2)
+ return list
+
+
+def pull_classinfo(db):
+ """At startup we build a classinfo[] dictionary that holds all the information needed to construct the possible inferiors"""
+ classinfo = {}
+ res = db.search(base=schema_base,
+ expression="objectclass=classSchema",
+ attrs=["ldapDisplayName", "systemOnly", "objectClassCategory",
+ "possSuperiors", "systemPossSuperiors",
+ "auxiliaryClass", "systemAuxiliaryClass", "subClassOf"])
+ for r in res:
+ name = str(r["ldapDisplayName"][0])
+ classinfo[name] = {}
+ if str(r["systemOnly"]) == "TRUE":
+ classinfo[name]["systemOnly"] = True
+ else:
+ classinfo[name]["systemOnly"] = False
+ if r.get("objectClassCategory"):
+ classinfo[name]["objectClassCategory"] = int(r["objectClassCategory"][0])
+ else:
+ classinfo[name]["objectClassCategory"] = 0
+ for a in ["possSuperiors", "systemPossSuperiors",
+ "auxiliaryClass", "systemAuxiliaryClass",
+ "subClassOf"]:
+ classinfo[name][a] = []
+ if r.get(a):
+ for i in r[a]:
+ classinfo[name][a].append(str(i))
+
+ # build a list of subclasses for each class
+ def subclasses_recurse(subclasses, oc):
+ list = subclasses[oc]
+ for c in list:
+ list.extend(subclasses_recurse(subclasses, c))
+ return list
+
+ subclasses = {}
+ for oc in classinfo:
+ subclasses[oc] = []
+ for oc in classinfo:
+ for c in classinfo[oc]["subClassOf"]:
+ if not c == oc:
+ subclasses[c].append(oc)
+ for oc in classinfo:
+ classinfo[oc]["SUBCLASSES"] = uniq_list(subclasses_recurse(subclasses, oc))
+
+ return classinfo
+
+
+def is_in_list(list, c):
+ for a in list:
+ if c == a:
+ return True
+ return False
+
+
+def possible_inferiors_constructed(db, classinfo, c):
+ list = []
+ for oc in classinfo:
+ superiors = posssuperiors(classinfo, [oc])
+ if (is_in_list(superiors, c) and
+ classinfo[oc]["systemOnly"] == False and
+ classinfo[oc]["objectClassCategory"] != 2 and
+ classinfo[oc]["objectClassCategory"] != 3):
+ list.append(oc)
+ list = uniq_list(list)
+ list.sort()
+ return list
+
+
+def test_class(db, classinfo, oc):
+ """test to see if one objectclass returns the correct possibleInferiors"""
+ print("test: objectClass.%s" % oc)
+ poss1 = possible_inferiors_search(db, oc)
+ poss2 = possible_inferiors_constructed(db, classinfo, oc)
+ if poss1 != poss2:
+ print("failure: objectClass.%s [" % oc)
+ print("Returned incorrect list for objectclass %s" % oc)
+ print("search: %s" % poss1)
+ print("constructed: %s" % poss2)
+ for i in range(0, min(len(poss1), len(poss2))):
+ print("%30s %30s" % (poss1[i], poss2[i]))
+ print("]")
+ sys.exit(1)
+ else:
+ print("success: objectClass.%s" % oc)
+
+
+def get_object_classes(db):
+ """return a list of all object classes"""
+ list = []
+ for item in classinfo:
+ list.append(item)
+ return list
+
+
+classinfo = pull_classinfo(db)
+
+if objectclass is None:
+ for oc in get_object_classes(db):
+ test_class(db, classinfo, oc)
+else:
+ test_class(db, classinfo, objectclass)
+
+print("Lists match OK")
diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c b/source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c
new file mode 100644
index 0000000..885248e
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/test_audit_log.c
@@ -0,0 +1,2305 @@
+/*
+ Unit tests for the dsdb audit logging code code in audit_log.c
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+
+ 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 <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <cmocka.h>
+
+int ldb_audit_log_module_init(const char *version);
+#include "../audit_log.c"
+
+#include "lib/ldb/include/ldb_private.h"
+#include <regex.h>
+#include <float.h>
+
+/*
+ * Test helper to check ISO 8601 timestamps for validity
+ */
+static void check_timestamp(time_t before, const char* timestamp)
+{
+ int rc;
+ int usec, tz;
+ char c[2];
+ struct tm tm;
+ time_t after;
+ time_t actual;
+ struct timeval tv;
+
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ after = tv.tv_sec;
+
+ /*
+ * Convert the ISO 8601 timestamp into a time_t
+ * Note for convenience we ignore the value of the microsecond
+ * part of the time stamp.
+ */
+ rc = sscanf(
+ timestamp,
+ "%4d-%2d-%2dT%2d:%2d:%2d.%6d%1c%4d",
+ &tm.tm_year,
+ &tm.tm_mon,
+ &tm.tm_mday,
+ &tm.tm_hour,
+ &tm.tm_min,
+ &tm.tm_sec,
+ &usec,
+ c,
+ &tz);
+ assert_int_equal(9, rc);
+ tm.tm_year = tm.tm_year - 1900;
+ tm.tm_mon = tm.tm_mon - 1;
+ tm.tm_isdst = -1;
+ actual = mktime(&tm);
+
+ /*
+ * The timestamp should be before <= actual <= after
+ */
+ assert_in_range(actual, before, after);
+}
+
+static void test_has_password_changed(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_message *msg = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ /*
+ * Empty message
+ */
+ msg = ldb_msg_new(ldb);
+ assert_false(has_password_changed(msg));
+ TALLOC_FREE(msg);
+
+ /*
+ * No password attributes
+ */
+ msg = ldb_msg_new(ldb);
+ ldb_msg_add_string(msg, "attr01", "value01");
+ assert_false(has_password_changed(msg));
+ TALLOC_FREE(msg);
+
+ /*
+ * No password attributes >1 entries
+ */
+ msg = ldb_msg_new(ldb);
+ ldb_msg_add_string(msg, "attr01", "value01");
+ ldb_msg_add_string(msg, "attr02", "value03");
+ ldb_msg_add_string(msg, "attr03", "value03");
+ assert_false(has_password_changed(msg));
+ TALLOC_FREE(msg);
+
+ /*
+ * userPassword set
+ */
+ msg = ldb_msg_new(ldb);
+ ldb_msg_add_string(msg, "userPassword", "value01");
+ assert_true(has_password_changed(msg));
+ TALLOC_FREE(msg);
+
+ /*
+ * clearTextPassword set
+ */
+ msg = ldb_msg_new(ldb);
+ ldb_msg_add_string(msg, "clearTextPassword", "value01");
+ assert_true(has_password_changed(msg));
+ TALLOC_FREE(msg);
+
+ /*
+ * unicodePwd set
+ */
+ msg = ldb_msg_new(ldb);
+ ldb_msg_add_string(msg, "unicodePwd", "value01");
+ assert_true(has_password_changed(msg));
+ TALLOC_FREE(msg);
+
+ /*
+ * dBCSPwd set
+ */
+ msg = ldb_msg_new(ldb);
+ ldb_msg_add_string(msg, "dBCSPwd", "value01");
+ assert_true(has_password_changed(msg));
+ TALLOC_FREE(msg);
+
+ /*
+ * All attributes set
+ */
+ msg = ldb_msg_new(ldb);
+ ldb_msg_add_string(msg, "userPassword", "value01");
+ ldb_msg_add_string(msg, "clearTextPassword", "value02");
+ ldb_msg_add_string(msg, "unicodePwd", "value03");
+ ldb_msg_add_string(msg, "dBCSPwd", "value04");
+ assert_true(has_password_changed(msg));
+ TALLOC_FREE(msg);
+
+ /*
+ * first attribute is a password attribute
+ */
+ msg = ldb_msg_new(ldb);
+ ldb_msg_add_string(msg, "userPassword", "value01");
+ ldb_msg_add_string(msg, "attr02", "value02");
+ ldb_msg_add_string(msg, "attr03", "value03");
+ ldb_msg_add_string(msg, "attr04", "value04");
+ assert_true(has_password_changed(msg));
+ TALLOC_FREE(msg);
+
+ /*
+ * last attribute is a password attribute
+ */
+ msg = ldb_msg_new(ldb);
+ ldb_msg_add_string(msg, "attr01", "value01");
+ ldb_msg_add_string(msg, "attr02", "value02");
+ ldb_msg_add_string(msg, "attr03", "value03");
+ ldb_msg_add_string(msg, "clearTextPassword", "value04");
+ assert_true(has_password_changed(msg));
+ TALLOC_FREE(msg);
+
+ /*
+ * middle attribute is a password attribute
+ */
+ msg = ldb_msg_new(ldb);
+ ldb_msg_add_string(msg, "attr01", "value01");
+ ldb_msg_add_string(msg, "attr02", "value02");
+ ldb_msg_add_string(msg, "unicodePwd", "pwd");
+ ldb_msg_add_string(msg, "attr03", "value03");
+ ldb_msg_add_string(msg, "attr04", "value04");
+ assert_true(has_password_changed(msg));
+ TALLOC_FREE(msg);
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_get_password_action(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct dsdb_control_password_acl_validation *pav = NULL;
+ int ret;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ ldb = ldb_init(ctx, NULL);
+
+ /*
+ * Add request, will always be a reset
+ */
+ ldb_build_add_req(&req, ldb, ctx, NULL, NULL, NULL, NULL, NULL);
+ reply = talloc_zero(ctx, struct ldb_reply);
+ assert_string_equal("Reset", get_password_action(req, reply));
+ TALLOC_FREE(req);
+ TALLOC_FREE(reply);
+
+ /*
+ * No password control acl, expect "Reset"
+ */
+ ldb_build_mod_req(&req, ldb, ctx, NULL, NULL, NULL, NULL, NULL);
+ reply = talloc_zero(ctx, struct ldb_reply);
+ assert_string_equal("Reset", get_password_action(req, reply));
+ TALLOC_FREE(req);
+ TALLOC_FREE(reply);
+
+ /*
+ * dsdb_control_password_acl_validation reset = false, expect "Change"
+ */
+ ret = ldb_build_mod_req(&req, ldb, ctx, NULL, NULL, NULL, NULL, NULL);
+ assert_int_equal(ret, LDB_SUCCESS);
+ reply = talloc_zero(ctx, struct ldb_reply);
+ pav = talloc_zero(req, struct dsdb_control_password_acl_validation);
+
+ ldb_reply_add_control(
+ reply,
+ DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID,
+ false,
+ pav);
+ assert_string_equal("Change", get_password_action(req, reply));
+ TALLOC_FREE(req);
+ TALLOC_FREE(reply);
+
+ /*
+ * dsdb_control_password_acl_validation reset = true, expect "Reset"
+ */
+ ldb_build_mod_req(&req, ldb, ctx, NULL, NULL, NULL, NULL, NULL);
+ reply = talloc_zero(ctx, struct ldb_reply);
+ pav = talloc_zero(req, struct dsdb_control_password_acl_validation);
+ pav->pwd_reset = true;
+
+ ldb_reply_add_control(
+ reply,
+ DSDB_CONTROL_PASSWORD_ACL_VALIDATION_OID,
+ false,
+ pav);
+ assert_string_equal("Reset", get_password_action(req, reply));
+ TALLOC_FREE(req);
+ TALLOC_FREE(reply);
+
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * Test helper to validate a version object.
+ */
+static void check_version(struct json_t *version, int major, int minor)
+{
+ struct json_t *v = NULL;
+
+ assert_true(json_is_object(version));
+ assert_int_equal(2, json_object_size(version));
+
+ v = json_object_get(version, "major");
+ assert_non_null(v);
+ assert_int_equal(major, json_integer_value(v));
+
+ v = json_object_get(version, "minor");
+ assert_non_null(v);
+ assert_int_equal(minor, json_integer_value(v));
+}
+
+/*
+ * minimal unit test of operation_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_operation_json_empty(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+
+ struct json_object json;
+ json_t *audit = NULL;
+ json_t *v = NULL;
+ json_t *o = NULL;
+ time_t before;
+ struct timeval tv;
+ int rc;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+ audit_private = talloc_zero(ctx, struct audit_private);
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ req = talloc_zero(ctx, struct ldb_request);
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_SUCCESS;
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ before = tv.tv_sec;
+ json = operation_json(module, req, reply);
+ assert_int_equal(3, json_object_size(json.root));
+
+
+ v = json_object_get(json.root, "type");
+ assert_non_null(v);
+ assert_string_equal("dsdbChange", json_string_value(v));
+
+ v = json_object_get(json.root, "timestamp");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ check_timestamp(before, json_string_value(v));
+
+ audit = json_object_get(json.root, "dsdbChange");
+ assert_non_null(audit);
+ assert_true(json_is_object(audit));
+ assert_int_equal(10, json_object_size(audit));
+
+ o = json_object_get(audit, "version");
+ assert_non_null(o);
+ check_version(o, OPERATION_MAJOR, OPERATION_MINOR);
+
+ v = json_object_get(audit, "statusCode");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(LDB_SUCCESS, json_integer_value(v));
+
+ v = json_object_get(audit, "status");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("Success", json_string_value(v));
+
+ v = json_object_get(audit, "operation");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ /*
+ * Search operation constant is zero
+ */
+ assert_string_equal("Search", json_string_value(v));
+
+ v = json_object_get(audit, "remoteAddress");
+ assert_non_null(v);
+ assert_true(json_is_null(v));
+
+ v = json_object_get(audit, "userSid");
+ assert_non_null(v);
+ assert_true(json_is_null(v));
+
+ v = json_object_get(audit, "performedAsSystem");
+ assert_non_null(v);
+ assert_true(json_is_boolean(v));
+ assert_true(json_is_false(v));
+
+
+ v = json_object_get(audit, "dn");
+ assert_non_null(v);
+ assert_true(json_is_null(v));
+
+ v = json_object_get(audit, "transactionId");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(
+ "00000000-0000-0000-0000-000000000000",
+ json_string_value(v));
+
+ v = json_object_get(audit, "sessionId");
+ assert_non_null(v);
+ assert_true(json_is_null(v));
+
+ json_free(&json);
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * unit test of operation_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_operation_json(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+
+ struct tsocket_address *ts = NULL;
+
+ struct auth_session_info *sess = NULL;
+ struct security_token *token = NULL;
+ struct dom_sid sid;
+ const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ struct GUID session_id;
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct ldb_dn *dn = NULL;
+ const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+
+ struct ldb_message *msg = NULL;
+
+ struct json_object json;
+ json_t *audit = NULL;
+ json_t *v = NULL;
+ json_t *o = NULL;
+ json_t *a = NULL;
+ json_t *b = NULL;
+ json_t *c = NULL;
+ json_t *d = NULL;
+ json_t *e = NULL;
+ json_t *f = NULL;
+ json_t *g = NULL;
+ time_t before;
+ struct timeval tv;
+ int rc;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ audit_private = talloc_zero(ctx, struct audit_private);
+ GUID_from_string(TRANSACTION, &transaction_id);
+ audit_private->transaction_guid = transaction_id;
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(ldb, "remoteAddress", ts);
+
+ sess = talloc_zero(ctx, struct auth_session_info);
+ token = talloc_zero(ctx, struct security_token);
+ string_to_sid(&sid, SID);
+ token->num_sids = 1;
+ token->sids = &sid;
+ sess->security_token = token;
+ GUID_from_string(SESSION, &session_id);
+ sess->unique_session_token = session_id;
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
+
+ msg = talloc_zero(ctx, struct ldb_message);
+ dn = ldb_dn_new(ctx, ldb, DN);
+ msg->dn = dn;
+ ldb_msg_add_string(msg, "attribute", "the-value");
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_ERR_OPERATIONS_ERROR;
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ before = tv.tv_sec;
+ json = operation_json(module, req, reply);
+ assert_int_equal(3, json_object_size(json.root));
+
+ v = json_object_get(json.root, "type");
+ assert_non_null(v);
+ assert_string_equal("dsdbChange", json_string_value(v));
+
+ v = json_object_get(json.root, "timestamp");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ check_timestamp(before, json_string_value(v));
+
+ audit = json_object_get(json.root, "dsdbChange");
+ assert_non_null(audit);
+ assert_true(json_is_object(audit));
+ assert_int_equal(11, json_object_size(audit));
+
+ o = json_object_get(audit, "version");
+ assert_non_null(o);
+ check_version(o, OPERATION_MAJOR, OPERATION_MINOR);
+
+ v = json_object_get(audit, "statusCode");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(LDB_ERR_OPERATIONS_ERROR, json_integer_value(v));
+
+ v = json_object_get(audit, "status");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("Operations error", json_string_value(v));
+
+ v = json_object_get(audit, "operation");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("Add", json_string_value(v));
+
+ v = json_object_get(audit, "remoteAddress");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("ipv4:127.0.0.1:0", json_string_value(v));
+
+ v = json_object_get(audit, "userSid");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(SID, json_string_value(v));
+
+ v = json_object_get(audit, "performedAsSystem");
+ assert_non_null(v);
+ assert_true(json_is_boolean(v));
+ assert_true(json_is_false(v));
+
+ v = json_object_get(audit, "dn");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(DN, json_string_value(v));
+
+ v = json_object_get(audit, "transactionId");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(TRANSACTION, json_string_value(v));
+
+ v = json_object_get(audit, "sessionId");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(SESSION, json_string_value(v));
+
+ o = json_object_get(audit, "attributes");
+ assert_non_null(v);
+ assert_true(json_is_object(o));
+ assert_int_equal(1, json_object_size(o));
+
+ a = json_object_get(o, "attribute");
+ assert_non_null(a);
+ assert_true(json_is_object(a));
+
+ b = json_object_get(a, "actions");
+ assert_non_null(b);
+ assert_true(json_is_array(b));
+ assert_int_equal(1, json_array_size(b));
+
+ c = json_array_get(b, 0);
+ assert_non_null(c);
+ assert_true(json_is_object(c));
+
+ d = json_object_get(c, "action");
+ assert_non_null(d);
+ assert_true(json_is_string(d));
+ assert_string_equal("add", json_string_value(d));
+
+ e = json_object_get(c, "values");
+ assert_non_null(b);
+ assert_true(json_is_array(e));
+ assert_int_equal(1, json_array_size(e));
+
+ f = json_array_get(e, 0);
+ assert_non_null(f);
+ assert_true(json_is_object(f));
+ assert_int_equal(1, json_object_size(f));
+
+ g = json_object_get(f, "value");
+ assert_non_null(g);
+ assert_true(json_is_string(g));
+ assert_string_equal("the-value", json_string_value(g));
+
+ json_free(&json);
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * unit test of operation_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ * In this case for an operation performed as the system user.
+ */
+static void test_as_system_operation_json(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+
+ struct tsocket_address *ts = NULL;
+
+ struct auth_session_info *sess = NULL;
+ struct auth_session_info *sys_sess = NULL;
+ struct security_token *token = NULL;
+ struct security_token *sys_token = NULL;
+ struct dom_sid sid;
+ const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const SYS_SESSION = "7130cb06-2062-6a1b-409e-3514c26b1998";
+ struct GUID session_id;
+ struct GUID sys_session_id;
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct ldb_dn *dn = NULL;
+ const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+
+ struct ldb_message *msg = NULL;
+
+ struct json_object json;
+ json_t *audit = NULL;
+ json_t *v = NULL;
+ json_t *o = NULL;
+ json_t *a = NULL;
+ json_t *b = NULL;
+ json_t *c = NULL;
+ json_t *d = NULL;
+ json_t *e = NULL;
+ json_t *f = NULL;
+ json_t *g = NULL;
+ time_t before;
+ struct timeval tv;
+ int rc;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ audit_private = talloc_zero(ctx, struct audit_private);
+ GUID_from_string(TRANSACTION, &transaction_id);
+ audit_private->transaction_guid = transaction_id;
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(ldb, "remoteAddress", ts);
+
+ sess = talloc_zero(ctx, struct auth_session_info);
+ token = talloc_zero(ctx, struct security_token);
+ string_to_sid(&sid, SID);
+ token->num_sids = 1;
+ token->sids = &sid;
+ sess->security_token = token;
+ GUID_from_string(SESSION, &session_id);
+ sess->unique_session_token = session_id;
+ ldb_set_opaque(ldb, DSDB_NETWORK_SESSION_INFO, sess);
+
+ sys_sess = talloc_zero(ctx, struct auth_session_info);
+ sys_token = talloc_zero(ctx, struct security_token);
+ sys_token->num_sids = 1;
+ sys_token->sids = discard_const(&global_sid_System);
+ sys_sess->security_token = sys_token;
+ GUID_from_string(SYS_SESSION, &sys_session_id);
+ sess->unique_session_token = sys_session_id;
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sys_sess);
+
+ msg = talloc_zero(ctx, struct ldb_message);
+ dn = ldb_dn_new(ctx, ldb, DN);
+ msg->dn = dn;
+ ldb_msg_add_string(msg, "attribute", "the-value");
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_ERR_OPERATIONS_ERROR;
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ before = tv.tv_sec;
+ json = operation_json(module, req, reply);
+ assert_int_equal(3, json_object_size(json.root));
+
+ v = json_object_get(json.root, "type");
+ assert_non_null(v);
+ assert_string_equal("dsdbChange", json_string_value(v));
+
+ v = json_object_get(json.root, "timestamp");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ check_timestamp(before, json_string_value(v));
+
+ audit = json_object_get(json.root, "dsdbChange");
+ assert_non_null(audit);
+ assert_true(json_is_object(audit));
+ assert_int_equal(11, json_object_size(audit));
+
+ o = json_object_get(audit, "version");
+ assert_non_null(o);
+ check_version(o, OPERATION_MAJOR, OPERATION_MINOR);
+
+ v = json_object_get(audit, "statusCode");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(LDB_ERR_OPERATIONS_ERROR, json_integer_value(v));
+
+ v = json_object_get(audit, "status");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("Operations error", json_string_value(v));
+
+ v = json_object_get(audit, "operation");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("Add", json_string_value(v));
+
+ v = json_object_get(audit, "remoteAddress");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("ipv4:127.0.0.1:0", json_string_value(v));
+
+ v = json_object_get(audit, "userSid");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(SID, json_string_value(v));
+
+ v = json_object_get(audit, "performedAsSystem");
+ assert_non_null(v);
+ assert_true(json_is_boolean(v));
+ assert_true(json_is_true(v));
+
+ v = json_object_get(audit, "dn");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(DN, json_string_value(v));
+
+ v = json_object_get(audit, "transactionId");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(TRANSACTION, json_string_value(v));
+
+ v = json_object_get(audit, "sessionId");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(SYS_SESSION, json_string_value(v));
+
+ o = json_object_get(audit, "attributes");
+ assert_non_null(v);
+ assert_true(json_is_object(o));
+ assert_int_equal(1, json_object_size(o));
+
+ a = json_object_get(o, "attribute");
+ assert_non_null(a);
+ assert_true(json_is_object(a));
+
+ b = json_object_get(a, "actions");
+ assert_non_null(b);
+ assert_true(json_is_array(b));
+ assert_int_equal(1, json_array_size(b));
+
+ c = json_array_get(b, 0);
+ assert_non_null(c);
+ assert_true(json_is_object(c));
+
+ d = json_object_get(c, "action");
+ assert_non_null(d);
+ assert_true(json_is_string(d));
+ assert_string_equal("add", json_string_value(d));
+
+ e = json_object_get(c, "values");
+ assert_non_null(b);
+ assert_true(json_is_array(e));
+ assert_int_equal(1, json_array_size(e));
+
+ f = json_array_get(e, 0);
+ assert_non_null(f);
+ assert_true(json_is_object(f));
+ assert_int_equal(1, json_object_size(f));
+
+ g = json_object_get(f, "value");
+ assert_non_null(g);
+ assert_true(json_is_string(g));
+ assert_string_equal("the-value", json_string_value(g));
+
+ json_free(&json);
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * minimal unit test of password_change_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_password_change_json_empty(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+
+ struct json_object json;
+ json_t *audit = NULL;
+ json_t *v = NULL;
+ json_t *o = NULL;
+ time_t before;
+ struct timeval tv;
+ int rc;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+ audit_private = talloc_zero(ctx, struct audit_private);
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ req = talloc_zero(ctx, struct ldb_request);
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_SUCCESS;
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ before = tv.tv_sec;
+ json = password_change_json(module, req, reply);
+ assert_int_equal(3, json_object_size(json.root));
+
+
+ v = json_object_get(json.root, "type");
+ assert_non_null(v);
+ assert_string_equal("passwordChange", json_string_value(v));
+
+ v = json_object_get(json.root, "timestamp");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ check_timestamp(before, json_string_value(v));
+
+ audit = json_object_get(json.root, "passwordChange");
+ assert_non_null(audit);
+ assert_true(json_is_object(audit));
+ assert_int_equal(10, json_object_size(audit));
+
+ o = json_object_get(audit, "version");
+ assert_non_null(o);
+
+ v = json_object_get(audit, "eventId");
+ assert_non_null(v);
+
+ v = json_object_get(audit, "statusCode");
+ assert_non_null(v);
+
+ v = json_object_get(audit, "status");
+ assert_non_null(v);
+
+ v = json_object_get(audit, "remoteAddress");
+ assert_non_null(v);
+
+ v = json_object_get(audit, "userSid");
+ assert_non_null(v);
+
+ v = json_object_get(audit, "dn");
+ assert_non_null(v);
+
+ v = json_object_get(audit, "transactionId");
+ assert_non_null(v);
+
+ v = json_object_get(audit, "sessionId");
+ assert_non_null(v);
+
+ v = json_object_get(audit, "action");
+ assert_non_null(v);
+
+ json_free(&json);
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * minimal unit test of password_change_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_password_change_json(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+
+ struct tsocket_address *ts = NULL;
+
+ struct auth_session_info *sess = NULL;
+ struct security_token *token = NULL;
+ struct dom_sid sid;
+ const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ struct GUID session_id;
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct ldb_dn *dn = NULL;
+ const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+
+ struct ldb_message *msg = NULL;
+
+ struct json_object json;
+ json_t *audit = NULL;
+ json_t *v = NULL;
+ json_t *o = NULL;
+ time_t before;
+ struct timeval tv;
+ int rc;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ audit_private = talloc_zero(ctx, struct audit_private);
+ GUID_from_string(TRANSACTION, &transaction_id);
+ audit_private->transaction_guid = transaction_id;
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(ldb, "remoteAddress", ts);
+
+ sess = talloc_zero(ctx, struct auth_session_info);
+ token = talloc_zero(ctx, struct security_token);
+ string_to_sid(&sid, SID);
+ token->num_sids = 1;
+ token->sids = &sid;
+ sess->security_token = token;
+ GUID_from_string(SESSION, &session_id);
+ sess->unique_session_token = session_id;
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
+
+ msg = talloc_zero(ctx, struct ldb_message);
+ dn = ldb_dn_new(ctx, ldb, DN);
+ msg->dn = dn;
+ ldb_msg_add_string(msg, "planTextPassword", "super-secret");
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_SUCCESS;
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ before = tv.tv_sec;
+ json = password_change_json(module, req, reply);
+ assert_int_equal(3, json_object_size(json.root));
+
+
+ v = json_object_get(json.root, "type");
+ assert_non_null(v);
+ assert_string_equal("passwordChange", json_string_value(v));
+
+ v = json_object_get(json.root, "timestamp");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ check_timestamp(before, json_string_value(v));
+
+ audit = json_object_get(json.root, "passwordChange");
+ assert_non_null(audit);
+ assert_true(json_is_object(audit));
+ assert_int_equal(10, json_object_size(audit));
+
+ o = json_object_get(audit, "version");
+ assert_non_null(o);
+ check_version(o, PASSWORD_MAJOR,PASSWORD_MINOR);
+
+ v = json_object_get(audit, "eventId");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(EVT_ID_PASSWORD_RESET, json_integer_value(v));
+
+ v = json_object_get(audit, "statusCode");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(LDB_SUCCESS, json_integer_value(v));
+
+ v = json_object_get(audit, "status");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("Success", json_string_value(v));
+
+ v = json_object_get(audit, "remoteAddress");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("ipv4:127.0.0.1:0", json_string_value(v));
+
+ v = json_object_get(audit, "userSid");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(SID, json_string_value(v));
+
+ v = json_object_get(audit, "dn");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(DN, json_string_value(v));
+
+ v = json_object_get(audit, "transactionId");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(TRANSACTION, json_string_value(v));
+
+ v = json_object_get(audit, "sessionId");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(SESSION, json_string_value(v));
+
+ v = json_object_get(audit, "action");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("Reset", json_string_value(v));
+
+ json_free(&json);
+ TALLOC_FREE(ctx);
+
+}
+
+
+/*
+ * minimal unit test of transaction_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_transaction_json(void **state)
+{
+
+ struct GUID guid;
+ const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct json_object json;
+ json_t *audit = NULL;
+ json_t *v = NULL;
+ json_t *o = NULL;
+ time_t before;
+ struct timeval tv;
+ int rc;
+
+ GUID_from_string(GUID, &guid);
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ before = tv.tv_sec;
+ json = transaction_json("delete", &guid, 10000099);
+
+ assert_int_equal(3, json_object_size(json.root));
+
+
+ v = json_object_get(json.root, "type");
+ assert_non_null(v);
+ assert_string_equal("dsdbTransaction", json_string_value(v));
+
+ v = json_object_get(json.root, "timestamp");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ check_timestamp(before, json_string_value(v));
+
+ audit = json_object_get(json.root, "dsdbTransaction");
+ assert_non_null(audit);
+ assert_true(json_is_object(audit));
+ assert_int_equal(4, json_object_size(audit));
+
+ o = json_object_get(audit, "version");
+ assert_non_null(o);
+ check_version(o, TRANSACTION_MAJOR, TRANSACTION_MINOR);
+
+ v = json_object_get(audit, "transactionId");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(GUID, json_string_value(v));
+
+ v = json_object_get(audit, "action");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("delete", json_string_value(v));
+
+ v = json_object_get(audit, "duration");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(10000099, json_integer_value(v));
+
+ json_free(&json);
+
+}
+
+/*
+ * minimal unit test of commit_failure_json, that ensures that all the
+ * expected attributes and objects are in the json object.
+ */
+static void test_commit_failure_json(void **state)
+{
+
+ struct GUID guid;
+ const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct json_object json;
+ json_t *audit = NULL;
+ json_t *v = NULL;
+ json_t *o = NULL;
+ time_t before;
+ struct timeval tv;
+ int rc;
+
+ GUID_from_string(GUID, &guid);
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ before = tv.tv_sec;
+ json = commit_failure_json(
+ "prepare",
+ 987876,
+ LDB_ERR_OPERATIONS_ERROR,
+ "because",
+ &guid);
+
+ assert_int_equal(3, json_object_size(json.root));
+
+
+ v = json_object_get(json.root, "type");
+ assert_non_null(v);
+ assert_string_equal("dsdbTransaction", json_string_value(v));
+
+ v = json_object_get(json.root, "timestamp");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ check_timestamp(before, json_string_value(v));
+
+ audit = json_object_get(json.root, "dsdbTransaction");
+ assert_non_null(audit);
+ assert_true(json_is_object(audit));
+ assert_int_equal(7, json_object_size(audit));
+
+ o = json_object_get(audit, "version");
+ assert_non_null(o);
+ check_version(o, TRANSACTION_MAJOR, TRANSACTION_MINOR);
+
+ v = json_object_get(audit, "transactionId");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(GUID, json_string_value(v));
+
+ v = json_object_get(audit, "action");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("prepare", json_string_value(v));
+
+ v = json_object_get(audit, "statusCode");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(LDB_ERR_OPERATIONS_ERROR, json_integer_value(v));
+
+ v = json_object_get(audit, "status");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("Operations error", json_string_value(v));
+ v = json_object_get(audit, "status");
+ assert_non_null(v);
+
+ v = json_object_get(audit, "reason");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("because", json_string_value(v));
+
+ v = json_object_get(audit, "duration");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(987876, json_integer_value(v));
+
+ json_free(&json);
+
+}
+
+/*
+ * minimal unit test of replicated_update_json, that ensures that all the
+ * expected attributes and objects are in the json object.
+ */
+static void test_replicated_update_json_empty(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+ struct dsdb_extended_replicated_objects *ro = NULL;
+ struct repsFromTo1 *source_dsa = NULL;
+
+ struct json_object json;
+ json_t *audit = NULL;
+ json_t *v = NULL;
+ json_t *o = NULL;
+ time_t before;
+ struct timeval tv;
+ int rc;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+ audit_private = talloc_zero(ctx, struct audit_private);
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ source_dsa = talloc_zero(ctx, struct repsFromTo1);
+ ro = talloc_zero(ctx, struct dsdb_extended_replicated_objects);
+ ro->source_dsa = source_dsa;
+ req = talloc_zero(ctx, struct ldb_request);
+ req->op.extended.data = ro;
+ req->operation = LDB_EXTENDED;
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_SUCCESS;
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ before = tv.tv_sec;
+ json = replicated_update_json(module, req, reply);
+ assert_int_equal(3, json_object_size(json.root));
+
+
+ v = json_object_get(json.root, "type");
+ assert_non_null(v);
+ assert_string_equal("replicatedUpdate", json_string_value(v));
+
+ v = json_object_get(json.root, "timestamp");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ check_timestamp(before, json_string_value(v));
+
+ audit = json_object_get(json.root, "replicatedUpdate");
+ assert_non_null(audit);
+ assert_true(json_is_object(audit));
+ assert_int_equal(11, json_object_size(audit));
+
+ o = json_object_get(audit, "version");
+ assert_non_null(o);
+ check_version(o, REPLICATION_MAJOR, REPLICATION_MINOR);
+
+ v = json_object_get(audit, "statusCode");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(LDB_SUCCESS, json_integer_value(v));
+
+ v = json_object_get(audit, "status");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("Success", json_string_value(v));
+
+ v = json_object_get(audit, "transactionId");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(
+ "00000000-0000-0000-0000-000000000000",
+ json_string_value(v));
+
+ v = json_object_get(audit, "objectCount");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(0, json_integer_value(v));
+
+ v = json_object_get(audit, "linkCount");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(0, json_integer_value(v));
+
+ v = json_object_get(audit, "partitionDN");
+ assert_non_null(v);
+ assert_true(json_is_null(v));
+
+ v = json_object_get(audit, "error");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(
+ "The operation completed successfully.",
+ json_string_value(v));
+
+ v = json_object_get(audit, "errorCode");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(0, json_integer_value(v));
+
+ v = json_object_get(audit, "sourceDsa");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(
+ "00000000-0000-0000-0000-000000000000",
+ json_string_value(v));
+
+ v = json_object_get(audit, "invocationId");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(
+ "00000000-0000-0000-0000-000000000000",
+ json_string_value(v));
+
+ json_free(&json);
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * unit test of replicated_update_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_replicated_update_json(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+ struct dsdb_extended_replicated_objects *ro = NULL;
+ struct repsFromTo1 *source_dsa = NULL;
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct ldb_dn *dn = NULL;
+ const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+
+ struct GUID source_dsa_obj_guid;
+ const char *const SOURCE_DSA = "7130cb06-2062-6a1b-409e-3514c26b1793";
+
+ struct GUID invocation_id;
+ const char *const INVOCATION_ID =
+ "7130cb06-2062-6a1b-409e-3514c26b1893";
+ struct json_object json;
+ json_t *audit = NULL;
+ json_t *v = NULL;
+ json_t *o = NULL;
+ time_t before;
+ struct timeval tv;
+ int rc;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ audit_private = talloc_zero(ctx, struct audit_private);
+ GUID_from_string(TRANSACTION, &transaction_id);
+ audit_private->transaction_guid = transaction_id;
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ dn = ldb_dn_new(ctx, ldb, DN);
+ GUID_from_string(SOURCE_DSA, &source_dsa_obj_guid);
+ GUID_from_string(INVOCATION_ID, &invocation_id);
+ source_dsa = talloc_zero(ctx, struct repsFromTo1);
+ source_dsa->source_dsa_obj_guid = source_dsa_obj_guid;
+ source_dsa->source_dsa_invocation_id = invocation_id;
+
+ ro = talloc_zero(ctx, struct dsdb_extended_replicated_objects);
+ ro->source_dsa = source_dsa;
+ ro->num_objects = 808;
+ ro->linked_attributes_count = 2910;
+ ro->partition_dn = dn;
+ ro->error = WERR_NOT_SUPPORTED;
+
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->op.extended.data = ro;
+ req->operation = LDB_EXTENDED;
+
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_ERR_NO_SUCH_OBJECT;
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ before = tv.tv_sec;
+ json = replicated_update_json(module, req, reply);
+ assert_int_equal(3, json_object_size(json.root));
+
+
+ v = json_object_get(json.root, "type");
+ assert_non_null(v);
+ assert_string_equal("replicatedUpdate", json_string_value(v));
+
+ v = json_object_get(json.root, "timestamp");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ check_timestamp(before, json_string_value(v));
+
+ audit = json_object_get(json.root, "replicatedUpdate");
+ assert_non_null(audit);
+ assert_true(json_is_object(audit));
+ assert_int_equal(11, json_object_size(audit));
+
+ o = json_object_get(audit, "version");
+ assert_non_null(o);
+ check_version(o, REPLICATION_MAJOR, REPLICATION_MINOR);
+
+ v = json_object_get(audit, "statusCode");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(LDB_ERR_NO_SUCH_OBJECT, json_integer_value(v));
+
+ v = json_object_get(audit, "status");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("No such object", json_string_value(v));
+
+ v = json_object_get(audit, "transactionId");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(TRANSACTION, json_string_value(v));
+
+ v = json_object_get(audit, "objectCount");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(808, json_integer_value(v));
+
+ v = json_object_get(audit, "linkCount");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(2910, json_integer_value(v));
+
+ v = json_object_get(audit, "partitionDN");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(DN, json_string_value(v));
+
+ v = json_object_get(audit, "error");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(
+ "The request is not supported.",
+ json_string_value(v));
+
+ v = json_object_get(audit, "errorCode");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(W_ERROR_V(WERR_NOT_SUPPORTED), json_integer_value(v));
+
+ v = json_object_get(audit, "sourceDsa");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(SOURCE_DSA, json_string_value(v));
+
+ v = json_object_get(audit, "invocationId");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal(INVOCATION_ID, json_string_value(v));
+
+ json_free(&json);
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * minimal unit test of operation_human_readable, that ensures that all the
+ * expected attributes and objects are in the json object.
+ */
+static void test_operation_hr_empty(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+
+ char *line = NULL;
+ const char *rs = NULL;
+ regex_t regex;
+
+ int ret;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+ audit_private = talloc_zero(ctx, struct audit_private);
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ req = talloc_zero(ctx, struct ldb_request);
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_SUCCESS;
+
+ line = operation_human_readable(ctx, module, req, reply);
+ assert_non_null(line);
+
+ /*
+ * We ignore the timestamp to make this test a little easier
+ * to write.
+ */
+ rs = "\\[Search] at \\["
+ "[^[]*"
+ "\\] status \\[Success\\] remote host \\[Unknown\\]"
+ " SID \\[(NULL SID)\\] DN \\[(null)\\]";
+
+ ret = regcomp(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * unit test of operation_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_operation_hr(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+
+ struct tsocket_address *ts = NULL;
+
+ struct auth_session_info *sess = NULL;
+ struct security_token *token = NULL;
+ struct dom_sid sid;
+ const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ struct GUID session_id;
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct ldb_dn *dn = NULL;
+ const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+
+ struct ldb_message *msg = NULL;
+
+ char *line = NULL;
+ const char *rs = NULL;
+ regex_t regex;
+
+ int ret;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ audit_private = talloc_zero(ctx, struct audit_private);
+ GUID_from_string(TRANSACTION, &transaction_id);
+ audit_private->transaction_guid = transaction_id;
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(ldb, "remoteAddress", ts);
+
+ sess = talloc_zero(ctx, struct auth_session_info);
+ token = talloc_zero(ctx, struct security_token);
+ string_to_sid(&sid, SID);
+ token->num_sids = 1;
+ token->sids = &sid;
+ sess->security_token = token;
+ GUID_from_string(SESSION, &session_id);
+ sess->unique_session_token = session_id;
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
+
+ msg = talloc_zero(ctx, struct ldb_message);
+ dn = ldb_dn_new(ctx, ldb, DN);
+ msg->dn = dn;
+ ldb_msg_add_string(msg, "attribute", "the-value");
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_SUCCESS;
+
+ line = operation_human_readable(ctx, module, req, reply);
+ assert_non_null(line);
+
+ /*
+ * We ignore the timestamp to make this test a little easier
+ * to write.
+ */
+ rs = "\\[Add\\] at \\["
+ "[^]]*"
+ "\\] status \\[Success\\] "
+ "remote host \\[ipv4:127.0.0.1:0\\] "
+ "SID \\[S-1-5-21-2470180966-3899876309-2637894779\\] "
+ "DN \\[dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG\\] "
+ "attributes \\[attribute \\[the-value\\]\\]";
+
+ ret = regcomp(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * unit test of operation_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ * In this case the operation is being performed in a system session.
+ */
+static void test_as_system_operation_hr(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+
+ struct tsocket_address *ts = NULL;
+
+ struct auth_session_info *sess = NULL;
+ struct auth_session_info *sys_sess = NULL;
+ struct security_token *token = NULL;
+ struct security_token *sys_token = NULL;
+ struct dom_sid sid;
+ const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const SYS_SESSION = "7130cb06-2062-6a1b-409e-3514c26b1999";
+ struct GUID session_id;
+ struct GUID sys_session_id;
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct ldb_dn *dn = NULL;
+ const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+
+ struct ldb_message *msg = NULL;
+
+ char *line = NULL;
+ const char *rs = NULL;
+ regex_t regex;
+
+ int ret;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ audit_private = talloc_zero(ctx, struct audit_private);
+ GUID_from_string(TRANSACTION, &transaction_id);
+ audit_private->transaction_guid = transaction_id;
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(ldb, "remoteAddress", ts);
+
+ sess = talloc_zero(ctx, struct auth_session_info);
+ token = talloc_zero(ctx, struct security_token);
+ string_to_sid(&sid, SID);
+ token->num_sids = 1;
+ token->sids = &sid;
+ sess->security_token = token;
+ GUID_from_string(SESSION, &session_id);
+ sess->unique_session_token = session_id;
+ ldb_set_opaque(ldb, DSDB_NETWORK_SESSION_INFO, sess);
+
+ sys_sess = talloc_zero(ctx, struct auth_session_info);
+ sys_token = talloc_zero(ctx, struct security_token);
+ sys_token->num_sids = 1;
+ sys_token->sids = discard_const(&global_sid_System);
+ sys_sess->security_token = sys_token;
+ GUID_from_string(SYS_SESSION, &sys_session_id);
+ sess->unique_session_token = sys_session_id;
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sys_sess);
+
+ msg = talloc_zero(ctx, struct ldb_message);
+ dn = ldb_dn_new(ctx, ldb, DN);
+ msg->dn = dn;
+ ldb_msg_add_string(msg, "attribute", "the-value");
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_SUCCESS;
+
+ line = operation_human_readable(ctx, module, req, reply);
+ assert_non_null(line);
+
+ /*
+ * We ignore the timestamp to make this test a little easier
+ * to write.
+ */
+ rs = "\\[Add\\] at \\["
+ "[^]]*"
+ "\\] status \\[Success\\] "
+ "remote host \\[ipv4:127.0.0.1:0\\] "
+ "SID \\[S-1-5-21-2470180966-3899876309-2637894779\\] "
+ "DN \\[dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG\\] "
+ "attributes \\[attribute \\[the-value\\]\\]";
+
+ ret = regcomp(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * minimal unit test of password_change_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_password_change_hr_empty(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+
+ char *line = NULL;
+ const char *rs = NULL;
+ regex_t regex;
+ int ret;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+ audit_private = talloc_zero(ctx, struct audit_private);
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ req = talloc_zero(ctx, struct ldb_request);
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_SUCCESS;
+
+ line = password_change_human_readable(ctx, module, req, reply);
+ assert_non_null(line);
+
+ /*
+ * We ignore the timestamp to make this test a little easier
+ * to write.
+ */
+ rs = "\\[Reset] at \\["
+ "[^[]*"
+ "\\] status \\[Success\\] remote host \\[Unknown\\]"
+ " SID \\[(NULL SID)\\] DN \\[(null)\\]";
+
+ ret = regcomp(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * minimal unit test of password_change_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_password_change_hr(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+
+ struct tsocket_address *ts = NULL;
+
+ struct auth_session_info *sess = NULL;
+ struct security_token *token = NULL;
+ struct dom_sid sid;
+ const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ struct GUID session_id;
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct ldb_dn *dn = NULL;
+ const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+
+ struct ldb_message *msg = NULL;
+
+ char *line = NULL;
+ const char *rs = NULL;
+ regex_t regex;
+ int ret;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ audit_private = talloc_zero(ctx, struct audit_private);
+ GUID_from_string(TRANSACTION, &transaction_id);
+ audit_private->transaction_guid = transaction_id;
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(ldb, "remoteAddress", ts);
+
+ sess = talloc_zero(ctx, struct auth_session_info);
+ token = talloc_zero(ctx, struct security_token);
+ string_to_sid(&sid, SID);
+ token->num_sids = 1;
+ token->sids = &sid;
+ sess->security_token = token;
+ GUID_from_string(SESSION, &session_id);
+ sess->unique_session_token = session_id;
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
+
+ msg = talloc_zero(ctx, struct ldb_message);
+ dn = ldb_dn_new(ctx, ldb, DN);
+ msg->dn = dn;
+ ldb_msg_add_string(msg, "planTextPassword", "super-secret");
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_SUCCESS;
+
+ line = password_change_human_readable(ctx, module, req, reply);
+ assert_non_null(line);
+
+ /*
+ * We ignore the timestamp to make this test a little easier
+ * to write.
+ */
+ rs = "\\[Reset\\] at \\["
+ "[^[]*"
+ "\\] status \\[Success\\] "
+ "remote host \\[ipv4:127.0.0.1:0\\] "
+ "SID \\[S-1-5-21-2470180966-3899876309-2637894779\\] "
+ "DN \\[dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG\\]";
+
+ ret = regcomp(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * minimal unit test of transaction_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_transaction_hr(void **state)
+{
+
+ struct GUID guid;
+ const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ char *line = NULL;
+ const char *rs = NULL;
+ regex_t regex;
+ int ret;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ GUID_from_string(GUID, &guid);
+
+ line = transaction_human_readable(ctx, "delete", 23);
+ assert_non_null(line);
+
+ /*
+ * We ignore the timestamp to make this test a little easier
+ * to write.
+ */
+ rs = "\\[delete] at \\[[^[]*\\] duration \\[23\\]";
+
+ ret = regcomp(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * minimal unit test of commit_failure_hr, that ensures
+ * that all the expected conten is in the log entry.
+ */
+static void test_commit_failure_hr(void **state)
+{
+
+ struct GUID guid;
+ const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ char *line = NULL;
+ const char *rs = NULL;
+ regex_t regex;
+ int ret;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ GUID_from_string(GUID, &guid);
+
+ line = commit_failure_human_readable(
+ ctx,
+ "commit",
+ 789345,
+ LDB_ERR_OPERATIONS_ERROR,
+ "because");
+
+ assert_non_null(line);
+
+ /*
+ * We ignore the timestamp to make this test a little easier
+ * to write.
+ */
+ rs = "\\[commit\\] at \\[[^[]*\\] duration \\[789345\\] "
+ "status \\[1\\] reason \\[because\\]";
+
+ ret = regcomp(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ TALLOC_FREE(ctx);
+}
+
+static void test_add_transaction_id(void **state)
+{
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct audit_private *audit_private = NULL;
+ struct GUID guid;
+ const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ struct ldb_control * control = NULL;
+ int status;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ audit_private = talloc_zero(ctx, struct audit_private);
+ GUID_from_string(GUID, &guid);
+ audit_private->transaction_guid = guid;
+
+ module = talloc_zero(ctx, struct ldb_module);
+ ldb_module_set_private(module, audit_private);
+
+ req = talloc_zero(ctx, struct ldb_request);
+
+ status = add_transaction_id(module, req);
+ assert_int_equal(LDB_SUCCESS, status);
+
+ control = ldb_request_get_control(
+ req,
+ DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID);
+ assert_non_null(control);
+ assert_memory_equal(
+ &audit_private->transaction_guid,
+ control->data,
+ sizeof(struct GUID));
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_log_attributes(void **state)
+{
+ struct ldb_message *msg = NULL;
+
+ char *buf = NULL;
+ char *str = NULL;
+ char lv[MAX_LENGTH+2];
+ char ex[MAX_LENGTH+80];
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+
+ /*
+ * Test an empty message
+ * Should get empty attributes representation.
+ */
+ buf = talloc_zero(ctx, char);
+ msg = talloc_zero(ctx, struct ldb_message);
+
+ str = log_attributes(ctx, buf, LDB_ADD, msg);
+ assert_string_equal("", str);
+
+ TALLOC_FREE(str);
+ TALLOC_FREE(msg);
+
+ /*
+ * Test a message with a single secret attribute
+ */
+ buf = talloc_zero(ctx, char);
+ msg = talloc_zero(ctx, struct ldb_message);
+ ldb_msg_add_string(msg, "clearTextPassword", "secret");
+
+ str = log_attributes(ctx, buf, LDB_ADD, msg);
+ assert_string_equal(
+ "clearTextPassword [REDACTED SECRET ATTRIBUTE]",
+ str);
+ TALLOC_FREE(str);
+ /*
+ * Test as a modify message, should add an action
+ * action will be unknown as there are no ACL's set
+ */
+ buf = talloc_zero(ctx, char);
+ str = log_attributes(ctx, buf, LDB_MODIFY, msg);
+ assert_string_equal(
+ "unknown: clearTextPassword [REDACTED SECRET ATTRIBUTE]",
+ str);
+
+ TALLOC_FREE(str);
+ TALLOC_FREE(msg);
+
+ /*
+ * Test a message with a single attribute, single valued attribute
+ */
+ buf = talloc_zero(ctx, char);
+ msg = talloc_zero(ctx, struct ldb_message);
+ ldb_msg_add_string(msg, "attribute", "value");
+
+ str = log_attributes(ctx, buf, LDB_ADD, msg);
+ assert_string_equal(
+ "attribute [value]",
+ str);
+
+ TALLOC_FREE(str);
+ TALLOC_FREE(msg);
+
+ /*
+ * Test a message with a single attribute, single valued attribute
+ * And as a modify
+ */
+ buf = talloc_zero(ctx, char);
+ msg = talloc_zero(ctx, struct ldb_message);
+ ldb_msg_add_string(msg, "attribute", "value");
+
+ str = log_attributes(ctx, buf, LDB_MODIFY, msg);
+ assert_string_equal(
+ "unknown: attribute [value]",
+ str);
+
+ TALLOC_FREE(str);
+ TALLOC_FREE(msg);
+
+ /*
+ * Test a message with multiple attributes and a multi-valued attribute
+ *
+ */
+ buf = talloc_zero(ctx, char);
+ msg = talloc_zero(ctx, struct ldb_message);
+ ldb_msg_add_string(msg, "attribute01", "value01");
+ ldb_msg_add_string(msg, "attribute02", "value02");
+ ldb_msg_add_string(msg, "attribute02", "value03");
+
+ str = log_attributes(ctx, buf, LDB_MODIFY, msg);
+ assert_string_equal(
+ "unknown: attribute01 [value01] "
+ "unknown: attribute02 [value02] [value03]",
+ str);
+
+ TALLOC_FREE(str);
+ TALLOC_FREE(msg);
+
+ /*
+ * Test a message with a single attribute, single valued attribute
+ * with a non printable character. Should be base64 encoded
+ */
+ buf = talloc_zero(ctx, char);
+ msg = talloc_zero(ctx, struct ldb_message);
+ ldb_msg_add_string(msg, "attribute", "value\n");
+
+ str = log_attributes(ctx, buf, LDB_ADD, msg);
+ assert_string_equal("attribute {dmFsdWUK}", str);
+
+ TALLOC_FREE(str);
+ TALLOC_FREE(msg);
+
+ /*
+ * Test a message with a single valued attribute
+ * with more than MAX_LENGTH characters, should be truncated with
+ * trailing ...
+ */
+ buf = talloc_zero(ctx, char);
+ msg = talloc_zero(ctx, struct ldb_message);
+ memset(lv, '\0', sizeof(lv));
+ memset(lv, 'x', MAX_LENGTH+1);
+ ldb_msg_add_string(msg, "attribute", lv);
+
+ str = log_attributes(ctx, buf, LDB_ADD, msg);
+ snprintf(ex, sizeof(ex), "attribute [%.*s...]", MAX_LENGTH, lv);
+ assert_string_equal(ex, str);
+
+ TALLOC_FREE(str);
+ TALLOC_FREE(msg);
+
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * minimal unit test of replicated_update_human_readable
+ */
+static void test_replicated_update_hr_empty(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+ struct dsdb_extended_replicated_objects *ro = NULL;
+ struct repsFromTo1 *source_dsa = NULL;
+
+ const char* line = NULL;
+ const char *rs = NULL;
+ regex_t regex;
+ int ret;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+ audit_private = talloc_zero(ctx, struct audit_private);
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ source_dsa = talloc_zero(ctx, struct repsFromTo1);
+ ro = talloc_zero(ctx, struct dsdb_extended_replicated_objects);
+ ro->source_dsa = source_dsa;
+ req = talloc_zero(ctx, struct ldb_request);
+ req->op.extended.data = ro;
+ req->operation = LDB_EXTENDED;
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_SUCCESS;
+
+ line = replicated_update_human_readable(ctx, module, req, reply);
+ assert_non_null(line);
+ /*
+ * We ignore the timestamp to make this test a little easier
+ * to write.
+ */
+ rs = "at \\[[^[]*\\] "
+ "status \\[Success\\] "
+ "error \\[The operation completed successfully.\\] "
+ "partition \\[(null)\\] objects \\[0\\] links \\[0\\] "
+ "object \\[00000000-0000-0000-0000-000000000000\\] "
+ "invocation \\[00000000-0000-0000-0000-000000000000\\]";
+
+ ret = regcomp(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * unit test of replicated_update_human_readable
+ */
+static void test_replicated_update_hr(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+ struct dsdb_extended_replicated_objects *ro = NULL;
+ struct repsFromTo1 *source_dsa = NULL;
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct ldb_dn *dn = NULL;
+ const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+
+ struct GUID source_dsa_obj_guid;
+ const char *const SOURCE_DSA = "7130cb06-2062-6a1b-409e-3514c26b1793";
+
+ struct GUID invocation_id;
+ const char *const INVOCATION_ID =
+ "7130cb06-2062-6a1b-409e-3514c26b1893";
+
+ const char* line = NULL;
+ const char *rs = NULL;
+ regex_t regex;
+ int ret;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ audit_private = talloc_zero(ctx, struct audit_private);
+ GUID_from_string(TRANSACTION, &transaction_id);
+ audit_private->transaction_guid = transaction_id;
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ dn = ldb_dn_new(ctx, ldb, DN);
+ GUID_from_string(SOURCE_DSA, &source_dsa_obj_guid);
+ GUID_from_string(INVOCATION_ID, &invocation_id);
+ source_dsa = talloc_zero(ctx, struct repsFromTo1);
+ source_dsa->source_dsa_obj_guid = source_dsa_obj_guid;
+ source_dsa->source_dsa_invocation_id = invocation_id;
+
+ ro = talloc_zero(ctx, struct dsdb_extended_replicated_objects);
+ ro->source_dsa = source_dsa;
+ ro->num_objects = 808;
+ ro->linked_attributes_count = 2910;
+ ro->partition_dn = dn;
+ ro->error = WERR_NOT_SUPPORTED;
+
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->op.extended.data = ro;
+ req->operation = LDB_EXTENDED;
+
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_ERR_NO_SUCH_OBJECT;
+
+ line = replicated_update_human_readable(ctx, module, req, reply);
+ assert_non_null(line);
+
+ /*
+ * We ignore the timestamp to make this test a little easier
+ * to write.
+ */
+ rs = "at \\[[^[]*\\] "
+ "status \\[No such object\\] "
+ "error \\[The request is not supported.\\] "
+ "partition \\[dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG\\] "
+ "objects \\[808\\] links \\[2910\\] "
+ "object \\[7130cb06-2062-6a1b-409e-3514c26b1793\\] "
+ "invocation \\[7130cb06-2062-6a1b-409e-3514c26b1893\\]";
+
+ ret = regcomp(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ TALLOC_FREE(ctx);
+}
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_has_password_changed),
+ cmocka_unit_test(test_get_password_action),
+ cmocka_unit_test(test_operation_json_empty),
+ cmocka_unit_test(test_operation_json),
+ cmocka_unit_test(test_as_system_operation_json),
+ cmocka_unit_test(test_password_change_json_empty),
+ cmocka_unit_test(test_password_change_json),
+ cmocka_unit_test(test_transaction_json),
+ cmocka_unit_test(test_commit_failure_json),
+ cmocka_unit_test(test_replicated_update_json_empty),
+ cmocka_unit_test(test_replicated_update_json),
+ cmocka_unit_test(test_add_transaction_id),
+ cmocka_unit_test(test_operation_hr_empty),
+ cmocka_unit_test(test_operation_hr),
+ cmocka_unit_test(test_as_system_operation_hr),
+ cmocka_unit_test(test_password_change_hr_empty),
+ cmocka_unit_test(test_password_change_hr),
+ cmocka_unit_test(test_transaction_hr),
+ cmocka_unit_test(test_commit_failure_hr),
+ cmocka_unit_test(test_log_attributes),
+ cmocka_unit_test(test_replicated_update_hr_empty),
+ cmocka_unit_test(test_replicated_update_hr),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_audit_log_errors.c b/source4/dsdb/samdb/ldb_modules/tests/test_audit_log_errors.c
new file mode 100644
index 0000000..2931768
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/test_audit_log_errors.c
@@ -0,0 +1,639 @@
+/*
+ Unit tests for the dsdb audit logging code code in audit_log.c
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+
+ 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/>.
+*/
+
+/*
+ * These tests exercise the error handling code
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <cmocka.h>
+
+int ldb_audit_log_module_init(const char *version);
+#include "../audit_log.c"
+#include "lib/ldb/include/ldb_private.h"
+
+/*
+ * cmocka wrappers for json_new_object
+ */
+struct json_object __wrap_json_new_object(void);
+struct json_object __real_json_new_object(void);
+struct json_object __wrap_json_new_object(void)
+{
+
+ bool use_real = (bool)mock();
+ if (!use_real) {
+ return json_empty_object;
+ }
+ return __real_json_new_object();
+}
+
+/*
+ * cmocka wrappers for json_add_version
+ */
+int __wrap_json_add_version(struct json_object *object, int major, int minor);
+int __real_json_add_version(struct json_object *object, int major, int minor);
+int __wrap_json_add_version(struct json_object *object, int major, int minor)
+{
+
+ int ret = (int)mock();
+ if (ret) {
+ return ret;
+ }
+ return __real_json_add_version(object, major, minor);
+}
+
+/*
+ * cmocka wrappers for json_add_version
+ */
+int __wrap_json_add_timestamp(struct json_object *object);
+int __real_json_add_timestamp(struct json_object *object);
+int __wrap_json_add_timestamp(struct json_object *object)
+{
+
+ int ret = (int)mock();
+ if (ret) {
+ return ret;
+ }
+ return __real_json_add_timestamp(object);
+}
+/*
+ * unit test of operation_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_operation_json(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+
+ struct tsocket_address *ts = NULL;
+
+ struct auth_session_info *sess = NULL;
+ struct security_token *token = NULL;
+ struct dom_sid sid;
+ const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ struct GUID session_id;
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct ldb_dn *dn = NULL;
+ const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+
+ struct ldb_message *msg = NULL;
+
+ struct json_object json;
+
+
+ /*
+ * Test setup
+ */
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ audit_private = talloc_zero(ctx, struct audit_private);
+ GUID_from_string(TRANSACTION, &transaction_id);
+ audit_private->transaction_guid = transaction_id;
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(ldb, "remoteAddress", ts);
+
+ sess = talloc_zero(ctx, struct auth_session_info);
+ token = talloc_zero(ctx, struct security_token);
+ string_to_sid(&sid, SID);
+ token->num_sids = 1;
+ token->sids = &sid;
+ sess->security_token = token;
+ GUID_from_string(SESSION, &session_id);
+ sess->unique_session_token = session_id;
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
+
+ msg = talloc_zero(ctx, struct ldb_message);
+ dn = ldb_dn_new(ctx, ldb, DN);
+ msg->dn = dn;
+ ldb_msg_add_string(msg, "attribute", "the-value");
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_ERR_OPERATIONS_ERROR;
+
+ /*
+ * Fail on the creation of the audit json object
+ */
+
+ will_return(__wrap_json_new_object, false);
+
+ json = operation_json(module, req, reply);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail adding the version object .
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, JSON_ERROR);
+
+ json = operation_json(module, req, reply);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail on creation of the wrapper.
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_new_object, false);
+
+ json = operation_json(module, req, reply);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail adding the timestamp to the wrapper object.
+ */
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_timestamp, JSON_ERROR);
+
+ json = operation_json(module, req, reply);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Now test the happy path
+ */
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_timestamp, 0);
+
+ json = operation_json(module, req, reply);
+ assert_false(json_is_invalid(&json));
+ json_free(&json);
+
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * minimal unit test of password_change_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_password_change_json(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+
+ struct tsocket_address *ts = NULL;
+
+ struct auth_session_info *sess = NULL;
+ struct security_token *token = NULL;
+ struct dom_sid sid;
+ const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ struct GUID session_id;
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct ldb_dn *dn = NULL;
+ const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+
+ struct ldb_message *msg = NULL;
+
+ struct json_object json;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ audit_private = talloc_zero(ctx, struct audit_private);
+ GUID_from_string(TRANSACTION, &transaction_id);
+ audit_private->transaction_guid = transaction_id;
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(ldb, "remoteAddress", ts);
+
+ sess = talloc_zero(ctx, struct auth_session_info);
+ token = talloc_zero(ctx, struct security_token);
+ string_to_sid(&sid, SID);
+ token->num_sids = 1;
+ token->sids = &sid;
+ sess->security_token = token;
+ GUID_from_string(SESSION, &session_id);
+ sess->unique_session_token = session_id;
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
+
+ msg = talloc_zero(ctx, struct ldb_message);
+ dn = ldb_dn_new(ctx, ldb, DN);
+ msg->dn = dn;
+ ldb_msg_add_string(msg, "planTextPassword", "super-secret");
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_SUCCESS;
+
+
+ /*
+ * Fail on the creation of the audit json object
+ */
+
+ will_return(__wrap_json_new_object, false);
+ json = password_change_json(module, req, reply);
+
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail adding the version object .
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, JSON_ERROR);
+
+ json = password_change_json(module, req, reply);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail on creation of the wrapper.
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, false);
+
+ json = password_change_json(module, req, reply);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail on creation of the time stamp.
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_timestamp, JSON_ERROR);
+
+ json = password_change_json(module, req, reply);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Now test the happy path
+ */
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_timestamp, 0);
+
+ json = password_change_json(module, req, reply);
+ assert_false(json_is_invalid(&json));
+ json_free(&json);
+
+ TALLOC_FREE(ctx);
+}
+
+
+/*
+ * minimal unit test of transaction_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_transaction_json(void **state)
+{
+
+ struct GUID guid;
+ const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct json_object json;
+
+ GUID_from_string(GUID, &guid);
+
+
+ /*
+ * Fail on the creation of the audit json object
+ */
+
+ will_return(__wrap_json_new_object, false);
+
+ json = transaction_json("delete", &guid, 10000099);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail adding the version object .
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, JSON_ERROR);
+
+ json = transaction_json("delete", &guid, 10000099);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail on creation of the wrapper.
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, false);
+
+ json = transaction_json("delete", &guid, 10000099);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail on creation of the time stamp.
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_timestamp, JSON_ERROR);
+
+ json = transaction_json("delete", &guid, 10000099);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Now test the happy path
+ */
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_timestamp, 0);
+
+ json = transaction_json("delete", &guid, 10000099);
+ assert_false(json_is_invalid(&json));
+ json_free(&json);
+}
+
+/*
+ * minimal unit test of commit_failure_json, that ensures that all the
+ * expected attributes and objects are in the json object.
+ */
+static void test_commit_failure_json(void **state)
+{
+
+ struct GUID guid;
+ const char * const GUID = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct json_object json;
+
+ GUID_from_string(GUID, &guid);
+
+
+ /*
+ * Fail on the creation of the audit json object
+ */
+
+ will_return(__wrap_json_new_object, false);
+
+ json = commit_failure_json(
+ "prepare",
+ 987876,
+ LDB_ERR_OPERATIONS_ERROR,
+ "because",
+ &guid);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail adding the version object .
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, JSON_ERROR);
+
+ json = commit_failure_json(
+ "prepare",
+ 987876,
+ LDB_ERR_OPERATIONS_ERROR,
+ "because",
+ &guid);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail on creation of the wrapper.
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, false);
+
+ json = commit_failure_json(
+ "prepare",
+ 987876,
+ LDB_ERR_OPERATIONS_ERROR,
+ "because",
+ &guid);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail on creation of the time stamp.
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_timestamp, JSON_ERROR);
+
+ json = commit_failure_json(
+ "prepare",
+ 987876,
+ LDB_ERR_OPERATIONS_ERROR,
+ "because",
+ &guid);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Now test the happy path
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_timestamp, 0);
+
+ json = commit_failure_json(
+ "prepare",
+ 987876,
+ LDB_ERR_OPERATIONS_ERROR,
+ "because",
+ &guid);
+ assert_false(json_is_invalid(&json));
+ json_free(&json);
+}
+
+/*
+ * unit test of replicated_update_json, that ensures that all the expected
+ * attributes and objects are in the json object.
+ */
+static void test_replicated_update_json(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+ struct ldb_reply *reply = NULL;
+ struct audit_private *audit_private = NULL;
+ struct dsdb_extended_replicated_objects *ro = NULL;
+ struct repsFromTo1 *source_dsa = NULL;
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct ldb_dn *dn = NULL;
+ const char *const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+
+ struct GUID source_dsa_obj_guid;
+ const char *const SOURCE_DSA = "7130cb06-2062-6a1b-409e-3514c26b1793";
+
+ struct GUID invocation_id;
+ const char *const INVOCATION_ID =
+ "7130cb06-2062-6a1b-409e-3514c26b1893";
+ struct json_object json;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ audit_private = talloc_zero(ctx, struct audit_private);
+ GUID_from_string(TRANSACTION, &transaction_id);
+ audit_private->transaction_guid = transaction_id;
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+ ldb_module_set_private(module, audit_private);
+
+ dn = ldb_dn_new(ctx, ldb, DN);
+ GUID_from_string(SOURCE_DSA, &source_dsa_obj_guid);
+ GUID_from_string(INVOCATION_ID, &invocation_id);
+ source_dsa = talloc_zero(ctx, struct repsFromTo1);
+ source_dsa->source_dsa_obj_guid = source_dsa_obj_guid;
+ source_dsa->source_dsa_invocation_id = invocation_id;
+
+ ro = talloc_zero(ctx, struct dsdb_extended_replicated_objects);
+ ro->source_dsa = source_dsa;
+ ro->num_objects = 808;
+ ro->linked_attributes_count = 2910;
+ ro->partition_dn = dn;
+ ro->error = WERR_NOT_SUPPORTED;
+
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->op.extended.data = ro;
+ req->operation = LDB_EXTENDED;
+
+ reply = talloc_zero(ctx, struct ldb_reply);
+ reply->error = LDB_ERR_NO_SUCH_OBJECT;
+
+
+ /*
+ * Fail on the creation of the audit json object
+ */
+
+ will_return(__wrap_json_new_object, false);
+
+ json = replicated_update_json(module, req, reply);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail adding the version object .
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, JSON_ERROR);
+
+ json = replicated_update_json(module, req, reply);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail on creation of the wrapper.
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, false);
+
+ json = replicated_update_json(module, req, reply);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail on creation of the time stamp.
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_timestamp, JSON_ERROR);
+
+ json = replicated_update_json(module, req, reply);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Now test the happy path.
+ */
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_timestamp, 0);
+
+ json = replicated_update_json(module, req, reply);
+ assert_false(json_is_invalid(&json));
+ json_free(&json);
+
+ TALLOC_FREE(ctx);
+}
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_operation_json),
+ cmocka_unit_test(test_password_change_json),
+ cmocka_unit_test(test_transaction_json),
+ cmocka_unit_test(test_commit_failure_json),
+ cmocka_unit_test(test_replicated_update_json),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c b/source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c
new file mode 100644
index 0000000..cf2546d
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/test_audit_util.c
@@ -0,0 +1,1260 @@
+/*
+ Unit tests for the dsdb audit logging utility code code in audit_util.c
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+
+ 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 <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <cmocka.h>
+
+#include "../audit_util.c"
+
+#include "lib/ldb/include/ldb_private.h"
+
+static void test_dsdb_audit_add_ldb_value(void **state)
+{
+ struct json_object object;
+ struct json_object array;
+ struct ldb_val val = data_blob_null;
+ struct json_t *el = NULL;
+ struct json_t *atr = NULL;
+ char* base64 = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ /*
+ * Test a non array object
+ */
+ object = json_new_object();
+ assert_false(json_is_invalid(&object));
+ dsdb_audit_add_ldb_value(&object, val);
+ assert_true(json_is_invalid(&object));
+ json_free(&object);
+
+ array = json_new_array();
+ /*
+ * Test a data_blob_null, should encode as a JSON null value.
+ */
+ val = data_blob_null;
+ dsdb_audit_add_ldb_value(&array, val);
+ el = json_array_get(array.root, 0);
+ assert_true(json_is_null(el));
+
+ /*
+ * Test a +ve length but a null data ptr, should encode as a null.
+ */
+ val = data_blob_null;
+ val.length = 1;
+ dsdb_audit_add_ldb_value(&array, val);
+ el = json_array_get(array.root, 1);
+ assert_true(json_is_null(el));
+
+ /*
+ * Test a zero length but a non null data ptr, should encode as a null.
+ */
+ val = data_blob_null;
+ val.data = discard_const("Data on the stack");
+ dsdb_audit_add_ldb_value(&array, val);
+ el = json_array_get(array.root, 2);
+ assert_true(json_is_null(el));
+
+ /*
+ * Test a printable value.
+ * value should not be encoded
+ * truncated and base64 should be missing
+ */
+ val = data_blob_string_const("A value of interest");
+ dsdb_audit_add_ldb_value(&array, val);
+ el = json_array_get(array.root, 3);
+ assert_true(json_is_object(el));
+ atr = json_object_get(el, "value");
+ assert_true(json_is_string(atr));
+ assert_string_equal("A value of interest", json_string_value(atr));
+ assert_null(json_object_get(el, "truncated"));
+ assert_null(json_object_get(el, "base64"));
+
+ /*
+ * Test non printable value, should be base64 encoded.
+ * truncated should be missing and base64 should be set.
+ */
+ val = data_blob_string_const("A value of interest\n");
+ dsdb_audit_add_ldb_value(&array, val);
+ el = json_array_get(array.root, 4);
+ assert_true(json_is_object(el));
+ atr = json_object_get(el, "value");
+ assert_true(json_is_string(atr));
+ assert_string_equal(
+ "QSB2YWx1ZSBvZiBpbnRlcmVzdAo=",
+ json_string_value(atr));
+ atr = json_object_get(el, "base64");
+ assert_true(json_is_boolean(atr));
+ assert_true(json_boolean(atr));
+ assert_null(json_object_get(el, "truncated"));
+
+ /*
+ * test a printable value exactly max bytes long
+ * should not be truncated or encoded.
+ */
+ val = data_blob_null;
+ val.length = MAX_LENGTH;
+ val.data = (unsigned char *)generate_random_str_list(
+ ctx,
+ MAX_LENGTH,
+ "abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "1234567890!@#$%^&*()");
+
+ dsdb_audit_add_ldb_value(&array, val);
+
+ el = json_array_get(array.root, 5);
+ assert_true(json_is_object(el));
+ atr = json_object_get(el, "value");
+ assert_true(json_is_string(atr));
+ assert_int_equal(MAX_LENGTH, strlen(json_string_value(atr)));
+ assert_memory_equal(val.data, json_string_value(atr), MAX_LENGTH);
+
+ assert_null(json_object_get(el, "base64"));
+ assert_null(json_object_get(el, "truncated"));
+
+
+ /*
+ * test a printable value exactly max + 1 bytes long
+ * should be truncated and not encoded.
+ */
+ val = data_blob_null;
+ val.length = MAX_LENGTH + 1;
+ val.data = (unsigned char *)generate_random_str_list(
+ ctx,
+ MAX_LENGTH + 1,
+ "abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "1234567890!@#$%^&*()");
+
+ dsdb_audit_add_ldb_value(&array, val);
+
+ el = json_array_get(array.root, 6);
+ assert_true(json_is_object(el));
+ atr = json_object_get(el, "value");
+ assert_true(json_is_string(atr));
+ assert_int_equal(MAX_LENGTH, strlen(json_string_value(atr)));
+ assert_memory_equal(val.data, json_string_value(atr), MAX_LENGTH);
+
+ atr = json_object_get(el, "truncated");
+ assert_true(json_is_boolean(atr));
+ assert_true(json_boolean(atr));
+
+ assert_null(json_object_get(el, "base64"));
+
+ TALLOC_FREE(val.data);
+
+ /*
+ * test a non-printable value exactly max bytes long
+ * should not be truncated but should be encoded.
+ */
+ val = data_blob_null;
+ val.length = MAX_LENGTH;
+ val.data = (unsigned char *)generate_random_str_list(
+ ctx,
+ MAX_LENGTH,
+ "abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "1234567890!@#$%^&*()");
+
+ val.data[0] = 0x03;
+ dsdb_audit_add_ldb_value(&array, val);
+ base64 = ldb_base64_encode(ctx, (char*) val.data, MAX_LENGTH);
+
+ el = json_array_get(array.root, 7);
+ assert_true(json_is_object(el));
+ atr = json_object_get(el, "value");
+ assert_true(json_is_string(atr));
+ assert_int_equal(strlen(base64), strlen(json_string_value(atr)));
+ assert_string_equal(base64, json_string_value(atr));
+
+ atr = json_object_get(el, "base64");
+ assert_true(json_is_boolean(atr));
+ assert_true(json_boolean(atr));
+
+ assert_null(json_object_get(el, "truncated"));
+ TALLOC_FREE(base64);
+ TALLOC_FREE(val.data);
+
+ /*
+ * test a non-printable value exactly max + 1 bytes long
+ * should be truncated and encoded.
+ */
+ val = data_blob_null;
+ val.length = MAX_LENGTH + 1;
+ val.data = (unsigned char *)generate_random_str_list(
+ ctx,
+ MAX_LENGTH + 1,
+ "abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "1234567890!@#$%^&*()");
+
+ val.data[0] = 0x03;
+ dsdb_audit_add_ldb_value(&array, val);
+ /*
+ * The data is truncated before it is base 64 encoded
+ */
+ base64 = ldb_base64_encode(ctx, (char*) val.data, MAX_LENGTH);
+
+ el = json_array_get(array.root, 8);
+ assert_true(json_is_object(el));
+ atr = json_object_get(el, "value");
+ assert_true(json_is_string(atr));
+ assert_int_equal(strlen(base64), strlen(json_string_value(atr)));
+ assert_string_equal(base64, json_string_value(atr));
+
+ atr = json_object_get(el, "base64");
+ assert_true(json_is_boolean(atr));
+ assert_true(json_boolean(atr));
+
+ atr = json_object_get(el, "truncated");
+ assert_true(json_is_boolean(atr));
+ assert_true(json_boolean(atr));
+
+ TALLOC_FREE(base64);
+ TALLOC_FREE(val.data);
+
+ json_free(&array);
+ TALLOC_FREE(ctx);
+}
+
+static void test_dsdb_audit_attributes_json(void **state)
+{
+ struct ldb_message *msg = NULL;
+
+ struct json_object o;
+ json_t *a = NULL;
+ json_t *v = NULL;
+ json_t *x = NULL;
+ json_t *y = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+
+ /*
+ * Test an empty message
+ * Should get an empty attributes object
+ */
+ msg = talloc_zero(ctx, struct ldb_message);
+
+ o = dsdb_audit_attributes_json(LDB_ADD, msg);
+ assert_true(json_is_object(o.root));
+ assert_int_equal(0, json_object_size(o.root));
+ json_free(&o);
+
+ o = dsdb_audit_attributes_json(LDB_MODIFY, msg);
+ assert_true(json_is_object(o.root));
+ assert_int_equal(0, json_object_size(o.root));
+ json_free(&o);
+
+ /*
+ * Test a message with a single secret attribute
+ * should only have that object and it should have no value
+ * attribute and redacted should be set.
+ */
+ msg = talloc_zero(ctx, struct ldb_message);
+ ldb_msg_add_string(msg, "clearTextPassword", "secret");
+
+ o = dsdb_audit_attributes_json(LDB_ADD, msg);
+ assert_true(json_is_object(o.root));
+ assert_int_equal(1, json_object_size(o.root));
+
+ a = json_object_get(o.root, "clearTextPassword");
+ assert_int_equal(1, json_object_size(a));
+
+ v = json_object_get(a, "actions");
+ assert_true(json_is_array(v));
+ assert_int_equal(1, json_array_size(v));
+
+ a = json_array_get(v, 0);
+ v = json_object_get(a, "redacted");
+ assert_true(json_is_boolean(v));
+ assert_true(json_boolean(v));
+
+ json_free(&o);
+
+ /*
+ * Test as a modify message, should add an action attribute
+ */
+ o = dsdb_audit_attributes_json(LDB_MODIFY, msg);
+ assert_true(json_is_object(o.root));
+ assert_int_equal(1, json_object_size(o.root));
+
+ a = json_object_get(o.root, "clearTextPassword");
+ assert_true(json_is_object(a));
+ assert_int_equal(1, json_object_size(a));
+
+ v = json_object_get(a, "actions");
+ assert_true(json_is_array(v));
+ assert_int_equal(1, json_array_size(v));
+
+ a = json_array_get(v, 0);
+ v = json_object_get(a, "redacted");
+ assert_true(json_is_boolean(v));
+ assert_true(json_boolean(v));
+
+ v = json_object_get(a, "action");
+ assert_true(json_is_string(v));
+ assert_string_equal("unknown", json_string_value(v));
+
+ json_free(&o);
+ TALLOC_FREE(msg);
+
+ /*
+ * Test a message with a single attribute, single valued attribute
+ */
+ msg = talloc_zero(ctx, struct ldb_message);
+ ldb_msg_add_string(msg, "attribute", "value");
+
+ o = dsdb_audit_attributes_json(LDB_ADD, msg);
+ assert_true(json_is_object(o.root));
+ assert_int_equal(1, json_object_size(o.root));
+
+ a = json_object_get(o.root, "attribute");
+ assert_true(json_is_object(a));
+ assert_int_equal(1, json_object_size(a));
+
+ v = json_object_get(a, "actions");
+ assert_true(json_is_array(v));
+ assert_int_equal(1, json_array_size(v));
+
+ x = json_array_get(v, 0);
+ assert_int_equal(2, json_object_size(x));
+ y = json_object_get(x, "action");
+ assert_string_equal("add", json_string_value(y));
+
+ y = json_object_get(x, "values");
+ assert_true(json_is_array(y));
+ assert_int_equal(1, json_array_size(y));
+
+ x = json_array_get(y, 0);
+ assert_true(json_is_object(x));
+ assert_int_equal(1, json_object_size(x));
+ y = json_object_get(x, "value");
+ assert_string_equal("value", json_string_value(y));
+
+ json_free(&o);
+ TALLOC_FREE(msg);
+
+ /*
+ * Test a message with a single attribute, single valued attribute
+ * And as a modify
+ */
+ msg = talloc_zero(ctx, struct ldb_message);
+ ldb_msg_add_string(msg, "attribute", "value");
+
+ o = dsdb_audit_attributes_json(LDB_MODIFY, msg);
+ assert_true(json_is_object(o.root));
+ assert_int_equal(1, json_object_size(o.root));
+
+ a = json_object_get(o.root, "attribute");
+ assert_true(json_is_object(a));
+ assert_int_equal(1, json_object_size(a));
+
+ v = json_object_get(a, "actions");
+ assert_true(json_is_array(v));
+ assert_int_equal(1, json_array_size(v));
+
+ x = json_array_get(v, 0);
+ assert_int_equal(2, json_object_size(x));
+ y = json_object_get(x, "action");
+ assert_string_equal("unknown", json_string_value(y));
+
+ y = json_object_get(x, "values");
+ assert_true(json_is_array(y));
+ assert_int_equal(1, json_array_size(y));
+
+ x = json_array_get(y, 0);
+ assert_true(json_is_object(x));
+ assert_int_equal(1, json_object_size(x));
+ y = json_object_get(x, "value");
+ assert_string_equal("value", json_string_value(y));
+
+ json_free(&o);
+ TALLOC_FREE(msg);
+
+ /*
+ * Test a message with a multivalues attributres
+ */
+ msg = talloc_zero(ctx, struct ldb_message);
+ ldb_msg_add_string(msg, "attribute01", "value01");
+ ldb_msg_add_string(msg, "attribute02", "value02");
+ ldb_msg_add_string(msg, "attribute02", "value03");
+
+ o = dsdb_audit_attributes_json(LDB_ADD, msg);
+ assert_true(json_is_object(o.root));
+ assert_int_equal(2, json_object_size(o.root));
+
+ a = json_object_get(o.root, "attribute01");
+ assert_true(json_is_object(a));
+ assert_int_equal(1, json_object_size(a));
+
+ v = json_object_get(a, "actions");
+ assert_true(json_is_array(v));
+ assert_int_equal(1, json_array_size(v));
+
+ x = json_array_get(v, 0);
+ assert_int_equal(2, json_object_size(x));
+ y = json_object_get(x, "action");
+ assert_string_equal("add", json_string_value(y));
+
+ y = json_object_get(x, "values");
+ assert_true(json_is_array(y));
+ assert_int_equal(1, json_array_size(y));
+
+ x = json_array_get(y, 0);
+ assert_true(json_is_object(x));
+ assert_int_equal(1, json_object_size(x));
+ y = json_object_get(x, "value");
+ assert_string_equal("value01", json_string_value(y));
+
+ a = json_object_get(o.root, "attribute02");
+ assert_true(json_is_object(a));
+ assert_int_equal(1, json_object_size(a));
+
+ v = json_object_get(a, "actions");
+ assert_true(json_is_array(v));
+ assert_int_equal(1, json_array_size(v));
+
+ x = json_array_get(v, 0);
+ assert_int_equal(2, json_object_size(x));
+ y = json_object_get(x, "action");
+ assert_string_equal("add", json_string_value(y));
+
+ y = json_object_get(x, "values");
+ assert_true(json_is_array(y));
+ assert_int_equal(2, json_array_size(y));
+
+ x = json_array_get(y, 0);
+ assert_true(json_is_object(x));
+ assert_int_equal(1, json_object_size(x));
+ v = json_object_get(x, "value");
+ assert_string_equal("value02", json_string_value(v));
+
+ x = json_array_get(y, 1);
+ assert_true(json_is_object(x));
+ assert_int_equal(1, json_object_size(x));
+ v = json_object_get(x, "value");
+ assert_string_equal("value03", json_string_value(v));
+
+ json_free(&o);
+ TALLOC_FREE(msg);
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_dsdb_audit_get_remote_address(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ const struct tsocket_address *ts = NULL;
+ struct tsocket_address *in = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ /*
+ * Test a freshly initialized ldb
+ * should return NULL
+ */
+ ldb = ldb_init(ctx, NULL);
+ ts = dsdb_audit_get_remote_address(ldb);
+ assert_null(ts);
+
+ /*
+ * opaque set to null, should return NULL
+ */
+ ldb_set_opaque(ldb, "remoteAddress", NULL);
+ ts = dsdb_audit_get_remote_address(ldb);
+ assert_null(ts);
+
+ /*
+ * Ensure that the value set is returned
+ */
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &in);
+ ldb_set_opaque(ldb, "remoteAddress", in);
+ ts = dsdb_audit_get_remote_address(ldb);
+ assert_non_null(ts);
+ assert_ptr_equal(in, ts);
+
+ TALLOC_FREE(ldb);
+ TALLOC_FREE(ctx);
+
+}
+
+static void test_dsdb_audit_get_ldb_error_string(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ const char *s = NULL;
+ const char * const text = "Custom reason";
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+
+ /*
+ * No ldb error string set should get the default error description for
+ * the status code
+ */
+ s = dsdb_audit_get_ldb_error_string(module, LDB_ERR_OPERATIONS_ERROR);
+ assert_string_equal("Operations error", s);
+
+ /*
+ * Set the error string that should now be returned instead of the
+ * default description.
+ */
+ ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, text);
+ s = dsdb_audit_get_ldb_error_string(module, LDB_ERR_OPERATIONS_ERROR);
+ /*
+ * Only test the start of the string as ldb_error adds location data.
+ */
+ assert_int_equal(0, strncmp(text, s, strlen(text)));
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_dsdb_audit_get_user_sid(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ const struct dom_sid *sid = NULL;
+ struct auth_session_info *sess = NULL;
+ struct security_token *token = NULL;
+ struct dom_sid sids[2];
+ const char * const SID0 = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SID1 = "S-1-5-21-4284042908-2889457889-3672286761";
+ struct dom_sid_buf sid_buf;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+
+ /*
+ * Freshly initialised structures, will be no session data
+ * so expect NULL
+ */
+ sid = dsdb_audit_get_user_sid(module);
+ assert_null(sid);
+
+ /*
+ * Now add a NULL session info
+ */
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
+ sid = dsdb_audit_get_user_sid(module);
+ assert_null(sid);
+
+ /*
+ * Now add a session info with no user sid
+ */
+ sess = talloc_zero(ctx, struct auth_session_info);
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
+ sid = dsdb_audit_get_user_sid(module);
+ assert_null(sid);
+
+ /*
+ * Now add an empty security token.
+ */
+ token = talloc_zero(ctx, struct security_token);
+ sess->security_token = token;
+ sid = dsdb_audit_get_user_sid(module);
+ assert_null(sid);
+
+ /*
+ * Add a single SID
+ */
+ string_to_sid(&sids[0], SID0);
+ token->num_sids = 1;
+ token->sids = sids;
+ sid = dsdb_audit_get_user_sid(module);
+ assert_non_null(sid);
+ dom_sid_str_buf(sid, &sid_buf);
+ assert_string_equal(SID0, sid_buf.buf);
+
+ /*
+ * Add a second SID, should still use the first SID
+ */
+ string_to_sid(&sids[1], SID1);
+ token->num_sids = 2;
+ sid = dsdb_audit_get_user_sid(module);
+ assert_non_null(sid);
+ dom_sid_str_buf(sid, &sid_buf);
+ assert_string_equal(SID0, sid_buf.buf);
+
+
+ /*
+ * Now test a null sid in the first position
+ */
+ token->num_sids = 1;
+ token->sids = NULL;
+ sid = dsdb_audit_get_user_sid(module);
+ assert_null(sid);
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_dsdb_audit_get_actual_sid(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ const struct dom_sid *sid = NULL;
+ struct auth_session_info *sess = NULL;
+ struct security_token *token = NULL;
+ struct dom_sid sids[2];
+ const char * const SID0 = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SID1 = "S-1-5-21-4284042908-2889457889-3672286761";
+ struct dom_sid_buf sid_buf;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ /*
+ * Freshly initialised structures, will be no session data
+ * so expect NULL
+ */
+ sid = dsdb_audit_get_actual_sid(ldb);
+ assert_null(sid);
+
+ /*
+ * Now add a NULL session info
+ */
+ ldb_set_opaque(ldb, DSDB_NETWORK_SESSION_INFO, NULL);
+ sid = dsdb_audit_get_actual_sid(ldb);
+ assert_null(sid);
+
+ /*
+ * Now add a session info with no user sid
+ */
+ sess = talloc_zero(ctx, struct auth_session_info);
+ ldb_set_opaque(ldb, DSDB_NETWORK_SESSION_INFO, sess);
+ sid = dsdb_audit_get_actual_sid(ldb);
+ assert_null(sid);
+
+ /*
+ * Now add an empty security token.
+ */
+ token = talloc_zero(ctx, struct security_token);
+ sess->security_token = token;
+ sid = dsdb_audit_get_actual_sid(ldb);
+ assert_null(sid);
+
+ /*
+ * Add a single SID
+ */
+ string_to_sid(&sids[0], SID0);
+ token->num_sids = 1;
+ token->sids = sids;
+ sid = dsdb_audit_get_actual_sid(ldb);
+ assert_non_null(sid);
+ dom_sid_str_buf(sid, &sid_buf);
+ assert_string_equal(SID0, sid_buf.buf);
+
+ /*
+ * Add a second SID, should still use the first SID
+ */
+ string_to_sid(&sids[1], SID1);
+ token->num_sids = 2;
+ sid = dsdb_audit_get_actual_sid(ldb);
+ assert_non_null(sid);
+ dom_sid_str_buf(sid, &sid_buf);
+ assert_string_equal(SID0, sid_buf.buf);
+
+
+ /*
+ * Now test a null sid in the first position
+ */
+ token->num_sids = 1;
+ token->sids = NULL;
+ sid = dsdb_audit_get_actual_sid(ldb);
+ assert_null(sid);
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_dsdb_audit_is_system_session(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ const struct dom_sid *sid = NULL;
+ struct auth_session_info *sess = NULL;
+ struct security_token *token = NULL;
+ struct dom_sid sids[2];
+ const char * const SID0 = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SID1 = "S-1-5-21-4284042908-2889457889-3672286761";
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+
+ /*
+ * Freshly initialised structures, will be no session data
+ * so expect NULL
+ */
+ assert_false(dsdb_audit_is_system_session(module));
+
+ /*
+ * Now add a NULL session info
+ */
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, NULL);
+ assert_false(dsdb_audit_is_system_session(module));
+
+ /*
+ * Now add a session info with no user sid
+ */
+ sess = talloc_zero(ctx, struct auth_session_info);
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
+ assert_false(dsdb_audit_is_system_session(module));
+
+ /*
+ * Now add an empty security token.
+ */
+ token = talloc_zero(ctx, struct security_token);
+ sess->security_token = token;
+ assert_false(dsdb_audit_is_system_session(module));
+
+ /*
+ * Add a single SID, non system sid
+ */
+ string_to_sid(&sids[0], SID0);
+ token->num_sids = 1;
+ token->sids = sids;
+ assert_false(dsdb_audit_is_system_session(module));
+
+ /*
+ * Add the system SID to the second position,
+ * this should be ignored.
+ */
+ token->num_sids = 2;
+ sids[1] = global_sid_System;
+ assert_false(dsdb_audit_is_system_session(module));
+
+ /*
+ * Add a single SID, system sid
+ */
+ token->num_sids = 1;
+ sids[0] = global_sid_System;
+ token->sids = sids;
+ assert_true(dsdb_audit_is_system_session(module));
+
+ /*
+ * Add a non system SID to position 2
+ */
+ sids[0] = global_sid_System;
+ string_to_sid(&sids[1], SID1);
+ token->num_sids = 2;
+ token->sids = sids;
+ assert_true(dsdb_audit_is_system_session(module));
+
+ /*
+ * Now test a null sid in the first position
+ */
+ token->num_sids = 1;
+ token->sids = NULL;
+ sid = dsdb_audit_get_user_sid(module);
+ assert_null(sid);
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_dsdb_audit_get_unique_session_token(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct auth_session_info *sess = NULL;
+ const struct GUID *guid;
+ const char * const GUID_S = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ struct GUID in;
+ char *guid_str;
+ struct GUID_txt_buf guid_buff;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+
+ /*
+ * Test a freshly initialized ldb
+ * should return NULL
+ */
+ guid = dsdb_audit_get_unique_session_token(module);
+ assert_null(guid);
+
+ /*
+ * Now add a NULL session info
+ */
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, NULL);
+ guid = dsdb_audit_get_unique_session_token(module);
+ assert_null(guid);
+
+ /*
+ * Now add a session info with no session id
+ * Note if the memory has not been zeroed correctly all bets are
+ * probably off.
+ */
+ sess = talloc_zero(ctx, struct auth_session_info);
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
+ guid = dsdb_audit_get_unique_session_token(module);
+ /*
+ * We will get a GUID, but it's contents will be undefined
+ */
+ assert_non_null(guid);
+
+ /*
+ * Now set the session id and confirm that we get it back.
+ */
+ GUID_from_string(GUID_S, &in);
+ sess->unique_session_token = in;
+ guid = dsdb_audit_get_unique_session_token(module);
+ assert_non_null(guid);
+ guid_str = GUID_buf_string(guid, &guid_buff);
+ assert_string_equal(GUID_S, guid_str);
+
+ TALLOC_FREE(ctx);
+
+}
+
+static void test_dsdb_audit_get_actual_unique_session_token(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct auth_session_info *sess = NULL;
+ const struct GUID *guid;
+ const char * const GUID_S = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ struct GUID in;
+ char *guid_str;
+ struct GUID_txt_buf guid_buff;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ /*
+ * Test a freshly initialized ldb
+ * should return NULL
+ */
+ guid = dsdb_audit_get_actual_unique_session_token(ldb);
+ assert_null(guid);
+
+ /*
+ * Now add a NULL session info
+ */
+ ldb_set_opaque(ldb, DSDB_NETWORK_SESSION_INFO, NULL);
+ guid = dsdb_audit_get_actual_unique_session_token(ldb);
+ assert_null(guid);
+
+ /*
+ * Now add a session info with no session id
+ * Note if the memory has not been zeroed correctly all bets are
+ * probably off.
+ */
+ sess = talloc_zero(ctx, struct auth_session_info);
+ ldb_set_opaque(ldb, DSDB_NETWORK_SESSION_INFO, sess);
+ guid = dsdb_audit_get_actual_unique_session_token(ldb);
+ /*
+ * We will get a GUID, but it's contents will be undefined
+ */
+ assert_non_null(guid);
+
+ /*
+ * Now set the session id and confirm that we get it back.
+ */
+ GUID_from_string(GUID_S, &in);
+ sess->unique_session_token = in;
+ guid = dsdb_audit_get_actual_unique_session_token(ldb);
+ assert_non_null(guid);
+ guid_str = GUID_buf_string(guid, &guid_buff);
+ assert_string_equal(GUID_S, guid_str);
+
+ TALLOC_FREE(ctx);
+
+}
+
+static void test_dsdb_audit_get_remote_host(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ char *rh = NULL;
+ struct tsocket_address *in = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ /*
+ * Test a freshly initialized ldb
+ * should return "Unknown"
+ */
+ rh = dsdb_audit_get_remote_host(ldb, ctx);
+ assert_string_equal("Unknown", rh);
+ TALLOC_FREE(rh);
+
+ /*
+ * opaque set to null, should return NULL
+ */
+ ldb_set_opaque(ldb, "remoteAddress", NULL);
+ rh = dsdb_audit_get_remote_host(ldb, ctx);
+ assert_string_equal("Unknown", rh);
+ TALLOC_FREE(rh);
+
+ /*
+ * Ensure that the value set is returned
+ */
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 42, &in);
+ ldb_set_opaque(ldb, "remoteAddress", in);
+ rh = dsdb_audit_get_remote_host(ldb, ctx);
+ assert_string_equal("ipv4:127.0.0.1:42", rh);
+ TALLOC_FREE(rh);
+
+ TALLOC_FREE(ctx);
+
+}
+
+static void test_dsdb_audit_get_primary_dn(void **state)
+{
+ struct ldb_request *req = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_context *ldb = NULL;
+
+ struct ldb_dn *dn = NULL;
+
+ const char * const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+ const char *s = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ req = talloc_zero(ctx, struct ldb_request);
+ msg = talloc_zero(ctx, struct ldb_message);
+ ldb = ldb_init(ctx, NULL);
+ dn = ldb_dn_new(ctx, ldb, DN);
+
+ /*
+ * Try an empty request.
+ */
+ s = dsdb_audit_get_primary_dn(req);
+ assert_null(s);
+
+ /*
+ * Now try an add with a null message.
+ */
+ req->operation = LDB_ADD;
+ req->op.add.message = NULL;
+ s = dsdb_audit_get_primary_dn(req);
+ assert_null(s);
+
+ /*
+ * Now try an mod with a null message.
+ */
+ req->operation = LDB_MODIFY;
+ req->op.mod.message = NULL;
+ s = dsdb_audit_get_primary_dn(req);
+ assert_null(s);
+
+ /*
+ * Now try an add with a missing dn
+ */
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+ s = dsdb_audit_get_primary_dn(req);
+ assert_null(s);
+
+ /*
+ * Now try a mod with a messing dn
+ */
+ req->operation = LDB_ADD;
+ req->op.mod.message = msg;
+ s = dsdb_audit_get_primary_dn(req);
+ assert_null(s);
+
+ /*
+ * Add a dn to the message
+ */
+ msg->dn = dn;
+
+ /*
+ * Now try an add with a dn
+ */
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+ s = dsdb_audit_get_primary_dn(req);
+ assert_non_null(s);
+ assert_string_equal(DN, s);
+
+ /*
+ * Now try a mod with a dn
+ */
+ req->operation = LDB_MODIFY;
+ req->op.mod.message = msg;
+ s = dsdb_audit_get_primary_dn(req);
+ assert_non_null(s);
+ assert_string_equal(DN, s);
+
+ /*
+ * Try a delete without a dn
+ */
+ req->operation = LDB_DELETE;
+ req->op.del.dn = NULL;
+ s = dsdb_audit_get_primary_dn(req);
+ assert_null(s);
+
+ /*
+ * Try a delete with a dn
+ */
+ req->operation = LDB_DELETE;
+ req->op.del.dn = dn;
+ s = dsdb_audit_get_primary_dn(req);
+ assert_non_null(s);
+ assert_string_equal(DN, s);
+
+ /*
+ * Try a rename without a dn
+ */
+ req->operation = LDB_RENAME;
+ req->op.rename.olddn = NULL;
+ s = dsdb_audit_get_primary_dn(req);
+ assert_null(s);
+
+ /*
+ * Try a rename with a dn
+ */
+ req->operation = LDB_RENAME;
+ req->op.rename.olddn = dn;
+ s = dsdb_audit_get_primary_dn(req);
+ assert_non_null(s);
+ assert_string_equal(DN, s);
+
+ /*
+ * Try an extended operation, i.e. one that does not have a DN
+ * associated with it for logging purposes.
+ */
+ req->operation = LDB_EXTENDED;
+ s = dsdb_audit_get_primary_dn(req);
+ assert_null(s);
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_dsdb_audit_get_message(void **state)
+{
+ struct ldb_request *req = NULL;
+ struct ldb_message *msg = NULL;
+ const struct ldb_message *r = NULL;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ req = talloc_zero(ctx, struct ldb_request);
+ msg = talloc_zero(ctx, struct ldb_message);
+
+ /*
+ * Test an empty message
+ */
+ r = dsdb_audit_get_message(req);
+ assert_null(r);
+
+ /*
+ * Test an add message
+ */
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+ r = dsdb_audit_get_message(req);
+ assert_ptr_equal(msg, r);
+
+ /*
+ * Test a modify message
+ */
+ req->operation = LDB_MODIFY;
+ req->op.mod.message = msg;
+ r = dsdb_audit_get_message(req);
+ assert_ptr_equal(msg, r);
+
+ /*
+ * Test a Delete message, i.e. trigger the default case
+ */
+ req->operation = LDB_DELETE;
+ r = dsdb_audit_get_message(req);
+ assert_null(r);
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_dsdb_audit_get_secondary_dn(void **state)
+{
+ struct ldb_request *req = NULL;
+ struct ldb_context *ldb = NULL;
+
+ struct ldb_dn *dn = NULL;
+
+ const char * const DN = "dn=CN=USER,CN=Users,DC=SAMBA,DC=ORG";
+ const char *s = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ req = talloc_zero(ctx, struct ldb_request);
+ ldb = ldb_init(ctx, NULL);
+ dn = ldb_dn_new(ctx, ldb, DN);
+
+ /*
+ * Try an empty request.
+ */
+ s = dsdb_audit_get_secondary_dn(req);
+ assert_null(s);
+
+ /*
+ * Try a rename without a dn
+ */
+ req->operation = LDB_RENAME;
+ req->op.rename.newdn = NULL;
+ s = dsdb_audit_get_secondary_dn(req);
+ assert_null(s);
+
+ /*
+ * Try a rename with a dn
+ */
+ req->operation = LDB_RENAME;
+ req->op.rename.newdn = dn;
+ s = dsdb_audit_get_secondary_dn(req);
+ assert_non_null(s);
+ assert_string_equal(DN, s);
+
+ /*
+ * Try an extended operation, i.e. one that does not have a DN
+ * associated with it for logging purposes.
+ */
+ req->operation = LDB_EXTENDED;
+ s = dsdb_audit_get_primary_dn(req);
+ assert_null(s);
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_dsdb_audit_get_operation_name(void **state)
+{
+ struct ldb_request *req = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ req = talloc_zero(ctx, struct ldb_request);
+
+ req->operation = LDB_SEARCH;
+ assert_string_equal("Search", dsdb_audit_get_operation_name(req));
+
+ req->operation = LDB_ADD;
+ assert_string_equal("Add", dsdb_audit_get_operation_name(req));
+
+ req->operation = LDB_MODIFY;
+ assert_string_equal("Modify", dsdb_audit_get_operation_name(req));
+
+ req->operation = LDB_DELETE;
+ assert_string_equal("Delete", dsdb_audit_get_operation_name(req));
+
+ req->operation = LDB_RENAME;
+ assert_string_equal("Rename", dsdb_audit_get_operation_name(req));
+
+ req->operation = LDB_EXTENDED;
+ assert_string_equal("Extended", dsdb_audit_get_operation_name(req));
+
+ req->operation = LDB_REQ_REGISTER_CONTROL;
+ assert_string_equal(
+ "Register Control",
+ dsdb_audit_get_operation_name(req));
+
+ req->operation = LDB_REQ_REGISTER_PARTITION;
+ assert_string_equal(
+ "Register Partition",
+ dsdb_audit_get_operation_name(req));
+
+ /*
+ * Trigger the default case
+ */
+ req->operation = -1;
+ assert_string_equal("Unknown", dsdb_audit_get_operation_name(req));
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_dsdb_audit_get_modification_action(void **state)
+{
+ assert_string_equal(
+ "add",
+ dsdb_audit_get_modification_action(LDB_FLAG_MOD_ADD));
+ assert_string_equal(
+ "delete",
+ dsdb_audit_get_modification_action(LDB_FLAG_MOD_DELETE));
+ assert_string_equal(
+ "replace",
+ dsdb_audit_get_modification_action(LDB_FLAG_MOD_REPLACE));
+ /*
+ * Trigger the default case
+ */
+ assert_string_equal(
+ "unknown",
+ dsdb_audit_get_modification_action(0));
+}
+
+static void test_dsdb_audit_is_password_attribute(void **state)
+{
+ assert_true(dsdb_audit_is_password_attribute("userPassword"));
+ assert_true(dsdb_audit_is_password_attribute("clearTextPassword"));
+ assert_true(dsdb_audit_is_password_attribute("unicodePwd"));
+ assert_true(dsdb_audit_is_password_attribute("dBCSPwd"));
+
+ assert_false(dsdb_audit_is_password_attribute("xserPassword"));
+}
+
+static void test_dsdb_audit_redact_attribute(void **state)
+{
+ assert_true(dsdb_audit_redact_attribute("userPassword"));
+
+ assert_true(dsdb_audit_redact_attribute("pekList"));
+ assert_true(dsdb_audit_redact_attribute("clearTextPassword"));
+ assert_true(dsdb_audit_redact_attribute("initialAuthIncoming"));
+
+ assert_false(dsdb_audit_redact_attribute("supaskrt"));
+}
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_dsdb_audit_add_ldb_value),
+ cmocka_unit_test(test_dsdb_audit_attributes_json),
+ cmocka_unit_test(test_dsdb_audit_get_remote_address),
+ cmocka_unit_test(test_dsdb_audit_get_ldb_error_string),
+ cmocka_unit_test(test_dsdb_audit_get_user_sid),
+ cmocka_unit_test(test_dsdb_audit_get_actual_sid),
+ cmocka_unit_test(test_dsdb_audit_is_system_session),
+ cmocka_unit_test(test_dsdb_audit_get_unique_session_token),
+ cmocka_unit_test(test_dsdb_audit_get_actual_unique_session_token),
+ cmocka_unit_test(test_dsdb_audit_get_remote_host),
+ cmocka_unit_test(test_dsdb_audit_get_primary_dn),
+ cmocka_unit_test(test_dsdb_audit_get_message),
+ cmocka_unit_test(test_dsdb_audit_get_secondary_dn),
+ cmocka_unit_test(test_dsdb_audit_get_operation_name),
+ cmocka_unit_test(test_dsdb_audit_get_modification_action),
+ cmocka_unit_test(test_dsdb_audit_is_password_attribute),
+ cmocka_unit_test(test_dsdb_audit_redact_attribute),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_encrypted_secrets.c b/source4/dsdb/samdb/ldb_modules/tests/test_encrypted_secrets.c
new file mode 100644
index 0000000..e639d4c
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/test_encrypted_secrets.c
@@ -0,0 +1,973 @@
+/*
+ Unit tests for the encrypted secrets code in encrypted_secrets.c
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
+
+ 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 <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <cmocka.h>
+
+int ldb_encrypted_secrets_module_init(const char *version);
+#define TEST_ENCRYPTED_SECRETS
+#include "../encrypted_secrets.c"
+
+struct ldbtest_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+ struct ldb_module *module;
+
+ const char *dbfile;
+ const char *lockfile; /* lockfile is separate */
+ const char *keyfile;
+
+ const char *dbpath;
+};
+
+/* -------------------------------------------------------------------------- */
+/*
+ * Replace the dsdb helper routines used by the operational_init function
+ *
+ */
+int dsdb_module_search_dn(
+ struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_result **_res,
+ struct ldb_dn *basedn,
+ const char * const *attrs,
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_message *msg = ldb_msg_new(ldb);
+ struct ldb_result *res = talloc_zero(mem_ctx, struct ldb_result);
+
+ msg->dn = ldb_dn_new(msg, ldb, "@SAMBA_DSDB");
+ ldb_msg_add_string(
+ msg,
+ SAMBA_REQUIRED_FEATURES_ATTR,
+ SAMBA_ENCRYPTED_SECRETS_FEATURE);
+
+ res->msgs = talloc_array(mem_ctx, struct ldb_message*, 1);
+ res->msgs[0] = msg;
+ *_res = res;
+ return LDB_SUCCESS;
+}
+
+int dsdb_module_reference_dn(
+ struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *base,
+ const char *attribute,
+ struct ldb_dn **dn,
+ struct ldb_request *parent)
+{
+ return LDB_SUCCESS;
+}
+/* -------------------------------------------------------------------------- */
+
+static void unlink_old_db(struct ldbtest_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->lockfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+
+ errno = 0;
+ ret = unlink(test_ctx->keyfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+static void write_key(void **state, DATA_BLOB key) {
+
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+ FILE *fp = NULL;
+ int written = 0;
+
+ fp = fopen(test_ctx->keyfile, "wb");
+ assert_non_null(fp);
+
+ written = fwrite(key.data, 1, key.length, fp);
+ assert_int_equal(written, key.length);
+ fclose(fp);
+}
+
+static const struct ldb_module_ops eol_ops = {
+ .name = "eol",
+ .search = NULL,
+ .add = NULL,
+ .modify = NULL,
+ .del = NULL,
+ .rename = NULL,
+ .init_context = NULL
+};
+
+static int setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx = NULL;
+ struct ldb_module *eol = NULL;
+ int rc;
+
+ test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+
+
+ test_ctx->module = ldb_module_new(
+ test_ctx,
+ test_ctx->ldb,
+ "encrypted_secrets",
+ &ldb_encrypted_secrets_module_ops);
+ assert_non_null(test_ctx->module);
+ eol = ldb_module_new(test_ctx, test_ctx->ldb, "eol", &eol_ops);
+ assert_non_null(eol);
+ ldb_module_set_next(test_ctx->module, eol);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "apitest.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock",
+ test_ctx->dbfile);
+ assert_non_null(test_ctx->lockfile);
+
+ test_ctx->keyfile = talloc_strdup(test_ctx, SECRETS_KEY_FILE);
+ assert_non_null(test_ctx->keyfile);
+
+ test_ctx->dbpath = talloc_asprintf(test_ctx,
+ TEST_BE"://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+
+ rc = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(rc, 0);
+ *state = test_ctx;
+ return 0;
+}
+
+static int setup_with_key(void **state)
+{
+ struct ldbtest_ctx *test_ctx = NULL;
+ DATA_BLOB key = data_blob_null;
+ uint8_t key_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
+ int rc;
+
+ setup(state);
+ key.data = key_data;
+ key.length = sizeof(key_data);
+
+ write_key(state, key);
+
+ test_ctx = talloc_get_type_abort(*state, struct ldbtest_ctx);
+ {
+ struct ldb_message *msg = ldb_msg_new(test_ctx->ldb);
+ msg->dn = ldb_dn_new(msg, test_ctx->ldb, "@SAMBA_DSDB");
+ ldb_msg_add_string(
+ msg,
+ SAMBA_REQUIRED_FEATURES_ATTR,
+ SAMBA_ENCRYPTED_SECRETS_FEATURE);
+ ldb_add(test_ctx->ldb, msg);
+ }
+
+ rc = es_init(test_ctx->module);
+ assert_int_equal(rc, LDB_SUCCESS);
+
+ return 0;
+}
+
+static int teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+/*
+ * No key file present.
+ *
+ * The key should be empty and encrypt_secrets should be false.
+ */
+static void test_no_key_file(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct es_data *data = NULL;
+
+ int rc;
+
+ rc = es_init(test_ctx->module);
+ assert_int_equal(rc, LDB_SUCCESS);
+
+ data = talloc_get_type(ldb_module_get_private(test_ctx->module),
+ struct es_data);
+
+ assert_false(data->encrypt_secrets);
+ assert_int_equal(0, data->keys[0].length);
+
+}
+
+/*
+ * Key file present.
+ *
+ * The key should be loaded and encrypt secrets should be true;
+ */
+static void test_key_file(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct es_data *data = NULL;
+ int rc;
+ DATA_BLOB key = data_blob_null;
+ uint8_t key_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
+
+ key.data = key_data;
+ key.length = sizeof(key_data);
+
+ write_key(state, key);
+
+
+ rc = es_init(test_ctx->module);
+ assert_int_equal(rc, LDB_SUCCESS);
+
+ data = talloc_get_type(ldb_module_get_private(test_ctx->module),
+ struct es_data);
+
+ assert_true(data->encrypt_secrets);
+ assert_int_equal(16, data->keys[0].length);
+ assert_int_equal(0, data_blob_cmp(&key, &data->keys[0]));
+
+}
+
+/*
+ * Key file present, short key.
+ *
+ * The key should be not be loaded and an error returned.
+ */
+static void test_key_file_short_key(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ int rc;
+ DATA_BLOB key = data_blob_null;
+ uint8_t key_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e};
+
+ key.data = key_data;
+ key.length = sizeof(key_data);
+
+ write_key(state, key);
+
+
+ rc = es_init(test_ctx->module);
+ assert_int_equal(rc, LDB_ERR_OPERATIONS_ERROR);
+}
+
+/*
+ * Key file present, long key.
+ *
+ * Only the first 16 bytes of the key should be loaded.
+ */
+static void test_key_file_long_key(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct es_data *data = NULL;
+ int rc;
+ DATA_BLOB key = data_blob_null;
+ uint8_t key_data[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf,
+ 0x10};
+
+ key.data = key_data;
+ key.length = sizeof(key_data);
+
+ write_key(state, key);
+
+ rc = es_init(test_ctx->module);
+ assert_int_equal(rc, LDB_SUCCESS);
+
+ data = talloc_get_type(ldb_module_get_private(test_ctx->module),
+ struct es_data);
+
+ assert_true(data->encrypt_secrets);
+ assert_int_equal(16, data->keys[0].length);
+
+ /*
+ * Should have only read the first 16 bytes of the written key
+ */
+ key.length = 16;
+ assert_int_equal(0, data_blob_cmp(&key, &data->keys[0]));
+}
+
+/*
+ * Test gnutls_encryption and decryption.
+ */
+static void test_gnutls_value_decryption(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ const struct ldb_val plain_text =
+ data_blob_string_const("A text value");
+ unsigned char iv_data[] = {
+ 0xe7, 0xa3, 0x85, 0x17, 0x45, 0x73, 0xf4, 0x25,
+ 0xa5, 0x56, 0xde, 0x4c,
+ };
+ unsigned char encrypted_data[] = {
+ 0xac, 0x13, 0x86, 0x94, 0x3b, 0xed, 0xf2, 0x51,
+ 0xec, 0x85, 0x4d, 0x00, 0x37, 0x81, 0x46, 0x15,
+ 0x42, 0x13, 0xb1, 0x69, 0x49, 0x10, 0xe7, 0x9e,
+ 0x15, 0xbd, 0x95, 0x75, 0x6b, 0x0c, 0xc0, 0xa4,
+ };
+ struct EncryptedSecret es = {
+ .iv = {
+ .data = iv_data,
+ .length = sizeof(iv_data),
+ },
+ .header = {
+ .magic = ENCRYPTED_SECRET_MAGIC_VALUE,
+ .version = SECRET_ATTRIBUTE_VERSION,
+ .algorithm = ENC_SECRET_AES_128_AEAD,
+ },
+ .encrypted = {
+ .data = encrypted_data,
+ .length = sizeof(encrypted_data),
+ }
+ };
+ unsigned char es_keys_blob[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ };
+ struct es_data data = {
+ .encrypt_secrets = true,
+ .keys[0] = {
+ .data = es_keys_blob,
+ .length = sizeof(es_keys_blob),
+ },
+ .encryption_algorithm = GNUTLS_CIPHER_AES_128_GCM,
+ };
+ struct PlaintextSecret *decrypted =
+ talloc_zero(test_ctx, struct PlaintextSecret);
+ int err = LDB_SUCCESS;
+
+ gnutls_decrypt_aead(&err,
+ test_ctx,
+ test_ctx->ldb,
+ &es,
+ decrypted,
+ &data);
+ assert_int_equal(LDB_SUCCESS, err);
+ assert_int_equal(plain_text.length, decrypted->cleartext.length);
+ assert_int_equal(0, data_blob_cmp(&decrypted->cleartext, &plain_text));
+}
+
+/*
+ * Test gnutls_encryption and decryption.
+ */
+static void test_gnutls_value_encryption(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct ldb_val plain_text = data_blob_null;
+ struct ldb_val cipher_text = data_blob_null;
+ struct EncryptedSecret es;
+
+ struct es_data *data = talloc_get_type(
+ ldb_module_get_private(test_ctx->module),
+ struct es_data);
+ int err = LDB_SUCCESS;
+ int rc;
+
+ plain_text = data_blob_string_const("A text value");
+ cipher_text = gnutls_encrypt_aead(
+ &err,
+ test_ctx,
+ test_ctx->ldb,
+ plain_text,
+ data);
+ assert_int_equal(LDB_SUCCESS, err);
+
+ rc = ndr_pull_struct_blob(
+ &cipher_text,
+ test_ctx,
+ &es,
+ (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret);
+ assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+ assert_true(check_header(&es));
+
+ {
+ struct PlaintextSecret *decrypted =
+ talloc_zero(test_ctx, struct PlaintextSecret);
+ gnutls_decrypt_aead(
+ &err,
+ test_ctx,
+ test_ctx->ldb,
+ &es,
+ decrypted,
+ data);
+ assert_int_equal(LDB_SUCCESS, err);
+ assert_int_equal(
+ plain_text.length,
+ decrypted->cleartext.length);
+ assert_int_equal(0,
+ data_blob_cmp(
+ &decrypted->cleartext,
+ &plain_text));
+ }
+}
+
+static void test_gnutls_altered_header(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct ldb_val plain_text = data_blob_null;
+ struct ldb_val cipher_text = data_blob_null;
+ struct EncryptedSecret es;
+
+ struct es_data *data = talloc_get_type(
+ ldb_module_get_private(test_ctx->module),
+ struct es_data);
+ int err = LDB_SUCCESS;
+ int rc;
+
+ plain_text = data_blob_string_const("A text value");
+ cipher_text = gnutls_encrypt_aead(
+ &err,
+ test_ctx,
+ test_ctx->ldb,
+ plain_text,
+ data);
+ assert_int_equal(LDB_SUCCESS, err);
+
+ rc = ndr_pull_struct_blob(
+ &cipher_text,
+ test_ctx,
+ &es,
+ (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret);
+ assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+ assert_true(check_header(&es));
+
+ {
+ struct PlaintextSecret *decrypted =
+ talloc_zero(test_ctx, struct PlaintextSecret);
+ gnutls_decrypt_aead(
+ &err,
+ test_ctx,
+ test_ctx->ldb,
+ &es,
+ decrypted,
+ data);
+ assert_int_equal(LDB_SUCCESS, err);
+ assert_int_equal(
+ plain_text.length,
+ decrypted->cleartext.length);
+ assert_int_equal(0,
+ data_blob_cmp(
+ &decrypted->cleartext,
+ &plain_text));
+ }
+ es.header.flags = es.header.flags ^ 0xffffffff;
+ {
+ struct PlaintextSecret *decrypted =
+ talloc_zero(test_ctx, struct PlaintextSecret);
+ gnutls_decrypt_aead(
+ &err,
+ test_ctx,
+ test_ctx->ldb,
+ &es,
+ decrypted,
+ data);
+ assert_int_equal(LDB_ERR_OPERATIONS_ERROR, err);
+ }
+}
+
+static void test_gnutls_altered_data(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct ldb_val plain_text = data_blob_null;
+ struct ldb_val cipher_text = data_blob_null;
+ struct EncryptedSecret es;
+
+ struct es_data *data = talloc_get_type(
+ ldb_module_get_private(test_ctx->module),
+ struct es_data);
+ int err = LDB_SUCCESS;
+ int rc;
+
+ plain_text = data_blob_string_const("A text value");
+ cipher_text = gnutls_encrypt_aead(
+ &err,
+ test_ctx,
+ test_ctx->ldb,
+ plain_text,
+ data);
+ assert_int_equal(LDB_SUCCESS, err);
+
+ rc = ndr_pull_struct_blob(
+ &cipher_text,
+ test_ctx,
+ &es,
+ (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret);
+ assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+ assert_true(check_header(&es));
+
+ {
+ struct PlaintextSecret *decrypted =
+ talloc_zero(test_ctx, struct PlaintextSecret);
+ gnutls_decrypt_aead(
+ &err,
+ test_ctx,
+ test_ctx->ldb,
+ &es,
+ decrypted,
+ data);
+ assert_int_equal(LDB_SUCCESS, err);
+ assert_int_equal(
+ plain_text.length,
+ decrypted->cleartext.length);
+ assert_int_equal(0,
+ data_blob_cmp(
+ &decrypted->cleartext,
+ &plain_text));
+ }
+ es.encrypted.data[0] = es.encrypted.data[0] ^ 0xff;
+ {
+ struct PlaintextSecret *decrypted =
+ talloc_zero(test_ctx, struct PlaintextSecret);
+ gnutls_decrypt_aead(
+ &err,
+ test_ctx,
+ test_ctx->ldb,
+ &es,
+ decrypted,
+ data);
+ assert_int_equal(LDB_ERR_OPERATIONS_ERROR, err);
+ }
+}
+
+static void test_gnutls_altered_iv(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct ldb_val plain_text = data_blob_null;
+ struct ldb_val cipher_text = data_blob_null;
+ struct EncryptedSecret es;
+
+ struct es_data *data = talloc_get_type(
+ ldb_module_get_private(test_ctx->module),
+ struct es_data);
+ int err = LDB_SUCCESS;
+ int rc;
+
+ plain_text = data_blob_string_const("A text value");
+ cipher_text = gnutls_encrypt_aead(
+ &err,
+ test_ctx,
+ test_ctx->ldb,
+ plain_text,
+ data);
+ assert_int_equal(LDB_SUCCESS, err);
+
+ rc = ndr_pull_struct_blob(
+ &cipher_text,
+ test_ctx,
+ &es,
+ (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret);
+ assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+ assert_true(check_header(&es));
+
+ {
+ struct PlaintextSecret *decrypted =
+ talloc_zero(test_ctx, struct PlaintextSecret);
+ gnutls_decrypt_aead(
+ &err,
+ test_ctx,
+ test_ctx->ldb,
+ &es,
+ decrypted,
+ data);
+ assert_int_equal(LDB_SUCCESS, err);
+ assert_int_equal(
+ plain_text.length,
+ decrypted->cleartext.length);
+ assert_int_equal(0,
+ data_blob_cmp(
+ &decrypted->cleartext,
+ &plain_text));
+ }
+ es.iv.data[0] = es.iv.data[0] ^ 0xff;
+ {
+ struct PlaintextSecret *decrypted =
+ talloc_zero(test_ctx, struct PlaintextSecret);
+ gnutls_decrypt_aead(
+ &err,
+ test_ctx,
+ test_ctx->ldb,
+ &es,
+ decrypted,
+ data);
+ assert_int_equal(LDB_ERR_OPERATIONS_ERROR, err);
+ }
+}
+
+/*
+ * Test samba encryption and decryption and decryption.
+ */
+
+/*
+ * Test message encryption.
+ * Test the secret attributes of a message are encrypted and decrypted.
+ * Test that the non secret attributes are not encrypted.
+ *
+ */
+static void test_message_encryption_decryption(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct ldb_context *ldb = test_ctx->ldb;
+ const char * const secrets[] = {DSDB_SECRET_ATTRIBUTES};
+ const size_t num_secrets
+ = (sizeof(secrets)/sizeof(secrets[0]));
+ struct ldb_message *msg = ldb_msg_new(ldb);
+ const struct ldb_message *encrypted_msg = NULL;
+ struct es_data *data = talloc_get_type(
+ ldb_module_get_private(test_ctx->module),
+ struct es_data);
+ struct ldb_message_element *el = NULL;
+ int ret = LDB_SUCCESS;
+ size_t i;
+ unsigned int j;
+
+ msg->dn = ldb_dn_new(msg, ldb, "dc=test");
+ ldb_msg_add_string(msg, "cmocka_test_name01", "value01");
+ for (i=0; i < num_secrets; i++) {
+ ldb_msg_add_string(
+ msg,
+ secrets[i],
+ secrets[i]);
+ }
+ ldb_msg_add_string(msg, "cmocka_test_name02", "value02");
+
+ encrypted_msg = encrypt_secret_attributes(
+ &ret,
+ test_ctx,
+ test_ctx->ldb,
+ msg,
+ data);
+ assert_int_equal(LDB_SUCCESS, ret);
+
+ /*
+ * Check that all the secret attributes have been encrypted
+ *
+ */
+ for (i=0; i < num_secrets; i++) {
+ el = ldb_msg_find_element(encrypted_msg, secrets[i]);
+ assert_non_null(el);
+ for (j = 0; j < el->num_values; j++) {
+ int rc = LDB_SUCCESS;
+ struct ldb_val dc = decrypt_value(
+ &rc,
+ test_ctx,
+ test_ctx->ldb,
+ el->values[j],
+ data);
+ assert_int_equal(LDB_SUCCESS, rc);
+ assert_memory_equal(
+ secrets[i],
+ dc.data,
+ dc.length);
+ TALLOC_FREE(dc.data);
+ }
+ }
+
+ /*
+ * Check that the normal attributes have not been encrypted
+ */
+ el = ldb_msg_find_element(encrypted_msg, "cmocka_test_name01");
+ assert_non_null(el);
+ assert_memory_equal(
+ "value01",
+ el->values[0].data,
+ el->values[0].length);
+
+ el = ldb_msg_find_element(encrypted_msg, "cmocka_test_name02");
+ assert_non_null(el);
+ assert_memory_equal(
+ "value02",
+ el->values[0].data,
+ el->values[0].length);
+
+ /*
+ * Now decrypt the message
+ */
+ ret = decrypt_secret_attributes(test_ctx->ldb,
+ discard_const(encrypted_msg),
+ data);
+ assert_int_equal(LDB_SUCCESS, ret);
+
+ /*
+ * Check that all the secret attributes have been decrypted
+ */
+ for (i=0; i < num_secrets; i++) {
+ el = ldb_msg_find_element(encrypted_msg, secrets[i]);
+ assert_non_null(el);
+ for (j = 0; j < el->num_values; j++) {
+ assert_memory_equal(
+ secrets[i],
+ el->values[j].data,
+ el->values[j].length);
+ }
+ }
+
+ /*
+ * Check that the normal attributes are intact
+ */
+ el = ldb_msg_find_element(msg, "cmocka_test_name01");
+ assert_non_null(el);
+ assert_memory_equal(
+ "value01",
+ el->values[0].data,
+ el->values[0].length);
+
+ el = ldb_msg_find_element(msg, "cmocka_test_name02");
+ assert_non_null(el);
+ assert_memory_equal(
+ "value02",
+ el->values[0].data,
+ el->values[0].length);
+
+}
+
+static void test_check_header(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+
+ struct ldb_val enc = data_blob_null;
+ struct EncryptedSecret *es = NULL;
+ int rc;
+
+ /*
+ * Valid EncryptedSecret
+ */
+ es = makeEncryptedSecret(test_ctx->ldb, test_ctx);
+ rc = ndr_push_struct_blob(
+ &enc,
+ test_ctx,
+ es,
+ (ndr_push_flags_fn_t) ndr_push_EncryptedSecret);
+ assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+ assert_true(check_header(es));
+ TALLOC_FREE(enc.data);
+ TALLOC_FREE(es);
+
+ /*
+ * invalid magic value
+ */
+ es = makeEncryptedSecret(test_ctx->ldb, test_ctx);
+ es->header.magic = 0xca5cadee;
+ rc = ndr_push_struct_blob(
+ &enc,
+ test_ctx,
+ es,
+ (ndr_push_flags_fn_t) ndr_push_EncryptedSecret);
+ assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+ assert_false(check_header(es));
+ TALLOC_FREE(enc.data);
+ TALLOC_FREE(es);
+
+ /*
+ * invalid version
+ */
+ es = makeEncryptedSecret(test_ctx->ldb, test_ctx);
+ es->header.version = SECRET_ATTRIBUTE_VERSION + 1;
+ rc = ndr_push_struct_blob(
+ &enc,
+ test_ctx,
+ es,
+ (ndr_push_flags_fn_t) ndr_push_EncryptedSecret);
+ assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+ assert_false(check_header(es));
+ TALLOC_FREE(enc.data);
+ TALLOC_FREE(es);
+
+ /*
+ * invalid algorithm
+ */
+ es = makeEncryptedSecret(test_ctx->ldb, test_ctx);
+ es->header.algorithm = SECRET_ENCRYPTION_ALGORITHM + 1;
+ rc = ndr_push_struct_blob(
+ &enc,
+ test_ctx,
+ es,
+ (ndr_push_flags_fn_t) ndr_push_EncryptedSecret);
+ assert_true(NDR_ERR_CODE_IS_SUCCESS(rc));
+ assert_false(check_header(es));
+ TALLOC_FREE(enc.data);
+ TALLOC_FREE(es);
+}
+
+/*
+ * Attempt to decrypt a message containing an unencrypted secret attribute
+ * this should fail
+ */
+static void test_unencrypted_secret(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct ldb_context *ldb = test_ctx->ldb;
+ struct ldb_message *msg = ldb_msg_new(ldb);
+ struct es_data *data = talloc_get_type(
+ ldb_module_get_private(test_ctx->module),
+ struct es_data);
+ int ret = LDB_SUCCESS;
+
+ msg->dn = ldb_dn_new(msg, ldb, "dc=test");
+ ldb_msg_add_string(msg, "unicodePwd", "value01");
+
+ ret = decrypt_secret_attributes(test_ctx->ldb, msg, data);
+ assert_int_equal(LDB_ERR_OPERATIONS_ERROR, ret);
+}
+
+/*
+ * Test full decryption of a static value with static key
+ */
+static void test_record_decryption(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ unsigned char plain_data[] = {
+ 0xe6, 0xa6, 0xb8, 0xff, 0xdf, 0x06, 0x6c, 0xe3,
+ 0xea, 0xd0, 0x94, 0xbb, 0x79, 0xbd, 0x0a, 0x24
+ };
+ unsigned char encrypted_data[] = {
+ 0x0c, 0x00, 0x00, 0x00, 0x33, 0x91, 0x74, 0x25,
+ 0x26, 0xcc, 0x0b, 0x8c, 0x21, 0xc1, 0x13, 0xe2,
+ 0xed, 0xad, 0x5c, 0xca, 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x1a, 0xdc, 0xc9, 0x11, 0x08, 0xca, 0x2c, 0xfb,
+ 0xc8, 0x32, 0x6b, 0x1b, 0x25, 0x7f, 0x52, 0xbb,
+ 0xae, 0x9b, 0x88, 0x52, 0xb0, 0x18, 0x6d, 0x9d,
+ 0x9b, 0xdd, 0xcd, 0x1b, 0x5f, 0x4a, 0x5c, 0x29,
+ 0xca, 0x0b, 0x36, 0xaa
+ };
+ struct ldb_val cipher_text
+ = data_blob_const(encrypted_data,
+ sizeof(encrypted_data));
+ unsigned char es_keys_blob[] = {
+ 0x1d, 0xae, 0xf5, 0xaa, 0xa3, 0x85, 0x0d, 0x0a,
+ 0x8c, 0x24, 0x5c, 0x4c, 0xa7, 0x0f, 0x81, 0x79
+ };
+ struct es_data data = {
+ .encrypt_secrets = true,
+ .keys[0] = {
+ .data = es_keys_blob,
+ .length = sizeof(es_keys_blob),
+ },
+ .encryption_algorithm = GNUTLS_CIPHER_AES_128_GCM,
+ };
+ int err = LDB_SUCCESS;
+ struct ldb_val dec = decrypt_value(&err, test_ctx, test_ctx->ldb, cipher_text,
+ &data);
+ assert_int_equal(LDB_SUCCESS, err);
+ assert_int_equal(sizeof(plain_data), dec.length);
+ assert_memory_equal(dec.data, plain_data, sizeof(plain_data));
+}
+
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_no_key_file,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_key_file,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_key_file_short_key,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_key_file_long_key,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_check_header,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_gnutls_value_decryption,
+ setup_with_key,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_gnutls_value_encryption,
+ setup_with_key,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_gnutls_altered_header,
+ setup_with_key,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_gnutls_altered_data,
+ setup_with_key,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_gnutls_altered_iv,
+ setup_with_key,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_message_encryption_decryption,
+ setup_with_key,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_unencrypted_secret,
+ setup_with_key,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_record_decryption,
+ setup_with_key,
+ teardown),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_group_audit.c b/source4/dsdb/samdb/ldb_modules/tests/test_group_audit.c
new file mode 100644
index 0000000..f7075f3
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/test_group_audit.c
@@ -0,0 +1,2022 @@
+/*
+ Unit tests for the dsdb group auditing code in group_audit.c
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+
+ 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 <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <cmocka.h>
+
+int ldb_group_audit_log_module_init(const char *version);
+#include "../group_audit.c"
+
+#include "lib/ldb/include/ldb_private.h"
+#include <regex.h>
+
+/*
+ * Mock version of dsdb_search_one
+ */
+struct ldb_dn *g_basedn = NULL;
+enum ldb_scope g_scope;
+const char * const *g_attrs = NULL;
+uint32_t g_dsdb_flags;
+const char *g_exp_fmt;
+const char *g_dn = NULL;
+int g_status = LDB_SUCCESS;
+struct ldb_result *g_result = NULL;
+
+int dsdb_search_one(struct ldb_context *ldb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message **msg,
+ struct ldb_dn *basedn,
+ enum ldb_scope scope,
+ const char * const *attrs,
+ uint32_t dsdb_flags,
+ const char *exp_fmt, ...) _PRINTF_ATTRIBUTE(8, 9)
+{
+ struct ldb_dn *dn = ldb_dn_new(mem_ctx, ldb, g_dn);
+ struct ldb_message *m = talloc_zero(mem_ctx, struct ldb_message);
+ m->dn = dn;
+ *msg = m;
+
+ g_basedn = basedn;
+ g_scope = scope;
+ g_attrs = attrs;
+ g_dsdb_flags = dsdb_flags;
+ g_exp_fmt = exp_fmt;
+
+ return g_status;
+}
+
+int dsdb_module_search_dn(
+ struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_result **res,
+ struct ldb_dn *basedn,
+ const char * const *attrs,
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
+{
+
+ g_basedn = basedn;
+ g_attrs = attrs;
+ g_dsdb_flags = dsdb_flags;
+
+ *res = g_result;
+
+ return g_status;
+}
+/*
+ * Mock version of audit_log_json
+ */
+
+#define MAX_EXPECTED_MESSAGES 16
+static struct json_object messages[MAX_EXPECTED_MESSAGES];
+static size_t messages_sent = 0;
+
+void audit_message_send(
+ struct imessaging_context *msg_ctx,
+ const char *server_name,
+ uint32_t message_type,
+ struct json_object *message)
+{
+ messages[messages_sent].root = json_deep_copy(message->root);
+ messages[messages_sent].valid = message->valid;
+ messages_sent++;
+}
+
+#define check_group_change_message(m, u, a, e) \
+ _check_group_change_message(m, u, a, e, __FILE__, __LINE__);
+/*
+ * declare the internal cmocka cm_print_error so that we can output messages
+ * in sub unit format
+ */
+void cm_print_error(const char * const format, ...);
+
+/*
+ * Validate a group change JSON audit message
+ *
+ * It should contain 3 elements.
+ * Have a type of "groupChange"
+ * Have a groupChange element
+ *
+ * The group change element should have 10 elements.
+ *
+ * There should be a user element matching the expected value
+ * There should be an action matching the expected value
+ */
+static void _check_group_change_message(const int message,
+ const char *user,
+ const char *action,
+ enum event_id_type event_id,
+ const char *file,
+ const int line)
+{
+ struct json_object json;
+ json_t *audit = NULL;
+ json_t *v = NULL;
+ const char* value;
+ int int_value;
+ int cmp;
+
+ json = messages[message];
+
+ /*
+ * Validate the root JSON element
+ * check the number of elements
+ */
+ if (json_object_size(json.root) != 3) {
+ cm_print_error(
+ "Unexpected number of elements in root %zu != %d\n",
+ json_object_size(json.root),
+ 3);
+ _fail(file, line);
+ }
+
+ /*
+ * Check the type element
+ */
+ v = json_object_get(json.root, "type");
+ if (v == NULL) {
+ cm_print_error( "No \"type\" element\n");
+ _fail(file, line);
+ }
+
+ value = json_string_value(v);
+ cmp = strcmp("groupChange", value);
+ if (cmp != 0) {
+ cm_print_error(
+ "Unexpected type \"%s\" != \"groupChange\"\n",
+ value);
+ _fail(file, line);
+ }
+
+
+ audit = json_object_get(json.root, "groupChange");
+ if (audit == NULL) {
+ cm_print_error("No groupChange element\n");
+ _fail(file, line);
+ }
+
+ /*
+ * Validate the groupChange element
+ */
+ if ((event_id == EVT_ID_NONE && json_object_size(audit) != 10) ||
+ (event_id != EVT_ID_NONE && json_object_size(audit) != 11)) {
+ cm_print_error("Unexpected number of elements in groupChange "
+ "%zu != %d\n",
+ json_object_size(audit),
+ 11);
+ _fail(file, line);
+ }
+ /*
+ * Validate the user element
+ */
+ v = json_object_get(audit, "user");
+ if (v == NULL) {
+ cm_print_error( "No user element\n");
+ _fail(file, line);
+ }
+
+ value = json_string_value(v);
+ cmp = strcmp(user, value);
+ if (cmp != 0) {
+ cm_print_error(
+ "Unexpected user name \"%s\" != \"%s\"\n",
+ value,
+ user);
+ _fail(file, line);
+ }
+ /*
+ * Validate the action element
+ */
+ v = json_object_get(audit, "action");
+ if (v == NULL) {
+ cm_print_error( "No action element\n");
+ _fail(file, line);
+ }
+
+ value = json_string_value(v);
+ cmp = strcmp(action, value);
+ if (cmp != 0) {
+ print_error(
+ "Unexpected action \"%s\" != \"%s\"\n",
+ value,
+ action);
+ _fail(file, line);
+ }
+
+ /*
+ * Validate the eventId element
+ */
+ v = json_object_get(audit, "eventId");
+ if (event_id == EVT_ID_NONE) {
+ if (v != NULL) {
+ int_value = json_integer_value(v);
+ cm_print_error("Unexpected eventId \"%d\", it should "
+ "NOT be present",
+ int_value);
+ _fail(file, line);
+ }
+ }
+ else {
+ if (v == NULL) {
+ cm_print_error("No eventId element\n");
+ _fail(file, line);
+ }
+
+ int_value = json_integer_value(v);
+ if (int_value != event_id) {
+ cm_print_error("Unexpected eventId \"%d\" != \"%d\"\n",
+ int_value,
+ event_id);
+ _fail(file, line);
+ }
+ }
+}
+
+#define check_timestamp(b, t)\
+ _check_timestamp(b, t, __FILE__, __LINE__);
+/*
+ * Test helper to check ISO 8601 timestamps for validity
+ */
+static void _check_timestamp(
+ time_t before,
+ const char *timestamp,
+ const char *file,
+ const int line)
+{
+ int rc;
+ int usec, tz;
+ char c[2];
+ struct tm tm;
+ time_t after;
+ time_t actual;
+ struct timeval tv;
+
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ after = tv.tv_sec;
+
+ /*
+ * Convert the ISO 8601 timestamp into a time_t
+ * Note for convenience we ignore the value of the microsecond
+ * part of the time stamp.
+ */
+ rc = sscanf(
+ timestamp,
+ "%4d-%2d-%2dT%2d:%2d:%2d.%6d%1c%4d",
+ &tm.tm_year,
+ &tm.tm_mon,
+ &tm.tm_mday,
+ &tm.tm_hour,
+ &tm.tm_min,
+ &tm.tm_sec,
+ &usec,
+ c,
+ &tz);
+ assert_int_equal(9, rc);
+ tm.tm_year = tm.tm_year - 1900;
+ tm.tm_mon = tm.tm_mon - 1;
+ tm.tm_isdst = -1;
+ actual = mktime(&tm);
+
+ /*
+ * The time stamp should be before <= actual <= after
+ */
+ if (difftime(actual, before) < 0) {
+ char buffer[40];
+ strftime(buffer,
+ sizeof(buffer)-1,
+ "%Y-%m-%dT%T",
+ localtime(&before));
+ cm_print_error(
+ "time stamp \"%s\" is before start time \"%s\"\n",
+ timestamp,
+ buffer);
+ _fail(file, line);
+ }
+ if (difftime(after, actual) < 0) {
+ char buffer[40];
+ strftime(buffer,
+ sizeof(buffer)-1,
+ "%Y-%m-%dT%T",
+ localtime(&after));
+ cm_print_error(
+ "time stamp \"%s\" is after finish time \"%s\"\n",
+ timestamp,
+ buffer);
+ _fail(file, line);
+ }
+}
+
+#define check_version(v, m, n)\
+ _check_version(v, m, n, __FILE__, __LINE__);
+/*
+ * Test helper to validate a version object.
+ */
+static void _check_version(
+ struct json_t *version,
+ int major,
+ int minor,
+ const char* file,
+ const int line)
+{
+ struct json_t *v = NULL;
+ int value;
+
+ if (!json_is_object(version)) {
+ cm_print_error("version is not a JSON object\n");
+ _fail(file, line);
+ }
+
+ if (json_object_size(version) != 2) {
+ cm_print_error(
+ "Unexpected number of elements in version %zu != %d\n",
+ json_object_size(version),
+ 2);
+ _fail(file, line);
+ }
+
+ /*
+ * Validate the major version number element
+ */
+ v = json_object_get(version, "major");
+ if (v == NULL) {
+ cm_print_error( "No major element\n");
+ _fail(file, line);
+ }
+
+ value = json_integer_value(v);
+ if (value != major) {
+ print_error(
+ "Unexpected major version number \"%d\" != \"%d\"\n",
+ value,
+ major);
+ _fail(file, line);
+ }
+
+ /*
+ * Validate the minor version number element
+ */
+ v = json_object_get(version, "minor");
+ if (v == NULL) {
+ cm_print_error( "No minor element\n");
+ _fail(file, line);
+ }
+
+ value = json_integer_value(v);
+ if (value != minor) {
+ print_error(
+ "Unexpected minor version number \"%d\" != \"%d\"\n",
+ value,
+ minor);
+ _fail(file, line);
+ }
+}
+
+/*
+ * Test helper to insert a transaction_id into a request.
+ */
+static void add_transaction_id(struct ldb_request *req, const char *id)
+{
+ struct GUID guid;
+ struct dsdb_control_transaction_identifier *transaction_id = NULL;
+
+ transaction_id = talloc_zero(
+ req,
+ struct dsdb_control_transaction_identifier);
+ assert_non_null(transaction_id);
+ GUID_from_string(id, &guid);
+ transaction_id->transaction_guid = guid;
+ ldb_request_add_control(
+ req,
+ DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID,
+ false,
+ transaction_id);
+}
+
+/*
+ * Test helper to add a session id and user SID
+ */
+static void add_session_data(
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const char *session,
+ const char *user_sid)
+{
+ struct auth_session_info *sess = NULL;
+ struct security_token *token = NULL;
+ struct dom_sid *sid = NULL;
+ struct GUID session_id;
+ bool ok;
+
+ sess = talloc_zero(ctx, struct auth_session_info);
+ token = talloc_zero(ctx, struct security_token);
+ sid = talloc_zero(ctx, struct dom_sid);
+ ok = string_to_sid(sid, user_sid);
+ assert_true(ok);
+ token->sids = sid;
+ sess->security_token = token;
+ GUID_from_string(session, &session_id);
+ sess->unique_session_token = session_id;
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
+}
+
+static void test_get_transaction_id(void **state)
+{
+ struct ldb_request *req = NULL;
+ struct GUID *guid;
+ const char * const ID = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ char *guid_str = NULL;
+ struct GUID_txt_buf guid_buff;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+
+ /*
+ * No transaction id, should return a zero guid
+ */
+ req = talloc_zero(ctx, struct ldb_request);
+ guid = get_transaction_id(req);
+ assert_null(guid);
+ TALLOC_FREE(req);
+
+ /*
+ * And now test with the transaction_id set
+ */
+ req = talloc_zero(ctx, struct ldb_request);
+ assert_non_null(req);
+ add_transaction_id(req, ID);
+
+ guid = get_transaction_id(req);
+ guid_str = GUID_buf_string(guid, &guid_buff);
+ assert_string_equal(ID, guid_str);
+ TALLOC_FREE(req);
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_audit_group_hr(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+
+ struct tsocket_address *ts = NULL;
+
+ const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+
+ char *line = NULL;
+ const char *rs = NULL;
+ regex_t regex;
+ int ret;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ GUID_from_string(TRANSACTION, &transaction_id);
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(ldb, "remoteAddress", ts);
+
+ add_session_data(ctx, ldb, SESSION, SID);
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ add_transaction_id(req, TRANSACTION);
+
+ line = audit_group_human_readable(
+ ctx,
+ module,
+ req,
+ "the-action",
+ "the-user-name",
+ "the-group-name",
+ LDB_ERR_OPERATIONS_ERROR);
+ assert_non_null(line);
+
+ rs = "\\[the-action\\] at \\["
+ "[^]]*"
+ "\\] status \\[Operations error\\] "
+ "Remote host \\[ipv4:127.0.0.1:0\\] "
+ "SID \\[S-1-5-21-2470180966-3899876309-2637894779\\] "
+ "Group \\[the-group-name\\] "
+ "User \\[the-user-name\\]";
+
+ ret = regcomp(&regex, rs, 0);
+ assert_int_equal(0, ret);
+
+ ret = regexec(&regex, line, 0, NULL, 0);
+ assert_int_equal(0, ret);
+
+ regfree(&regex);
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * test get_parsed_dns
+ * For this test we assume Valgrind or Address Sanitizer will detect any over
+ * runs. Also we don't care that the values are DN's only that the value in the
+ * element is copied to the parsed_dns.
+ */
+static void test_get_parsed_dns(void **state)
+{
+ struct ldb_message_element *el = NULL;
+ struct parsed_dn *dns = NULL;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ el = talloc_zero(ctx, struct ldb_message_element);
+
+ /*
+ * empty element, zero dns
+ */
+ dns = get_parsed_dns(ctx, el);
+ assert_null(dns);
+
+ /*
+ * one entry
+ */
+ el->num_values = 1;
+ el->values = talloc_zero_array(ctx, DATA_BLOB, 1);
+ el->values[0] = data_blob_string_const("The first value");
+
+ dns = get_parsed_dns(ctx, el);
+
+ assert_ptr_equal(el->values[0].data, dns[0].v->data);
+ assert_int_equal(el->values[0].length, dns[0].v->length);
+
+ TALLOC_FREE(dns);
+ TALLOC_FREE(el);
+
+
+ /*
+ * Multiple values
+ */
+ el = talloc_zero(ctx, struct ldb_message_element);
+ el->num_values = 2;
+ el->values = talloc_zero_array(ctx, DATA_BLOB, 2);
+ el->values[0] = data_blob_string_const("The first value");
+ el->values[0] = data_blob_string_const("The second value");
+
+ dns = get_parsed_dns(ctx, el);
+
+ assert_ptr_equal(el->values[0].data, dns[0].v->data);
+ assert_int_equal(el->values[0].length, dns[0].v->length);
+
+ assert_ptr_equal(el->values[1].data, dns[1].v->data);
+ assert_int_equal(el->values[1].length, dns[1].v->length);
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_dn_compare(void **state)
+{
+
+ struct ldb_context *ldb = NULL;
+ struct parsed_dn *a;
+ DATA_BLOB ab;
+
+ struct parsed_dn *b;
+ DATA_BLOB bb;
+
+ int res;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ const struct GUID *ZERO_GUID = talloc_zero(ctx, struct GUID);
+
+ ldb = ldb_init(ctx, NULL);
+ ldb_register_samba_handlers(ldb);
+
+
+ /*
+ * Identical binary DN's
+ */
+ ab = data_blob_string_const(
+ "<GUID=fbee08fd-6f75-4bd4-af3f-e4f063a6379e>;"
+ "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org");
+ a = talloc_zero(ctx, struct parsed_dn);
+ a->v = &ab;
+
+ bb = data_blob_string_const(
+ "<GUID=fbee08fd-6f75-4bd4-af3f-e4f063a6379e>;"
+ "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org");
+ b = talloc_zero(ctx, struct parsed_dn);
+ b->v = &bb;
+
+ res = dn_compare(ctx, ldb, a, b);
+ assert_int_equal(BINARY_EQUAL, res);
+ /*
+ * DN's should not have been parsed
+ */
+ assert_null(a->dsdb_dn);
+ assert_memory_equal(ZERO_GUID, &a->guid, sizeof(struct GUID));
+ assert_null(b->dsdb_dn);
+ assert_memory_equal(ZERO_GUID, &b->guid, sizeof(struct GUID));
+
+ TALLOC_FREE(a);
+ TALLOC_FREE(b);
+
+ /*
+ * differing binary DN's but equal GUID's
+ */
+ ab = data_blob_string_const(
+ "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651e>;"
+ "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=com");
+ a = talloc_zero(ctx, struct parsed_dn);
+ a->v = &ab;
+
+ bb = data_blob_string_const(
+ "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651e>;"
+ "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org");
+ b = talloc_zero(ctx, struct parsed_dn);
+ b->v = &bb;
+
+ res = dn_compare(ctx, ldb, a, b);
+ assert_int_equal(EQUAL, res);
+ /*
+ * DN's should have been parsed
+ */
+ assert_non_null(a->dsdb_dn);
+ assert_memory_not_equal(ZERO_GUID, &a->guid, sizeof(struct GUID));
+ assert_non_null(b->dsdb_dn);
+ assert_memory_not_equal(ZERO_GUID, &b->guid, sizeof(struct GUID));
+
+ TALLOC_FREE(a);
+ TALLOC_FREE(b);
+
+ /*
+ * differing binary DN's but and second guid greater
+ */
+ ab = data_blob_string_const(
+ "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651d>;"
+ "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=com");
+ a = talloc_zero(ctx, struct parsed_dn);
+ a->v = &ab;
+
+ bb = data_blob_string_const(
+ "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651e>;"
+ "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org");
+ b = talloc_zero(ctx, struct parsed_dn);
+ b->v = &bb;
+
+ res = dn_compare(ctx, ldb, a, b);
+ assert_int_equal(LESS_THAN, res);
+ /*
+ * DN's should have been parsed
+ */
+ assert_non_null(a->dsdb_dn);
+ assert_memory_not_equal(ZERO_GUID, &a->guid, sizeof(struct GUID));
+ assert_non_null(b->dsdb_dn);
+ assert_memory_not_equal(ZERO_GUID, &b->guid, sizeof(struct GUID));
+
+ TALLOC_FREE(a);
+ TALLOC_FREE(b);
+
+ /*
+ * differing binary DN's but and second guid less
+ */
+ ab = data_blob_string_const(
+ "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651d>;"
+ "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=com");
+ a = talloc_zero(ctx, struct parsed_dn);
+ a->v = &ab;
+
+ bb = data_blob_string_const(
+ "<GUID=efdc91e5-5a5a-493e-9606-166ed0c2651c>;"
+ "OU=Domain Controllers,DC=ad,DC=testing,DC=samba,DC=org");
+ b = talloc_zero(ctx, struct parsed_dn);
+ b->v = &bb;
+
+ res = dn_compare(ctx, ldb, a, b);
+ assert_int_equal(GREATER_THAN, res);
+ /*
+ * DN's should have been parsed
+ */
+ assert_non_null(a->dsdb_dn);
+ assert_memory_not_equal(ZERO_GUID, &a->guid, sizeof(struct GUID));
+ assert_non_null(b->dsdb_dn);
+ assert_memory_not_equal(ZERO_GUID, &b->guid, sizeof(struct GUID));
+
+ TALLOC_FREE(a);
+ TALLOC_FREE(b);
+
+ TALLOC_FREE(ctx);
+}
+
+static void test_get_primary_group_dn(void **state)
+{
+
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ const uint32_t RID = 71;
+ struct dom_sid sid;
+ const char *SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char *DN = "OU=Things,DC=ad,DC=testing,DC=samba,DC=org";
+ const char *dn;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+ ldb_register_samba_handlers(ldb);
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+
+ /*
+ * Pass an empty dom sid this will cause dom_sid_split_rid to fail;
+ * assign to sid.num_auths to suppress a valgrind warning.
+ */
+ sid.num_auths = 0;
+ dn = get_primary_group_dn(ctx, module, &sid, RID);
+ assert_null(dn);
+
+ /*
+ * A valid dom sid
+ */
+ assert_true(string_to_sid(&sid, SID));
+ g_dn = DN;
+ dn = get_primary_group_dn(ctx, module, &sid, RID);
+ assert_non_null(dn);
+ assert_string_equal(DN, dn);
+ assert_int_equal(LDB_SCOPE_BASE, g_scope);
+ assert_int_equal(0, g_dsdb_flags);
+ assert_null(g_attrs);
+ assert_null(g_exp_fmt);
+ assert_string_equal
+ ("<SID=S-1-5-21-2470180966-3899876309-71>",
+ ldb_dn_get_extended_linearized(ctx, g_basedn, 1));
+
+ /*
+ * Test dsdb search failure
+ */
+ g_status = LDB_ERR_NO_SUCH_OBJECT;
+ dn = get_primary_group_dn(ctx, module, &sid, RID);
+ assert_null(dn);
+
+ TALLOC_FREE(ldb);
+ TALLOC_FREE(ctx);
+}
+
+static void test_audit_group_json(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+
+ struct tsocket_address *ts = NULL;
+
+ const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ enum event_id_type event_id = EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP;
+
+ struct json_object json;
+ json_t *audit = NULL;
+ json_t *v = NULL;
+ json_t *o = NULL;
+ time_t before;
+ struct timeval tv;
+ int rc;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ GUID_from_string(TRANSACTION, &transaction_id);
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(ldb, "remoteAddress", ts);
+
+ add_session_data(ctx, ldb, SESSION, SID);
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ add_transaction_id(req, TRANSACTION);
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ before = tv.tv_sec;
+ json = audit_group_json(module,
+ req,
+ "the-action",
+ "the-user-name",
+ "the-group-name",
+ event_id,
+ LDB_SUCCESS);
+ assert_int_equal(3, json_object_size(json.root));
+
+ v = json_object_get(json.root, "type");
+ assert_non_null(v);
+ assert_string_equal("groupChange", json_string_value(v));
+
+ v = json_object_get(json.root, "timestamp");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ check_timestamp(before, json_string_value(v));
+
+ audit = json_object_get(json.root, "groupChange");
+ assert_non_null(audit);
+ assert_true(json_is_object(audit));
+ assert_int_equal(11, json_object_size(audit));
+
+ o = json_object_get(audit, "version");
+ assert_non_null(o);
+ check_version(o, AUDIT_MAJOR, AUDIT_MINOR);
+
+ v = json_object_get(audit, "eventId");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP,
+ json_integer_value(v));
+
+ v = json_object_get(audit, "statusCode");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(LDB_SUCCESS, json_integer_value(v));
+
+ v = json_object_get(audit, "status");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("Success", json_string_value(v));
+
+ v = json_object_get(audit, "user");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("the-user-name", json_string_value(v));
+
+ v = json_object_get(audit, "group");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("the-group-name", json_string_value(v));
+
+ v = json_object_get(audit, "action");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("the-action", json_string_value(v));
+
+ json_free(&json);
+ TALLOC_FREE(ctx);
+}
+
+static void test_audit_group_json_error(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+
+ struct tsocket_address *ts = NULL;
+
+ const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ enum event_id_type event_id = EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP;
+
+ struct json_object json;
+ json_t *audit = NULL;
+ json_t *v = NULL;
+ json_t *o = NULL;
+ time_t before;
+ struct timeval tv;
+ int rc;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ GUID_from_string(TRANSACTION, &transaction_id);
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(ldb, "remoteAddress", ts);
+
+ add_session_data(ctx, ldb, SESSION, SID);
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ add_transaction_id(req, TRANSACTION);
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ before = tv.tv_sec;
+ json = audit_group_json(module,
+ req,
+ "the-action",
+ "the-user-name",
+ "the-group-name",
+ event_id,
+ LDB_ERR_OPERATIONS_ERROR);
+ assert_int_equal(3, json_object_size(json.root));
+
+ v = json_object_get(json.root, "type");
+ assert_non_null(v);
+ assert_string_equal("groupChange", json_string_value(v));
+
+ v = json_object_get(json.root, "timestamp");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ check_timestamp(before, json_string_value(v));
+
+ audit = json_object_get(json.root, "groupChange");
+ assert_non_null(audit);
+ assert_true(json_is_object(audit));
+ assert_int_equal(11, json_object_size(audit));
+
+ o = json_object_get(audit, "version");
+ assert_non_null(o);
+ check_version(o, AUDIT_MAJOR, AUDIT_MINOR);
+
+ v = json_object_get(audit, "eventId");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(
+ EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP,
+ json_integer_value(v));
+
+ v = json_object_get(audit, "statusCode");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(LDB_ERR_OPERATIONS_ERROR, json_integer_value(v));
+
+ v = json_object_get(audit, "status");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("Operations error", json_string_value(v));
+
+ v = json_object_get(audit, "user");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("the-user-name", json_string_value(v));
+
+ v = json_object_get(audit, "group");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("the-group-name", json_string_value(v));
+
+ v = json_object_get(audit, "action");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("the-action", json_string_value(v));
+
+ json_free(&json);
+ TALLOC_FREE(ctx);
+}
+
+static void test_audit_group_json_no_event(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+
+ struct tsocket_address *ts = NULL;
+
+ const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ enum event_id_type event_id = EVT_ID_NONE;
+
+ struct json_object json;
+ json_t *audit = NULL;
+ json_t *v = NULL;
+ json_t *o = NULL;
+ time_t before;
+ struct timeval tv;
+ int rc;
+
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ GUID_from_string(TRANSACTION, &transaction_id);
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(ldb, "remoteAddress", ts);
+
+ add_session_data(ctx, ldb, SESSION, SID);
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ add_transaction_id(req, TRANSACTION);
+
+ rc = gettimeofday(&tv, NULL);
+ assert_return_code(rc, errno);
+ before = tv.tv_sec;
+ json = audit_group_json(module,
+ req,
+ "the-action",
+ "the-user-name",
+ "the-group-name",
+ event_id,
+ LDB_SUCCESS);
+ assert_int_equal(3, json_object_size(json.root));
+
+ v = json_object_get(json.root, "type");
+ assert_non_null(v);
+ assert_string_equal("groupChange", json_string_value(v));
+
+ v = json_object_get(json.root, "timestamp");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ check_timestamp(before, json_string_value(v));
+
+ audit = json_object_get(json.root, "groupChange");
+ assert_non_null(audit);
+ assert_true(json_is_object(audit));
+ assert_int_equal(10, json_object_size(audit));
+
+ o = json_object_get(audit, "version");
+ assert_non_null(o);
+ check_version(o, AUDIT_MAJOR, AUDIT_MINOR);
+
+ v = json_object_get(audit, "eventId");
+ assert_null(v);
+
+ v = json_object_get(audit, "statusCode");
+ assert_non_null(v);
+ assert_true(json_is_integer(v));
+ assert_int_equal(LDB_SUCCESS, json_integer_value(v));
+
+ v = json_object_get(audit, "status");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("Success", json_string_value(v));
+
+ v = json_object_get(audit, "user");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("the-user-name", json_string_value(v));
+
+ v = json_object_get(audit, "group");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("the-group-name", json_string_value(v));
+
+ v = json_object_get(audit, "action");
+ assert_non_null(v);
+ assert_true(json_is_string(v));
+ assert_string_equal("the-action", json_string_value(v));
+
+ json_free(&json);
+ TALLOC_FREE(ctx);
+}
+static void setup_ldb(
+ TALLOC_CTX *ctx,
+ struct ldb_context **ldb,
+ struct ldb_module **module,
+ const char *ip,
+ const char *session,
+ const char *sid)
+{
+ struct tsocket_address *ts = NULL;
+ struct audit_context *context = NULL;
+
+ *ldb = ldb_init(ctx, NULL);
+ ldb_register_samba_handlers(*ldb);
+
+
+ *module = talloc_zero(ctx, struct ldb_module);
+ (*module)->ldb = *ldb;
+
+ context = talloc_zero(*module, struct audit_context);
+ context->send_events = true;
+ context->msg_ctx = (struct imessaging_context *) 0x01;
+
+ ldb_module_set_private(*module, context);
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(*ldb, "remoteAddress", ts);
+
+ add_session_data(ctx, *ldb, session, sid);
+}
+
+/*
+ * Test the removal of a user from a group.
+ *
+ * The new element contains one group member
+ * The old element contains two group member
+ *
+ * Expect to see the removed entry logged.
+ *
+ * This test confirms bug 13664
+ * https://bugzilla.samba.org/show_bug.cgi?id=13664
+ */
+static void test_log_membership_changes_removed(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const IP = "127.0.0.1";
+ struct ldb_request *req = NULL;
+ struct ldb_message_element *new_el = NULL;
+ struct ldb_message_element *old_el = NULL;
+ uint32_t group_type = GTYPE_SECURITY_GLOBAL_GROUP;
+ int status = 0;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ setup_ldb(ctx, &ldb, &module, IP, SESSION, SID);
+
+ /*
+ * Build the ldb_request
+ */
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ add_transaction_id(req, TRANSACTION);
+
+ /*
+ * Populate the new elements, containing one entry.
+ * Indicating that one element has been removed
+ */
+ new_el = talloc_zero(ctx, struct ldb_message_element);
+ new_el->num_values = 1;
+ new_el->values = talloc_zero_array(ctx, DATA_BLOB, 1);
+ new_el->values[0] = data_blob_string_const(
+ "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
+ "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
+ "DC=example,DC=com");
+
+ /*
+ * Populate the old elements, with two elements
+ * The first is the same as the one in new elements.
+ */
+ old_el = talloc_zero(ctx, struct ldb_message_element);
+ old_el->num_values = 2;
+ old_el->values = talloc_zero_array(ctx, DATA_BLOB, 2);
+ old_el->values[0] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;"
+ "cn=grpadttstuser01,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+ old_el->values[1] = data_blob_string_const(
+ "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
+ "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
+ "DC=example,DC=com");
+
+ /*
+ * call log_membership_changes
+ */
+ messages_sent = 0;
+ log_membership_changes(module, req, new_el, old_el, group_type, status);
+
+ /*
+ * Check the results
+ */
+ assert_int_equal(1, messages_sent);
+
+ check_group_change_message(
+ 0,
+ "cn=grpadttstuser01,cn=users,DC=addom,DC=samba,DC=example,DC=com",
+ "Removed",
+ EVT_ID_USER_REMOVED_FROM_GLOBAL_SEC_GROUP);
+
+ /*
+ * Clean up
+ */
+ json_free(&messages[0]);
+ TALLOC_FREE(ctx);
+}
+
+/* test log_membership_changes
+ *
+ * old contains 2 user dn's
+ * new contains 0 user dn's
+ *
+ * Expect to see both dn's logged as deleted.
+ */
+static void test_log_membership_changes_remove_all(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const IP = "127.0.0.1";
+ struct ldb_request *req = NULL;
+ struct ldb_message_element *new_el = NULL;
+ struct ldb_message_element *old_el = NULL;
+ int status = 0;
+ uint32_t group_type = GTYPE_SECURITY_BUILTIN_LOCAL_GROUP;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ setup_ldb(ctx, &ldb, &module, IP, SESSION, SID);
+
+ /*
+ * Build the ldb_request
+ */
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ add_transaction_id(req, TRANSACTION);
+
+ /*
+ * Populate the new elements, containing no entries.
+ * Indicating that all elements have been removed
+ */
+ new_el = talloc_zero(ctx, struct ldb_message_element);
+ new_el->num_values = 0;
+ new_el->values = NULL;
+
+ /*
+ * Populate the old elements, with two elements
+ */
+ old_el = talloc_zero(ctx, struct ldb_message_element);
+ old_el->num_values = 2;
+ old_el->values = talloc_zero_array(ctx, DATA_BLOB, 2);
+ old_el->values[0] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;"
+ "cn=grpadttstuser01,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+ old_el->values[1] = data_blob_string_const(
+ "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
+ "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
+ "DC=example,DC=com");
+
+ /*
+ * call log_membership_changes
+ */
+ messages_sent = 0;
+ log_membership_changes(module, req, new_el, old_el, group_type, status);
+
+ /*
+ * Check the results
+ */
+ assert_int_equal(2, messages_sent);
+
+ check_group_change_message(
+ 0,
+ "cn=grpadttstuser01,cn=users,DC=addom,DC=samba,DC=example,DC=com",
+ "Removed",
+ EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP);
+
+ check_group_change_message(
+ 1,
+ "CN=testuser131953,CN=Users,DC=addom,DC=samba,DC=example,DC=com",
+ "Removed",
+ EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP);
+
+ /*
+ * Clean up
+ */
+ json_free(&messages[0]);
+ json_free(&messages[1]);
+ TALLOC_FREE(ctx);
+}
+
+/* test log_membership_changes
+ *
+ * Add an entry.
+ *
+ * Old entries contains a single user dn
+ * New entries contains 2 user dn's, one matching the dn in old entries
+ *
+ * Should see a single new entry logged.
+ */
+static void test_log_membership_changes_added(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const IP = "127.0.0.1";
+ struct ldb_request *req = NULL;
+ struct ldb_message_element *new_el = NULL;
+ struct ldb_message_element *old_el = NULL;
+ uint32_t group_type = GTYPE_SECURITY_DOMAIN_LOCAL_GROUP;
+ int status = 0;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ setup_ldb(ctx, &ldb, &module, IP, SESSION, SID);
+
+ /*
+ * Build the ldb_request
+ */
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ add_transaction_id(req, TRANSACTION);
+
+ /*
+ * Populate the old elements adding a single entry.
+ */
+ old_el = talloc_zero(ctx, struct ldb_message_element);
+ old_el->num_values = 1;
+ old_el->values = talloc_zero_array(ctx, DATA_BLOB, 1);
+ old_el->values[0] = data_blob_string_const(
+ "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
+ "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
+ "DC=example,DC=com");
+
+ /*
+ * Populate the new elements adding two entries. One matches the entry
+ * in old elements. We expect to see the other element logged as Added
+ */
+ new_el = talloc_zero(ctx, struct ldb_message_element);
+ new_el->num_values = 2;
+ new_el->values = talloc_zero_array(ctx, DATA_BLOB, 2);
+ new_el->values[0] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;"
+ "cn=grpadttstuser01,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+ new_el->values[1] = data_blob_string_const(
+ "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
+ "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
+ "DC=example,DC=com");
+
+ /*
+ * call log_membership_changes
+ */
+ messages_sent = 0;
+ log_membership_changes(module, req, new_el, old_el, group_type, status);
+
+ /*
+ * Check the results
+ */
+ assert_int_equal(1, messages_sent);
+
+ check_group_change_message(
+ 0,
+ "cn=grpadttstuser01,cn=users,DC=addom,DC=samba,DC=example,DC=com",
+ "Added",
+ EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP);
+
+ /*
+ * Clean up
+ */
+ json_free(&messages[0]);
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * test log_membership_changes.
+ *
+ * Old entries is empty
+ * New entries contains 2 user dn's
+ *
+ * Expect to see log messages for two added users
+ */
+static void test_log_membership_changes_add_to_empty(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const IP = "127.0.0.1";
+ struct ldb_request *req = NULL;
+ struct ldb_message_element *new_el = NULL;
+ struct ldb_message_element *old_el = NULL;
+ uint32_t group_type = GTYPE_SECURITY_UNIVERSAL_GROUP;
+ int status = 0;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ /*
+ * Set up the ldb and module structures
+ */
+ setup_ldb(ctx, &ldb, &module, IP, SESSION, SID);
+
+ /*
+ * Build the request structure
+ */
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ add_transaction_id(req, TRANSACTION);
+
+ /*
+ * Build the element containing the old values
+ */
+ old_el = talloc_zero(ctx, struct ldb_message_element);
+ old_el->num_values = 0;
+ old_el->values = NULL;
+
+ /*
+ * Build the element containing the new values
+ */
+ new_el = talloc_zero(ctx, struct ldb_message_element);
+ new_el->num_values = 2;
+ new_el->values = talloc_zero_array(ctx, DATA_BLOB, 2);
+ new_el->values[0] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;"
+ "cn=grpadttstuser01,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+ new_el->values[1] = data_blob_string_const(
+ "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
+ "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
+ "DC=example,DC=com");
+
+ /*
+ * Run log membership changes
+ */
+ messages_sent = 0;
+ log_membership_changes(module, req, new_el, old_el, group_type, status);
+ assert_int_equal(2, messages_sent);
+
+ check_group_change_message(
+ 0,
+ "cn=grpadttstuser01,cn=users,DC=addom,DC=samba,DC=example,DC=com",
+ "Added",
+ EVT_ID_USER_ADDED_TO_UNIVERSAL_SEC_GROUP);
+
+ check_group_change_message(
+ 1,
+ "CN=testuser131953,CN=Users,DC=addom,DC=samba,DC=example,DC=com",
+ "Added",
+ EVT_ID_USER_ADDED_TO_UNIVERSAL_SEC_GROUP);
+
+ json_free(&messages[0]);
+ json_free(&messages[1]);
+ TALLOC_FREE(ctx);
+}
+
+/* test log_membership_changes
+ *
+ * Test Replication Meta Data flag handling.
+ *
+ * 4 entries in old and new entries with their RMD_FLAGS set as below:
+ * old new
+ * 1) 0 0 Not logged
+ * 2) 1 1 Both deleted, no change not logged
+ * 3) 0 1 New tagged as deleted, log as deleted
+ * 4) 1 0 Has been undeleted, log as an add
+ *
+ * Should see a single new entry logged.
+ */
+static void test_log_membership_changes_rmd_flags(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const IP = "127.0.0.1";
+ struct ldb_request *req = NULL;
+ struct ldb_message_element *new_el = NULL;
+ struct ldb_message_element *old_el = NULL;
+ uint32_t group_type = GTYPE_SECURITY_GLOBAL_GROUP;
+ int status = 0;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ setup_ldb(ctx, &ldb, &module, IP, SESSION, SID);
+
+ /*
+ * Build the ldb_request
+ */
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ add_transaction_id(req, TRANSACTION);
+
+ /*
+ * Populate the old elements.
+ */
+ old_el = talloc_zero(ctx, struct ldb_message_element);
+ old_el->num_values = 4;
+ old_el->values = talloc_zero_array(ctx, DATA_BLOB, 4);
+ old_el->values[0] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;"
+ "<RMD_FLAGS=0>;"
+ "cn=grpadttstuser01,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+ old_el->values[1] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681c>;"
+ "<RMD_FLAGS=1>;"
+ "cn=grpadttstuser02,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+ old_el->values[2] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681d>;"
+ "<RMD_FLAGS=0>;"
+ "cn=grpadttstuser03,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+ old_el->values[3] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681e>;"
+ "<RMD_FLAGS=1>;"
+ "cn=grpadttstuser04,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+
+ /*
+ * Populate the new elements.
+ */
+ new_el = talloc_zero(ctx, struct ldb_message_element);
+ new_el->num_values = 4;
+ new_el->values = talloc_zero_array(ctx, DATA_BLOB, 4);
+ new_el->values[0] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;"
+ "<RMD_FLAGS=0>;"
+ "cn=grpadttstuser01,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+ new_el->values[1] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681c>;"
+ "<RMD_FLAGS=1>;"
+ "cn=grpadttstuser02,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+ new_el->values[2] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681d>;"
+ "<RMD_FLAGS=1>;"
+ "cn=grpadttstuser03,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+ new_el->values[3] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681e>;"
+ "<RMD_FLAGS=0>;"
+ "cn=grpadttstuser04,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+
+ /*
+ * call log_membership_changes
+ */
+ messages_sent = 0;
+ log_membership_changes(module, req, new_el, old_el, group_type, status);
+
+ /*
+ * Check the results
+ */
+ assert_int_equal(2, messages_sent);
+
+ check_group_change_message(
+ 0,
+ "cn=grpadttstuser03,cn=users,DC=addom,DC=samba,DC=example,DC=com",
+ "Removed",
+ EVT_ID_USER_REMOVED_FROM_GLOBAL_SEC_GROUP);
+ check_group_change_message(
+ 1,
+ "cn=grpadttstuser04,cn=users,DC=addom,DC=samba,DC=example,DC=com",
+ "Added",
+ EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP);
+
+ /*
+ * Clean up
+ */
+ json_free(&messages[0]);
+ json_free(&messages[1]);
+ TALLOC_FREE(ctx);
+}
+
+static void test_get_add_member_event(void **state)
+{
+ assert_int_equal(
+ EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP,
+ get_add_member_event(GTYPE_SECURITY_BUILTIN_LOCAL_GROUP));
+
+ assert_int_equal(EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP,
+ get_add_member_event(GTYPE_SECURITY_GLOBAL_GROUP));
+
+ assert_int_equal(
+ EVT_ID_USER_ADDED_TO_LOCAL_SEC_GROUP,
+ get_add_member_event(GTYPE_SECURITY_DOMAIN_LOCAL_GROUP));
+
+ assert_int_equal(EVT_ID_USER_ADDED_TO_UNIVERSAL_SEC_GROUP,
+ get_add_member_event(GTYPE_SECURITY_UNIVERSAL_GROUP));
+
+ assert_int_equal(EVT_ID_USER_ADDED_TO_GLOBAL_GROUP,
+ get_add_member_event(GTYPE_DISTRIBUTION_GLOBAL_GROUP));
+
+ assert_int_equal(
+ EVT_ID_USER_ADDED_TO_LOCAL_GROUP,
+ get_add_member_event(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP));
+
+ assert_int_equal(
+ EVT_ID_USER_ADDED_TO_UNIVERSAL_GROUP,
+ get_add_member_event(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP));
+
+ assert_int_equal(EVT_ID_NONE, get_add_member_event(0));
+
+ assert_int_equal(EVT_ID_NONE, get_add_member_event(UINT32_MAX));
+}
+
+static void test_get_remove_member_event(void **state)
+{
+ assert_int_equal(
+ EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP,
+ get_remove_member_event(GTYPE_SECURITY_BUILTIN_LOCAL_GROUP));
+
+ assert_int_equal(EVT_ID_USER_REMOVED_FROM_GLOBAL_SEC_GROUP,
+ get_remove_member_event(GTYPE_SECURITY_GLOBAL_GROUP));
+
+ assert_int_equal(
+ EVT_ID_USER_REMOVED_FROM_LOCAL_SEC_GROUP,
+ get_remove_member_event(GTYPE_SECURITY_DOMAIN_LOCAL_GROUP));
+
+ assert_int_equal(
+ EVT_ID_USER_REMOVED_FROM_UNIVERSAL_SEC_GROUP,
+ get_remove_member_event(GTYPE_SECURITY_UNIVERSAL_GROUP));
+
+ assert_int_equal(
+ EVT_ID_USER_REMOVED_FROM_GLOBAL_GROUP,
+ get_remove_member_event(GTYPE_DISTRIBUTION_GLOBAL_GROUP));
+
+ assert_int_equal(
+ EVT_ID_USER_REMOVED_FROM_LOCAL_GROUP,
+ get_remove_member_event(GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP));
+
+ assert_int_equal(
+ EVT_ID_USER_REMOVED_FROM_UNIVERSAL_GROUP,
+ get_remove_member_event(GTYPE_DISTRIBUTION_UNIVERSAL_GROUP));
+
+ assert_int_equal(EVT_ID_NONE, get_remove_member_event(0));
+
+ assert_int_equal(EVT_ID_NONE, get_remove_member_event(UINT32_MAX));
+}
+
+/* test log_group_membership_changes
+ *
+ * Happy path test case
+ *
+ */
+static void test_log_group_membership_changes(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const IP = "127.0.0.1";
+ struct ldb_request *req = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_message_element *el = NULL;
+ struct audit_callback_context *acc = NULL;
+ struct ldb_result *res = NULL;
+ struct ldb_message *new_msg = NULL;
+ struct ldb_message_element *group_type = NULL;
+ const char *group_type_str = NULL;
+ struct ldb_message_element *new_el = NULL;
+ struct ldb_message_element *old_el = NULL;
+ int status = 0;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ setup_ldb(ctx, &ldb, &module, IP, SESSION, SID);
+
+ /*
+ * Build the ldb message
+ */
+ msg = talloc_zero(ctx, struct ldb_message);
+
+ /*
+ * Populate message elements, adding a new entry to the membership list
+ *
+ */
+
+ el = talloc_zero(ctx, struct ldb_message_element);
+ el->name = "member";
+ el->num_values = 1;
+ el->values = talloc_zero_array(ctx, DATA_BLOB, 1);
+ el->values[0] = data_blob_string_const(
+ "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
+ "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
+ "DC=example,DC=com");
+ msg->elements = el;
+ msg->num_elements = 1;
+
+ /*
+ * Build the ldb_request
+ */
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+ add_transaction_id(req, TRANSACTION);
+
+ /*
+ * Build the initial state of the database
+ */
+ old_el = talloc_zero(ctx, struct ldb_message_element);
+ old_el->name = "member";
+ old_el->num_values = 1;
+ old_el->values = talloc_zero_array(ctx, DATA_BLOB, 1);
+ old_el->values[0] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;"
+ "cn=grpadttstuser01,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+
+ /*
+ * Build the updated state of the database
+ */
+ res = talloc_zero(ctx, struct ldb_result);
+ new_msg = talloc_zero(ctx, struct ldb_message);
+ new_el = talloc_zero(ctx, struct ldb_message_element);
+ new_el->name = "member";
+ new_el->num_values = 2;
+ new_el->values = talloc_zero_array(ctx, DATA_BLOB, 2);
+ new_el->values[0] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;"
+ "cn=grpadttstuser01,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+ new_el->values[1] = data_blob_string_const(
+ "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
+ "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
+ "DC=example,DC=com");
+
+ group_type = talloc_zero(ctx, struct ldb_message_element);
+ group_type->name = "groupType";
+ group_type->num_values = 1;
+ group_type->values = talloc_zero_array(ctx, DATA_BLOB, 1);
+ group_type_str = talloc_asprintf(ctx, "%u", GTYPE_SECURITY_GLOBAL_GROUP);
+ group_type->values[0] = data_blob_string_const(group_type_str);
+
+
+ new_msg->elements = talloc_zero_array(ctx, struct ldb_message_element, 2);
+ new_msg->num_elements = 2;
+ new_msg->elements[0] = *new_el;
+ new_msg->elements[1] = *group_type;
+
+ res->count = 1;
+ res->msgs = &new_msg;
+
+ acc = talloc_zero(ctx, struct audit_callback_context);
+ acc->request = req;
+ acc->module = module;
+ acc->members = old_el;
+ /*
+ * call log_membership_changes
+ */
+ messages_sent = 0;
+ g_result = res;
+ g_status = LDB_SUCCESS;
+ log_group_membership_changes(acc, status);
+ g_result = NULL;
+
+ /*
+ * Check the results
+ */
+ assert_int_equal(1, messages_sent);
+
+ check_group_change_message(
+ 0,
+ "CN=testuser131953,CN=Users,DC=addom,DC=samba,DC=example,DC=com",
+ "Added",
+ EVT_ID_USER_ADDED_TO_GLOBAL_SEC_GROUP);
+
+ /*
+ * Clean up
+ */
+ json_free(&messages[0]);
+ TALLOC_FREE(ctx);
+}
+
+/* test log_group_membership_changes
+ *
+ * The ldb query to retrieve the new values failed.
+ *
+ * Should generate group membership change Failure message.
+ *
+ */
+static void test_log_group_membership_changes_read_new_failure(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const IP = "127.0.0.1";
+ struct ldb_request *req = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_message_element *el = NULL;
+ struct audit_callback_context *acc = NULL;
+ struct ldb_message_element *old_el = NULL;
+ int status = 0;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ setup_ldb(ctx, &ldb, &module, IP, SESSION, SID);
+
+ /*
+ * Build the ldb message
+ */
+ msg = talloc_zero(ctx, struct ldb_message);
+
+ /*
+ * Populate message elements, adding a new entry to the membership list
+ *
+ */
+
+ el = talloc_zero(ctx, struct ldb_message_element);
+ el->name = "member";
+ el->num_values = 1;
+ el->values = talloc_zero_array(ctx, DATA_BLOB, 1);
+ el->values[0] = data_blob_string_const(
+ "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
+ "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
+ "DC=example,DC=com");
+ msg->elements = el;
+ msg->num_elements = 1;
+
+ /*
+ * Build the ldb_request
+ */
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+ add_transaction_id(req, TRANSACTION);
+
+ /*
+ * Build the initial state of the database
+ */
+ old_el = talloc_zero(ctx, struct ldb_message_element);
+ old_el->name = "member";
+ old_el->num_values = 1;
+ old_el->values = talloc_zero_array(ctx, DATA_BLOB, 1);
+ old_el->values[0] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;"
+ "cn=grpadttstuser01,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+
+ acc = talloc_zero(ctx, struct audit_callback_context);
+ acc->request = req;
+ acc->module = module;
+ acc->members = old_el;
+ /*
+ * call log_membership_changes
+ */
+ messages_sent = 0;
+ g_result = NULL;
+ g_status = LDB_ERR_NO_SUCH_OBJECT;
+ log_group_membership_changes(acc, status);
+
+ /*
+ * Check the results
+ */
+ assert_int_equal(1, messages_sent);
+
+ check_group_change_message(
+ 0,
+ "",
+ "Failure",
+ EVT_ID_NONE);
+
+ /*
+ * Clean up
+ */
+ json_free(&messages[0]);
+ TALLOC_FREE(ctx);
+}
+
+/* test log_group_membership_changes
+ *
+ * The operation failed.
+ *
+ * Should generate group membership change Failure message.
+ *
+ */
+static void test_log_group_membership_changes_error(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ const char * const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+ const char * const IP = "127.0.0.1";
+ struct ldb_request *req = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_message_element *el = NULL;
+ struct ldb_message_element *old_el = NULL;
+ struct audit_callback_context *acc = NULL;
+ int status = LDB_ERR_OPERATIONS_ERROR;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ setup_ldb(ctx, &ldb, &module, IP, SESSION, SID);
+
+ /*
+ * Build the ldb message
+ */
+ msg = talloc_zero(ctx, struct ldb_message);
+
+ /*
+ * Populate message elements, adding a new entry to the membership list
+ *
+ */
+
+ el = talloc_zero(ctx, struct ldb_message_element);
+ el->name = "member";
+ el->num_values = 1;
+ el->values = talloc_zero_array(ctx, DATA_BLOB, 1);
+ el->values[0] = data_blob_string_const(
+ "<GUID=081519b5-a709-44a0-bc95-dd4bfe809bf8>;"
+ "CN=testuser131953,CN=Users,DC=addom,DC=samba,"
+ "DC=example,DC=com");
+ msg->elements = el;
+ msg->num_elements = 1;
+
+ /*
+ * Build the ldb_request
+ */
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ req->op.add.message = msg;
+ add_transaction_id(req, TRANSACTION);
+
+ /*
+ * Build the initial state of the database
+ */
+ old_el = talloc_zero(ctx, struct ldb_message_element);
+ old_el->name = "member";
+ old_el->num_values = 1;
+ old_el->values = talloc_zero_array(ctx, DATA_BLOB, 1);
+ old_el->values[0] = data_blob_string_const(
+ "<GUID=cb8c2777-dcf5-419c-ab57-f645dbdf681b>;"
+ "cn=grpadttstuser01,cn=users,DC=addom,"
+ "DC=samba,DC=example,DC=com");
+
+
+ acc = talloc_zero(ctx, struct audit_callback_context);
+ acc->request = req;
+ acc->module = module;
+ acc->members = old_el;
+ /*
+ * call log_membership_changes
+ */
+ messages_sent = 0;
+ log_group_membership_changes(acc, status);
+
+ /*
+ * Check the results
+ */
+ assert_int_equal(1, messages_sent);
+
+ check_group_change_message(
+ 0,
+ "",
+ "Failure",
+ EVT_ID_NONE);
+
+ /*
+ * Clean up
+ */
+ json_free(&messages[0]);
+ TALLOC_FREE(ctx);
+}
+
+/*
+ * Note: to run under valgrind us:
+ * valgrind --suppressions=test_group_audit.valgrind bin/test_group_audit
+ * This suppresses the errors generated because the ldb_modules are not
+ * de-registered.
+ *
+ */
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_audit_group_json),
+ cmocka_unit_test(test_audit_group_json_error),
+ cmocka_unit_test(test_audit_group_json_no_event),
+ cmocka_unit_test(test_get_transaction_id),
+ cmocka_unit_test(test_audit_group_hr),
+ cmocka_unit_test(test_get_parsed_dns),
+ cmocka_unit_test(test_dn_compare),
+ cmocka_unit_test(test_get_primary_group_dn),
+ cmocka_unit_test(test_log_membership_changes_removed),
+ cmocka_unit_test(test_log_membership_changes_remove_all),
+ cmocka_unit_test(test_log_membership_changes_added),
+ cmocka_unit_test(test_log_membership_changes_add_to_empty),
+ cmocka_unit_test(test_log_membership_changes_rmd_flags),
+ cmocka_unit_test(test_get_add_member_event),
+ cmocka_unit_test(test_get_remove_member_event),
+ cmocka_unit_test(test_log_group_membership_changes),
+ cmocka_unit_test(test_log_group_membership_changes_read_new_failure),
+ cmocka_unit_test(test_log_group_membership_changes_error),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_group_audit.valgrind b/source4/dsdb/samdb/ldb_modules/tests/test_group_audit.valgrind
new file mode 100644
index 0000000..1cf2b4e
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/test_group_audit.valgrind
@@ -0,0 +1,19 @@
+{
+ ldb_modules_load modules not are freed
+ Memcheck:Leak
+ match-leak-kinds: possible
+ fun:malloc
+ fun:__talloc_with_prefix
+ fun:__talloc
+ fun:_talloc_named_const
+ fun:talloc_named_const
+ fun:ldb_register_module
+ fun:ldb_init_module
+ fun:ldb_modules_load_path
+ fun:ldb_modules_load_dir
+ fun:ldb_modules_load_path
+ fun:ldb_modules_load
+ fun:ldb_init
+}
+
+
diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_group_audit_errors.c b/source4/dsdb/samdb/ldb_modules/tests/test_group_audit_errors.c
new file mode 100644
index 0000000..ea9f2b7
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/test_group_audit_errors.c
@@ -0,0 +1,266 @@
+/*
+ Unit tests for the dsdb group auditing code in group_audit.c
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
+
+ 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/>.
+*/
+
+/*
+ * These tests exercise the error handling routines.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <cmocka.h>
+
+int ldb_group_audit_log_module_init(const char *version);
+#include "../group_audit.c"
+
+#include "lib/ldb/include/ldb_private.h"
+
+/*
+ * cmocka wrappers for json_new_object
+ */
+struct json_object __wrap_json_new_object(void);
+struct json_object __real_json_new_object(void);
+struct json_object __wrap_json_new_object(void)
+{
+
+ bool use_real = (bool)mock();
+ if (!use_real) {
+ return json_empty_object;
+ }
+ return __real_json_new_object();
+}
+
+/*
+ * cmocka wrappers for json_add_version
+ */
+int __wrap_json_add_version(struct json_object *object, int major, int minor);
+int __real_json_add_version(struct json_object *object, int major, int minor);
+int __wrap_json_add_version(struct json_object *object, int major, int minor)
+{
+
+ int ret = (int)mock();
+ if (ret) {
+ return ret;
+ }
+ return __real_json_add_version(object, major, minor);
+}
+
+/*
+ * cmocka wrappers for json_add_version
+ */
+int __wrap_json_add_timestamp(struct json_object *object);
+int __real_json_add_timestamp(struct json_object *object);
+int __wrap_json_add_timestamp(struct json_object *object)
+{
+
+ int ret = (int)mock();
+ if (ret) {
+ return ret;
+ }
+ return __real_json_add_timestamp(object);
+}
+
+/*
+ * Test helper to add a session id and user SID
+ */
+static void add_session_data(
+ TALLOC_CTX *ctx,
+ struct ldb_context *ldb,
+ const char *session,
+ const char *user_sid)
+{
+ struct auth_session_info *sess = NULL;
+ struct security_token *token = NULL;
+ struct dom_sid *sid = NULL;
+ struct GUID session_id;
+ bool ok;
+
+ sess = talloc_zero(ctx, struct auth_session_info);
+ token = talloc_zero(ctx, struct security_token);
+ sid = talloc_zero(ctx, struct dom_sid);
+ ok = string_to_sid(sid, user_sid);
+ assert_true(ok);
+ token->sids = sid;
+ sess->security_token = token;
+ GUID_from_string(session, &session_id);
+ sess->unique_session_token = session_id;
+ ldb_set_opaque(ldb, DSDB_SESSION_INFO, sess);
+}
+
+/*
+ * Test helper to insert a transaction_id into a request.
+ */
+static void add_transaction_id(struct ldb_request *req, const char *id)
+{
+ struct GUID guid;
+ struct dsdb_control_transaction_identifier *transaction_id = NULL;
+
+ transaction_id = talloc_zero(
+ req,
+ struct dsdb_control_transaction_identifier);
+ assert_non_null(transaction_id);
+ GUID_from_string(id, &guid);
+ transaction_id->transaction_guid = guid;
+ ldb_request_add_control(
+ req,
+ DSDB_CONTROL_TRANSACTION_IDENTIFIER_OID,
+ false,
+ transaction_id);
+}
+
+static void test_audit_group_json(void **state)
+{
+ struct ldb_context *ldb = NULL;
+ struct ldb_module *module = NULL;
+ struct ldb_request *req = NULL;
+
+ struct tsocket_address *ts = NULL;
+
+ const char *const SID = "S-1-5-21-2470180966-3899876309-2637894779";
+ const char * const SESSION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ struct GUID transaction_id;
+ const char *const TRANSACTION = "7130cb06-2062-6a1b-409e-3514c26b1773";
+
+ enum event_id_type event_id = EVT_ID_USER_REMOVED_FROM_GLOBAL_SEC_GROUP;
+
+ struct json_object json;
+
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ ldb = ldb_init(ctx, NULL);
+
+ GUID_from_string(TRANSACTION, &transaction_id);
+
+ module = talloc_zero(ctx, struct ldb_module);
+ module->ldb = ldb;
+
+ tsocket_address_inet_from_strings(ctx, "ip", "127.0.0.1", 0, &ts);
+ ldb_set_opaque(ldb, "remoteAddress", ts);
+
+ add_session_data(ctx, ldb, SESSION, SID);
+
+ req = talloc_zero(ctx, struct ldb_request);
+ req->operation = LDB_ADD;
+ add_transaction_id(req, TRANSACTION);
+
+ /*
+ * Fail on the creation of the audit json object
+ */
+
+ will_return(__wrap_json_new_object, false);
+
+ json = audit_group_json(module,
+ req,
+ "the-action",
+ "the-user-name",
+ "the-group-name",
+ event_id,
+ LDB_ERR_OPERATIONS_ERROR);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail adding the version object .
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, JSON_ERROR);
+
+ json = audit_group_json(module,
+ req,
+ "the-action",
+ "the-user-name",
+ "the-group-name",
+ event_id,
+ LDB_ERR_OPERATIONS_ERROR);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail on creation of the wrapper.
+ */
+
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, false);
+
+ json = audit_group_json(module,
+ req,
+ "the-action",
+ "the-user-name",
+ "the-group-name",
+ event_id,
+ LDB_ERR_OPERATIONS_ERROR);
+ assert_true(json_is_invalid(&json));
+
+ /*
+ * Fail adding the timestamp to the wrapper object.
+ */
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_timestamp, JSON_ERROR);
+
+ json = audit_group_json(module,
+ req,
+ "the-action",
+ "the-user-name",
+ "the-group-name",
+ event_id,
+ LDB_ERR_OPERATIONS_ERROR);
+ assert_true(json_is_invalid(&json));
+
+
+ /*
+ * Now test the happy path
+ */
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_version, 0);
+ will_return(__wrap_json_new_object, true);
+ will_return(__wrap_json_add_timestamp, 0);
+
+ json = audit_group_json(module,
+ req,
+ "the-action",
+ "the-user-name",
+ "the-group-name",
+ event_id,
+ LDB_ERR_OPERATIONS_ERROR);
+ assert_false(json_is_invalid(&json));
+
+ json_free(&json);
+ TALLOC_FREE(ctx);
+
+}
+
+/*
+ * Note: to run under valgrind us:
+ * valgrind --suppressions=test_group_audit.valgrind bin/test_group_audit
+ * This suppresses the errors generated because the ldb_modules are not
+ * de-registered.
+ *
+ */
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_audit_group_json),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/tests/test_unique_object_sids.c b/source4/dsdb/samdb/ldb_modules/tests/test_unique_object_sids.c
new file mode 100644
index 0000000..f9065e4
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tests/test_unique_object_sids.c
@@ -0,0 +1,514 @@
+/*
+ Unit tests for the unique objectSID code in unique_object_sids.c
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
+
+ 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 <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <unistd.h>
+#include <cmocka.h>
+
+int ldb_unique_object_sids_init(const char *version);
+#include "../unique_object_sids.c"
+
+#include "../libcli/security/dom_sid.h"
+#include "librpc/gen_ndr/ndr_security.h"
+
+#define TEST_BE "tdb"
+
+#define DOMAIN_SID "S-1-5-21-2470180966-3899876309-2637894779"
+#define LOCAL_SID "S-1-5-21-2470180966-3899876309-2637894779-1000"
+#define FOREIGN_SID "S-1-5-21-2470180966-3899876309-2637894778-1000"
+
+static struct ldb_request *last_request;
+
+/*
+ * ldb_next_request mock, records the request passed in last_request
+ * so it can be examined in the test cases.
+ */
+int ldb_next_request(
+ struct ldb_module *module,
+ struct ldb_request *request)
+{
+ last_request = request;
+ return ldb_module_done(request, NULL, NULL, LDB_SUCCESS);
+}
+
+/*
+ * Test context
+ */
+struct ldbtest_ctx {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+ struct ldb_module *module;
+
+ const char *dbfile;
+ const char *lockfile; /* lockfile is separate */
+
+ const char *dbpath;
+ struct dom_sid *domain_sid;
+};
+
+/*
+ * Remove any database files created by the tests
+ */
+static void unlink_old_db(struct ldbtest_ctx *test_ctx)
+{
+ int ret;
+
+ errno = 0;
+ ret = unlink(test_ctx->lockfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+
+ errno = 0;
+ ret = unlink(test_ctx->dbfile);
+ if (ret == -1 && errno != ENOENT) {
+ fail();
+ }
+}
+
+/*
+ * Empty module to signal the end of the module list
+ */
+static const struct ldb_module_ops eol_ops = {
+ .name = "eol",
+ .search = NULL,
+ .add = NULL,
+ .modify = NULL,
+ .del = NULL,
+ .rename = NULL,
+ .init_context = NULL
+};
+
+/*
+ * Test set up
+ */
+static int setup(void **state)
+{
+ struct ldbtest_ctx *test_ctx = NULL;
+ struct ldb_module *eol = NULL;
+ int rc;
+
+ test_ctx = talloc_zero(NULL, struct ldbtest_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->ev = tevent_context_init(test_ctx);
+ assert_non_null(test_ctx->ev);
+
+ test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
+ assert_non_null(test_ctx->ldb);
+
+ test_ctx->domain_sid = talloc_zero(test_ctx, struct dom_sid);
+ assert_non_null(test_ctx->domain_sid);
+ assert_true(string_to_sid(test_ctx->domain_sid, DOMAIN_SID));
+ ldb_set_opaque(test_ctx->ldb, "cache.domain_sid", test_ctx->domain_sid);
+
+ test_ctx->module = ldb_module_new(
+ test_ctx,
+ test_ctx->ldb,
+ "unique_object_sids",
+ &ldb_unique_object_sids_module_ops);
+ assert_non_null(test_ctx->module);
+ eol = ldb_module_new(test_ctx, test_ctx->ldb, "eol", &eol_ops);
+ assert_non_null(eol);
+ ldb_module_set_next(test_ctx->module, eol);
+
+ test_ctx->dbfile = talloc_strdup(test_ctx, "duptest.ldb");
+ assert_non_null(test_ctx->dbfile);
+
+ test_ctx->lockfile = talloc_asprintf(test_ctx, "%s-lock",
+ test_ctx->dbfile);
+ assert_non_null(test_ctx->lockfile);
+
+ test_ctx->dbpath = talloc_asprintf(test_ctx,
+ TEST_BE"://%s", test_ctx->dbfile);
+ assert_non_null(test_ctx->dbpath);
+
+ unlink_old_db(test_ctx);
+
+ rc = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, NULL);
+ assert_int_equal(rc, LDB_SUCCESS);
+
+ rc = unique_object_sids_init(test_ctx->module);
+ assert_int_equal(rc, LDB_SUCCESS);
+
+ *state = test_ctx;
+
+ last_request = NULL;
+ return 0;
+}
+
+/*
+ * Test clean up
+ */
+static int teardown(void **state)
+{
+ struct ldbtest_ctx *test_ctx = talloc_get_type_abort(*state,
+ struct ldbtest_ctx);
+
+ unlink_old_db(test_ctx);
+ talloc_free(test_ctx);
+ return 0;
+}
+
+/*
+ * Add an objectSID in string form to the supplied message
+ *
+ *
+ */
+static void add_sid(
+ struct ldb_message *msg,
+ const char *sid_str)
+{
+ struct ldb_val v;
+ enum ndr_err_code ndr_err;
+ struct dom_sid *sid = NULL;
+
+ sid = talloc_zero(msg, struct dom_sid);
+ assert_non_null(sid);
+ assert_true(string_to_sid(sid, sid_str));
+ ndr_err = ndr_push_struct_blob(&v, msg, sid,
+ (ndr_push_flags_fn_t)ndr_push_dom_sid);
+ assert_true(NDR_ERR_CODE_IS_SUCCESS(ndr_err));
+ assert_int_equal(0, ldb_msg_add_value(msg, "objectSID", &v, NULL));
+}
+
+/*
+ * The object is in the current local domain so it should have
+ * DB_FLAG_INTERNAL_UNIQUE_VALUE set
+ */
+static void test_objectSID_in_domain(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct ldb_context *ldb = test_ctx->ldb;
+ struct ldb_message *msg = ldb_msg_new(test_ctx);
+ struct ldb_message_element *el = NULL;
+ struct ldb_request *request = NULL;
+ struct ldb_request *original_request = NULL;
+ int rc;
+
+ msg->dn = ldb_dn_new(msg, ldb, "dc=test");
+ add_sid(msg, LOCAL_SID);
+
+ rc = ldb_build_add_req(
+ &request,
+ test_ctx->ldb,
+ test_ctx,
+ msg,
+ NULL,
+ NULL,
+ ldb_op_default_callback,
+ NULL);
+
+ assert_int_equal(rc, LDB_SUCCESS);
+ assert_non_null(request);
+ original_request = request;
+
+ rc = unique_object_sids_add(test_ctx->module, request);
+ assert_int_equal(rc, LDB_SUCCESS);
+
+ /*
+ * Check that a copy of the request was passed to the next module
+ * and not the original request
+ */
+ assert_ptr_not_equal(last_request, original_request);
+
+ /*
+ * Check the flag was set on the request passed to the next
+ * module
+ */
+ el = ldb_msg_find_element(last_request->op.add.message, "objectSID");
+ assert_non_null(el);
+ assert_true(el->flags & LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX);
+
+ /*
+ * Check the flag was not set on the original request
+ */
+ el = ldb_msg_find_element(request->op.add.message, "objectSID");
+ assert_non_null(el);
+ assert_false(el->flags & LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX);
+
+}
+
+/*
+ * The object is not in the current local domain so it should NOT have
+ * DB_FLAG_INTERNAL_UNIQUE_VALUE set
+ */
+static void test_objectSID_not_in_domain(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct ldb_context *ldb = test_ctx->ldb;
+ struct ldb_message *msg = ldb_msg_new(test_ctx);
+ struct ldb_message_element *el = NULL;
+ struct ldb_request *request = NULL;
+ struct ldb_request *original_request = NULL;
+ int rc;
+
+ msg->dn = ldb_dn_new(msg, ldb, "dc=test");
+ add_sid(msg, FOREIGN_SID);
+
+ rc = ldb_build_add_req(
+ &request,
+ test_ctx->ldb,
+ test_ctx,
+ msg,
+ NULL,
+ NULL,
+ ldb_op_default_callback,
+ NULL);
+
+ assert_int_equal(rc, LDB_SUCCESS);
+ assert_non_null(request);
+ original_request = request;
+
+ rc = unique_object_sids_add(test_ctx->module, request);
+ assert_int_equal(rc, LDB_SUCCESS);
+
+ /*
+ * Check that the original request was passed to the next module
+ * and not a copy
+ */
+ assert_ptr_equal(last_request, original_request);
+
+ /*
+ * Check that the flag was not set on the objectSID element
+ */
+ el = ldb_msg_find_element(msg, "objectSID");
+ assert_non_null(el);
+ assert_false(el->flags & LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX);
+}
+
+/*
+ * No objectSID on the record so it should pass through the module untouched
+ *
+ */
+static void test_no_objectSID(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct ldb_context *ldb = test_ctx->ldb;
+ struct ldb_message *msg = ldb_msg_new(test_ctx);
+ struct ldb_request *request = NULL;
+ struct ldb_request *original_request = NULL;
+ int rc;
+
+ msg->dn = ldb_dn_new(msg, ldb, "dc=test");
+ assert_int_equal(LDB_SUCCESS, ldb_msg_add_string(msg, "cn", "test"));
+
+ rc = ldb_build_add_req(
+ &request,
+ test_ctx->ldb,
+ test_ctx,
+ msg,
+ NULL,
+ NULL,
+ ldb_op_default_callback,
+ NULL);
+
+ assert_int_equal(rc, LDB_SUCCESS);
+ assert_non_null(request);
+ original_request = request;
+
+ rc = unique_object_sids_add(test_ctx->module, request);
+ assert_int_equal(rc, LDB_SUCCESS);
+
+ /*
+ * Check that the original request was passed to the next module
+ * and not a copy
+ */
+ assert_ptr_equal(last_request, original_request);
+
+}
+
+/*
+ * Attempt to modify an objectSID DSDB_CONTROL_REPLICATED_UPDATE_OID not set
+ * this should fail with LDB_ERR_UNWILLING_TO_PERFORM
+ */
+static void test_modify_of_objectSID_not_replicated(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct ldb_context *ldb = test_ctx->ldb;
+ struct ldb_message *msg = ldb_msg_new(test_ctx);
+ struct ldb_request *request = NULL;
+ int rc;
+
+ msg->dn = ldb_dn_new(msg, ldb, "dc=test");
+ add_sid(msg, LOCAL_SID);
+
+ rc = ldb_build_mod_req(
+ &request,
+ test_ctx->ldb,
+ test_ctx,
+ msg,
+ NULL,
+ NULL,
+ ldb_op_default_callback,
+ NULL);
+
+ assert_int_equal(rc, LDB_SUCCESS);
+ assert_non_null(request);
+
+ rc = unique_object_sids_modify(test_ctx->module, request);
+
+ assert_int_equal(rc, LDB_ERR_UNWILLING_TO_PERFORM);
+}
+
+
+/*
+ * Attempt to modify an objectSID DSDB_CONTROL_REPLICATED_UPDATE_OID set
+ * this should succeed
+ */
+static void test_modify_of_objectSID_replicated(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct ldb_context *ldb = test_ctx->ldb;
+ struct ldb_message *msg = ldb_msg_new(test_ctx);
+ struct ldb_message_element *el = NULL;
+ struct ldb_request *request = NULL;
+ struct ldb_request *original_request = NULL;
+ int rc;
+
+ msg->dn = ldb_dn_new(msg, ldb, "dc=test");
+ add_sid(msg, LOCAL_SID);
+
+ rc = ldb_build_mod_req(
+ &request,
+ test_ctx->ldb,
+ test_ctx,
+ msg,
+ NULL,
+ NULL,
+ ldb_op_default_callback,
+ NULL);
+ assert_int_equal(rc, LDB_SUCCESS);
+ assert_non_null(request);
+ original_request = request;
+
+ rc = ldb_request_add_control(
+ request,
+ DSDB_CONTROL_REPLICATED_UPDATE_OID,
+ false,
+ NULL);
+ assert_int_equal(rc, LDB_SUCCESS);
+
+ rc = unique_object_sids_modify(test_ctx->module, request);
+
+ assert_int_equal(rc, LDB_SUCCESS);
+
+ /*
+ * Check that a copy of the request was passed to the next module
+ * and not the original request
+ */
+ assert_ptr_not_equal(last_request, original_request);
+
+ /*
+ * Check the flag was set on the request passed to the next
+ * module
+ */
+ el = ldb_msg_find_element(last_request->op.add.message, "objectSID");
+ assert_non_null(el);
+ assert_true(el->flags & LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX);
+
+ /*
+ * Check the flag was not set on the original request
+ */
+ el = ldb_msg_find_element(request->op.add.message, "objectSID");
+ assert_non_null(el);
+ assert_false(el->flags & LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX);
+
+}
+
+/*
+ * Test the a modify with no object SID is passed through correctly
+ *
+ */
+static void test_modify_no_objectSID(void **state)
+{
+ struct ldbtest_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct ldbtest_ctx);
+ struct ldb_context *ldb = test_ctx->ldb;
+ struct ldb_message *msg = ldb_msg_new(test_ctx);
+ struct ldb_request *request = NULL;
+ struct ldb_request *original_request = NULL;
+ int rc;
+
+ msg->dn = ldb_dn_new(msg, ldb, "dc=test");
+ assert_int_equal(LDB_SUCCESS, ldb_msg_add_string(msg, "cn", "test"));
+
+ rc = ldb_build_mod_req(
+ &request,
+ test_ctx->ldb,
+ test_ctx,
+ msg,
+ NULL,
+ NULL,
+ ldb_op_default_callback,
+ NULL);
+
+ assert_int_equal(rc, LDB_SUCCESS);
+ assert_non_null(request);
+ original_request = request;
+
+ rc = unique_object_sids_modify(test_ctx->module, request);
+ assert_int_equal(rc, LDB_SUCCESS);
+
+ /*
+ * Check that the original request was passed to the next module
+ * and not a copy
+ */
+ assert_ptr_equal(last_request, original_request);
+
+}
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(
+ test_objectSID_in_domain,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_objectSID_not_in_domain,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_no_objectSID,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_modify_no_objectSID,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_modify_of_objectSID_not_replicated,
+ setup,
+ teardown),
+ cmocka_unit_test_setup_teardown(
+ test_modify_of_objectSID_replicated,
+ setup,
+ teardown),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c
new file mode 100644
index 0000000..99c5955
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c
@@ -0,0 +1,423 @@
+/*
+ ldb database library
+
+ Copyright (C) Kamen Mazdrashki <kamenim@samba.org> 2014
+
+ 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: tombstone_reanimate
+ *
+ * Component: Handle Tombstone reanimation requests
+ *
+ * Description:
+ * Tombstone reanimation requests are plain ldap modify request like:
+ * dn: CN=tombi 1\0ADEL:e6e17ff7-8986-4cdd-87ad-afb683ccbb89,CN=Deleted Objects,DC=samba4,DC=devel
+ * changetype: modify
+ * delete: isDeleted
+ * -
+ * replace: distinguishedName
+ * distinguishedName: CN=Tombi 1,CN=Users,DC=samba4,DC=devel
+ * -
+ *
+ * Usually we don't allow distinguishedName modifications (see rdn_name.c)
+ * Reanimating Tombstones is described here:
+ * - http://msdn.microsoft.com/en-us/library/cc223467.aspx
+ *
+ * Author: Kamen Mazdrashki
+ */
+
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/samdb.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"
+#include "../libds/common/flags.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "libds/common/flag_mapping.h"
+
+struct tr_context {
+ struct ldb_module *module;
+
+ struct ldb_request *req;
+ const struct ldb_message *req_msg;
+
+ struct ldb_result *search_res;
+ const struct ldb_message *search_msg;
+
+ struct ldb_message *mod_msg;
+ struct ldb_result *mod_res;
+ struct ldb_request *mod_req;
+
+ struct ldb_dn *rename_dn;
+ struct ldb_result *rename_res;
+ struct ldb_request *rename_req;
+
+ const struct dsdb_schema *schema;
+};
+
+static struct tr_context *tr_init_context(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct tr_context *ac;
+
+ ac = talloc_zero(req, struct tr_context);
+ if (ac == NULL) {
+ ldb_oom(ldb);
+ return NULL;
+ }
+
+ ac->module = module;
+ ac->req = req;
+ ac->req_msg = req->op.mod.message;
+ ac->schema = dsdb_get_schema(ldb, ac);
+
+ return ac;
+}
+
+
+static bool is_tombstone_reanimate_request(struct ldb_request *req,
+ const struct ldb_message_element **pel_dn)
+{
+ struct ldb_message_element *el_dn;
+ struct ldb_message_element *el_deleted;
+
+ /* check distinguishedName requirement */
+ el_dn = ldb_msg_find_element(req->op.mod.message, "distinguishedName");
+ if (el_dn == NULL) {
+ return false;
+ }
+ if (LDB_FLAG_MOD_TYPE(el_dn->flags) != LDB_FLAG_MOD_REPLACE) {
+ return false;
+ }
+ if (el_dn->num_values != 1) {
+ return false;
+ }
+
+ /* check isDeleted requirement */
+ el_deleted = ldb_msg_find_element(req->op.mod.message, "isDeleted");
+ if (el_deleted == NULL) {
+ return false;
+ }
+
+ if (LDB_FLAG_MOD_TYPE(el_deleted->flags) != LDB_FLAG_MOD_DELETE) {
+ return false;
+ }
+
+ *pel_dn = el_dn;
+ return true;
+}
+
+/**
+ * Local rename implementation based on dsdb_module_rename()
+ * so we could fine tune it and add more controls
+ */
+static int tr_prepare_rename(struct tr_context *ac,
+ const struct ldb_message_element *new_dn)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ int ret;
+
+ ac->rename_dn = ldb_dn_from_ldb_val(ac, ldb, &new_dn->values[0]);
+ if (ac->rename_dn == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ ac->rename_res = talloc_zero(ac, struct ldb_result);
+ if (ac->rename_res == NULL) {
+ return ldb_module_oom(ac->module);
+ }
+
+ ret = ldb_build_rename_req(&ac->rename_req, ldb, ac,
+ ac->req_msg->dn,
+ ac->rename_dn,
+ NULL,
+ ac->rename_res,
+ ldb_modify_default_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(ac->rename_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * Local rename implementation based on dsdb_module_modify()
+ * so we could fine tune it and add more controls
+ */
+static int tr_do_down_req(struct tr_context *ac, struct ldb_request *down_req)
+{
+ int ret;
+
+ /* We need this since object is 'delete' atm */
+ ret = ldb_request_add_control(down_req,
+ LDB_CONTROL_SHOW_DELETED_OID,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* mark request as part of Tombstone reanimation */
+ ret = ldb_request_add_control(down_req,
+ DSDB_CONTROL_RESTORE_TOMBSTONE_OID,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Run request from Next module */
+ ret = ldb_next_request(ac->module, down_req);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(down_req->handle, LDB_WAIT_ALL);
+ }
+
+ return ret;
+}
+
+static int tr_prepare_attributes(struct tr_context *ac)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(ac->module);
+ int ret;
+ struct ldb_message_element *el = NULL;
+ uint32_t account_type, user_account_control;
+ struct ldb_dn *objectcategory = NULL;
+
+ ac->mod_msg = ldb_msg_copy_shallow(ac, ac->req_msg);
+ if (ac->mod_msg == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ac->mod_res = talloc_zero(ac, struct ldb_result);
+ if (ac->mod_res == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ret = ldb_build_mod_req(&ac->mod_req, ldb, ac,
+ ac->mod_msg,
+ NULL,
+ ac->mod_res,
+ ldb_modify_default_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(ac->mod_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* - remove distinguishedName - we don't need it */
+ ldb_msg_remove_attr(ac->mod_msg, "distinguishedName");
+
+ /* remove isRecycled */
+ ret = ldb_msg_add_empty(ac->mod_msg, "isRecycled",
+ LDB_FLAG_MOD_DELETE, NULL);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Failed to reset isRecycled attribute: %s", ldb_strerror(ret));
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* objectClass is USER */
+ if (samdb_find_attribute(ldb, ac->search_msg, "objectclass", "user") != NULL) {
+ uint32_t primary_group_rid;
+ /* restoring 'user' instance attribute is heavily borrowed from samldb.c */
+
+ /* Default values */
+ ret = dsdb_user_obj_set_defaults(ldb, ac->mod_msg, ac->mod_req);
+ if (ret != LDB_SUCCESS) return ret;
+
+ /* "userAccountControl" must exists on deleted object */
+ user_account_control = ldb_msg_find_attr_as_uint(ac->search_msg,
+ "userAccountControl",
+ (uint32_t)-1);
+ if (user_account_control == (uint32_t)-1) {
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "reanimate: No 'userAccountControl' attribute found!");
+ }
+
+ /* restore "sAMAccountType" */
+ ret = dsdb_user_obj_set_account_type(ldb, ac->mod_msg,
+ user_account_control, NULL);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* "userAccountControl" -> "primaryGroupID" mapping */
+ ret = dsdb_user_obj_set_primary_group_id(ldb, ac->mod_msg,
+ user_account_control,
+ &primary_group_rid);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ /*
+ * Older AD deployments don't know about the
+ * RODC group
+ */
+ if (primary_group_rid == DOMAIN_RID_READONLY_DCS) {
+ /* TODO: check group exists */
+ }
+
+ }
+
+ /* objectClass is GROUP */
+ if (samdb_find_attribute(ldb, ac->search_msg, "objectclass", "group") != NULL) {
+ /* "groupType" -> "sAMAccountType" */
+ uint32_t group_type;
+
+ el = ldb_msg_find_element(ac->search_msg, "groupType");
+ if (el == NULL) {
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "reanimate: Unexpected: missing groupType attribute.");
+ }
+
+ group_type = ldb_msg_find_attr_as_uint(ac->search_msg,
+ "groupType", 0);
+
+ account_type = ds_gtype2atype(group_type);
+ if (account_type == 0) {
+ return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM,
+ "reanimate: Unrecognized account type!");
+ }
+ ret = samdb_msg_append_uint(ldb, ac->mod_msg, ac->mod_msg,
+ "sAMAccountType", account_type,
+ LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR,
+ "reanimate: Failed to add sAMAccountType to restored object.");
+ }
+
+ /* Default values set by Windows */
+ ret = samdb_find_or_add_attribute(ldb, ac->mod_msg,
+ "adminCount", "0");
+ if (ret != LDB_SUCCESS) return ret;
+ ret = samdb_find_or_add_attribute(ldb, ac->mod_msg,
+ "operatorCount", "0");
+ if (ret != LDB_SUCCESS) return ret;
+ }
+
+ /* - restore objectCategory if not present */
+ objectcategory = ldb_msg_find_attr_as_dn(ldb, ac, ac->search_msg,
+ "objectCategory");
+ if (objectcategory == NULL) {
+ const char *value;
+
+ ret = dsdb_make_object_category(ldb, ac->schema, ac->search_msg,
+ ac->mod_msg, &value);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_msg_append_string(ac->mod_msg, "objectCategory", value,
+ LDB_FLAG_MOD_ADD);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+/**
+ * Handle special LDAP modify request to restore deleted objects
+ */
+static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ const struct ldb_message_element *el_dn = NULL;
+ struct tr_context *ac = NULL;
+ int ret;
+
+ ldb_debug(ldb, LDB_DEBUG_TRACE, "%s\n", __PRETTY_FUNCTION__);
+
+ /* do not manipulate our control entries */
+ if (ldb_dn_is_special(req->op.mod.message->dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ /* Check if this is a reanimate request */
+ if (!is_tombstone_reanimate_request(req, &el_dn)) {
+ return ldb_next_request(module, req);
+ }
+
+ ac = tr_init_context(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ /* Load original object */
+ ret = dsdb_module_search_dn(module, ac, &ac->search_res,
+ ac->req_msg->dn, NULL,
+ DSDB_FLAG_TOP_MODULE |
+ DSDB_SEARCH_SHOW_DELETED,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ldb_operr(ldb);
+ }
+ ac->search_msg = ac->search_res->msgs[0];
+
+ /* check if it a Deleted Object */
+ if (!ldb_msg_find_attr_as_bool(ac->search_msg, "isDeleted", false)) {
+ return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM, "Trying to restore not deleted object\n");
+ }
+
+ /* Simple implementation */
+
+ /* prepare attributed depending on objectClass */
+ ret = tr_prepare_attributes(ac);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Rename request to modify distinguishedName */
+ ret = tr_prepare_rename(ac, el_dn);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* restore attributed depending on objectClass */
+ ret = tr_do_down_req(ac, ac->mod_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ /* Rename request to modify distinguishedName */
+ ret = tr_do_down_req(ac, ac->rename_req);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_ERROR, "Renaming object to %s has failed with %s\n", el_dn->values[0].data, ldb_strerror(ret));
+ if (ret != LDB_ERR_ENTRY_ALREADY_EXISTS && ret != LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS ) {
+ /* Windows returns Operations Error in case we can't rename the object */
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ return ret;
+ }
+
+ return ldb_module_done(ac->req, NULL, NULL, LDB_SUCCESS);
+}
+
+
+static const struct ldb_module_ops ldb_reanimate_module_ops = {
+ .name = "tombstone_reanimate",
+ .modify = tombstone_reanimate_modify,
+};
+
+int ldb_tombstone_reanimate_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_reanimate_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/unique_object_sids.c b/source4/dsdb/samdb/ldb_modules/unique_object_sids.c
new file mode 100644
index 0000000..f8427bc
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/unique_object_sids.c
@@ -0,0 +1,262 @@
+/*
+ ldb database module to enforce unique local objectSIDs
+
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
+
+ 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/>.
+*/
+
+/*
+
+ Duplicate ObjectSIDs are possible on foreign security principals and
+ replication conflict records. However a duplicate objectSID within
+ the local domainSID is an error.
+
+ As the uniqueness requirement depends on the source domain it is not possible
+ to enforce this with a unique index.
+
+ This module sets the LDB_FLAG_FORCE_UNIQUE_INDEX for objectSIDs in the
+ local domain.
+*/
+
+#include "includes.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/samdb.h"
+#include "libcli/security/dom_sid.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+
+struct private_data {
+ const struct dom_sid *domain_sid;
+};
+
+
+/*
+ * Does the add request contain a local objectSID
+ */
+static bool message_contains_local_objectSID(
+ struct ldb_module *module,
+ const struct ldb_message *msg)
+{
+ struct dom_sid *objectSID = NULL;
+
+ struct private_data *data =
+ talloc_get_type(
+ ldb_module_get_private(module),
+ struct private_data);
+
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ objectSID = samdb_result_dom_sid(frame, msg, "objectSID");
+ if (objectSID == NULL) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ /*
+ * data->domain_sid can be NULL but dom_sid_in_domain handles this
+ * case correctly. See unique_object_sids_init for more details.
+ */
+ if (!dom_sid_in_domain(data->domain_sid, objectSID)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ TALLOC_FREE(frame);
+ return true;
+}
+
+static int flag_objectSID(
+ struct ldb_module *module,
+ struct ldb_request *req,
+ const struct ldb_message *msg,
+ struct ldb_message **new_msg)
+{
+ struct ldb_message_element *el = NULL;
+
+ *new_msg = ldb_msg_copy_shallow(req, msg);
+ if (!*new_msg) {
+ return ldb_module_oom(module);
+ }
+
+ el = ldb_msg_find_element(*new_msg, "objectSID");
+ if (el == NULL) {
+ struct ldb_context *ldb = NULL;
+ ldb = ldb_module_get_ctx(module);
+ ldb_asprintf_errstring(
+ ldb,
+ "Unable to locate objectSID in copied request\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ el->flags |= LDB_FLAG_INTERNAL_FORCE_UNIQUE_INDEX;
+ return LDB_SUCCESS;
+}
+
+/* add */
+static int unique_object_sids_add(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+ const struct ldb_message *msg = req->op.add.message;
+ struct ldb_message *new_msg = NULL;
+ struct ldb_request *new_req = NULL;
+ struct ldb_context *ldb = NULL;
+ int rc;
+
+ if (!message_contains_local_objectSID(module, msg)) {
+ /*
+ * Request does not contain a local objectSID so chain the
+ * next module
+ */
+ return ldb_next_request(module, req);
+ }
+
+ /*
+ * The add request contains an objectSID for the local domain
+ */
+
+ rc = flag_objectSID(module, req, msg, &new_msg);
+ if (rc != LDB_SUCCESS) {
+ return rc;
+ }
+
+ ldb = ldb_module_get_ctx(module);
+ rc = ldb_build_add_req(
+ &new_req,
+ ldb,
+ req,
+ new_msg,
+ req->controls,
+ req,
+ dsdb_next_callback,
+ req);
+ if (rc != LDB_SUCCESS) {
+ return rc;
+ }
+
+ return ldb_next_request(module, new_req);
+}
+
+/* modify */
+static int unique_object_sids_modify(
+ struct ldb_module *module,
+ struct ldb_request *req)
+{
+
+ const struct ldb_message *msg = req->op.mod.message;
+ struct ldb_message *new_msg = NULL;
+ struct ldb_request *new_req = NULL;
+ struct ldb_context *ldb = NULL;
+ int rc;
+
+ if (!message_contains_local_objectSID(module, msg)) {
+ /*
+ * Request does not contain a local objectSID so chain the
+ * next module
+ */
+ return ldb_next_request(module, req);
+ }
+
+ ldb = ldb_module_get_ctx(module);
+
+ /*
+ * If DSDB_CONTROL_REPLICATED_UPDATE_OID replicated is set we know
+ * that the modify request is well formed and objectSID only appears
+ * once.
+ *
+ * Enforcing this assumption simplifies the subsequent code.
+ *
+ */
+ if(!ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
+ ldb_asprintf_errstring(
+ ldb,
+ "Modify of %s rejected, "
+ "as it is modifying an objectSID\n",
+ ldb_dn_get_linearized(msg->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+
+ rc = flag_objectSID(module, req, msg, &new_msg);
+ if (rc != LDB_SUCCESS) {
+ return rc;
+ }
+
+ ldb = ldb_module_get_ctx(module);
+ rc = ldb_build_mod_req(
+ &new_req,
+ ldb,
+ req,
+ new_msg,
+ req->controls,
+ req,
+ dsdb_next_callback,
+ req);
+ if (rc != LDB_SUCCESS) {
+ return rc;
+ }
+
+ return ldb_next_request(module, new_req);
+}
+
+/* init */
+static int unique_object_sids_init(
+ struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct private_data *data = NULL;
+ int ret;
+
+ ret = ldb_next_init(module);
+
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ data = talloc_zero(module, struct private_data);
+ if (!data) {
+ return ldb_module_oom(module);
+ }
+
+ data->domain_sid = samdb_domain_sid(ldb);
+ if (data->domain_sid == NULL) {
+ /*
+ * Unable to determine the domainSID, this normally occurs
+ * when provisioning. As there is no easy way to detect
+ * that we are provisioning. We currently just log this as a
+ * warning.
+ */
+ ldb_debug(
+ ldb,
+ LDB_DEBUG_WARNING,
+ "Unable to determine the DomainSID, "
+ "can not enforce uniqueness constraint on local "
+ "domainSIDs\n");
+ }
+
+ ldb_module_set_private(module, data);
+
+ return LDB_SUCCESS;
+}
+
+static const struct ldb_module_ops ldb_unique_object_sids_module_ops = {
+ .name = "unique_object_sids",
+ .init_context = unique_object_sids_init,
+ .add = unique_object_sids_add,
+ .modify = unique_object_sids_modify,
+};
+
+int ldb_unique_object_sids_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_unique_object_sids_module_ops);
+}
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 0000000..780eb81
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/update_keytab.c
@@ -0,0 +1,510 @@
+/*
+ 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_module.h"
+#include "lib/util/dlinklist.h"
+#include "auth/credentials/credentials.h"
+#include "auth/credentials/credentials_krb5.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "auth/kerberos/kerberos_srv_keytab.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "param/secrets.h"
+
+struct dn_list {
+ struct ldb_message *msg;
+ bool do_delete;
+ struct dn_list *prev, *next;
+};
+
+struct update_kt_private {
+ struct dn_list *changed_dns;
+};
+
+struct update_kt_ctx {
+ struct ldb_module *module;
+ struct ldb_request *req;
+
+ struct ldb_dn *dn;
+ bool do_delete;
+
+ struct ldb_reply *op_reply;
+ bool found;
+};
+
+static struct update_kt_ctx *update_kt_ctx_init(struct ldb_module *module,
+ struct ldb_request *req)
+{
+ struct update_kt_ctx *ac;
+
+ ac = talloc_zero(req, struct update_kt_ctx);
+ if (ac == NULL) {
+ ldb_oom(ldb_module_get_ctx(module));
+ return NULL;
+ }
+
+ ac->module = module;
+ ac->req = req;
+
+ return ac;
+}
+
+/* FIXME: too many semi-async searches here for my taste, direct and indirect as
+ * cli_credentials_set_secrets() performs a sync ldb search.
+ * Just hope we are lucky and nothing breaks (using the tdb backend masks a lot
+ * of async issues). -SSS
+ */
+static int add_modified(struct ldb_module *module, struct ldb_dn *dn, bool do_delete,
+ struct ldb_request *parent)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct update_kt_private *data = talloc_get_type(ldb_module_get_private(module), struct update_kt_private);
+ struct dn_list *item;
+ char *filter;
+ struct ldb_result *res;
+ int ret;
+
+ filter = talloc_asprintf(data,
+ "(&(objectClass=kerberosSecret)(privateKeytab=*))");
+ if (!filter) {
+ return ldb_oom(ldb);
+ }
+
+ ret = dsdb_module_search(module, data, &res,
+ dn, LDB_SCOPE_BASE, NULL,
+ DSDB_FLAG_NEXT_MODULE, parent,
+ "%s", filter);
+ talloc_free(filter);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (res->count != 1) {
+ /* if it's not a kerberosSecret then we don't have anything to update */
+ talloc_free(res);
+ return LDB_SUCCESS;
+ }
+
+ item = talloc(data->changed_dns? (void *)data->changed_dns: (void *)data, struct dn_list);
+ if (!item) {
+ talloc_free(res);
+ return ldb_oom(ldb);
+ }
+
+ item->msg = talloc_steal(item, res->msgs[0]);
+ item->do_delete = do_delete;
+ talloc_free(res);
+
+ DLIST_ADD_END(data->changed_dns, item);
+ return LDB_SUCCESS;
+}
+
+static int ukt_search_modified(struct update_kt_ctx *ac);
+
+static int update_kt_op_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct ldb_context *ldb;
+ struct update_kt_ctx *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct update_kt_ctx);
+ ldb = ldb_module_get_ctx(ac->module);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ if (ares->type != LDB_REPLY_DONE) {
+ ldb_set_errstring(ldb, "Invalid request type!\n");
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ac->do_delete) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, LDB_SUCCESS);
+ }
+
+ ac->op_reply = talloc_steal(ac, ares);
+
+ ret = ukt_search_modified(ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int ukt_del_op(struct update_kt_ctx *ac)
+{
+ struct ldb_context *ldb;
+ struct ldb_request *down_req;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ ret = ldb_build_del_req(&down_req, ldb, ac,
+ ac->dn,
+ ac->req->controls,
+ ac, update_kt_op_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(ac->module, down_req);
+}
+
+static int ukt_search_modified_callback(struct ldb_request *req,
+ struct ldb_reply *ares)
+{
+ struct update_kt_ctx *ac;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct update_kt_ctx);
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+
+ ac->found = true;
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ /* ignore */
+ break;
+
+ case LDB_REPLY_DONE:
+
+ if (ac->found) {
+ /* do the dirty sync job here :/ */
+ ret = add_modified(ac->module, ac->dn, ac->do_delete, ac->req);
+ }
+
+ if (ac->do_delete) {
+ ret = ukt_del_op(ac);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req,
+ NULL, NULL, ret);
+ }
+ break;
+ }
+
+ return ldb_module_done(ac->req, ac->op_reply->controls,
+ ac->op_reply->response, LDB_SUCCESS);
+ }
+
+ talloc_free(ares);
+ return LDB_SUCCESS;
+}
+
+static int ukt_search_modified(struct update_kt_ctx *ac)
+{
+ struct ldb_context *ldb;
+ static const char * const no_attrs[] = { NULL };
+ struct ldb_request *search_req;
+ int ret;
+
+ ldb = ldb_module_get_ctx(ac->module);
+
+ ret = ldb_build_search_req(&search_req, ldb, ac,
+ ac->dn, LDB_SCOPE_BASE,
+ "(&(objectClass=kerberosSecret)"
+ "(privateKeytab=*))", no_attrs,
+ NULL,
+ ac, ukt_search_modified_callback,
+ ac->req);
+ LDB_REQ_SET_LOCATION(search_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ return ldb_next_request(ac->module, search_req);
+}
+
+
+/* add */
+static int update_kt_add(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct update_kt_ctx *ac;
+ struct ldb_request *down_req;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = update_kt_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ ac->dn = req->op.add.message->dn;
+
+ ret = ldb_build_add_req(&down_req, ldb, ac,
+ req->op.add.message,
+ req->controls,
+ ac, update_kt_op_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, down_req);
+}
+
+/* modify */
+static int update_kt_modify(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct update_kt_ctx *ac;
+ struct ldb_request *down_req;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = update_kt_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ ac->dn = req->op.mod.message->dn;
+
+ ret = ldb_build_mod_req(&down_req, ldb, ac,
+ req->op.mod.message,
+ req->controls,
+ ac, update_kt_op_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, down_req);
+}
+
+/* delete */
+static int update_kt_delete(struct ldb_module *module, struct ldb_request *req)
+{
+ struct update_kt_ctx *ac;
+
+ ac = update_kt_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ ac->dn = req->op.del.dn;
+ ac->do_delete = true;
+
+ return ukt_search_modified(ac);
+}
+
+/* rename */
+static int update_kt_rename(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct update_kt_ctx *ac;
+ struct ldb_request *down_req;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ ac = update_kt_ctx_init(module, req);
+ if (ac == NULL) {
+ return ldb_operr(ldb);
+ }
+
+ ac->dn = req->op.rename.newdn;
+
+ ret = ldb_build_rename_req(&down_req, ldb, ac,
+ req->op.rename.olddn,
+ req->op.rename.newdn,
+ req->controls,
+ ac, update_kt_op_callback,
+ req);
+ LDB_REQ_SET_LOCATION(down_req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ return ldb_next_request(module, down_req);
+}
+
+/* prepare for a commit */
+static int update_kt_prepare_commit(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct update_kt_private *data = talloc_get_type(ldb_module_get_private(module), struct update_kt_private);
+ struct dn_list *p;
+ struct smb_krb5_context *smb_krb5_context;
+ int krb5_ret = smb_krb5_init_context(data,
+ ldb_get_opaque(ldb, "loadparm"),
+ &smb_krb5_context);
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ if (krb5_ret != 0) {
+ ldb_asprintf_errstring(ldb, "Failed to setup krb5_context: %s", error_message(krb5_ret));
+ goto fail;
+ }
+
+ tmp_ctx = talloc_new(data);
+ if (!tmp_ctx) {
+ ldb_oom(ldb);
+ goto fail;
+ }
+
+ for (p=data->changed_dns; p; p = p->next) {
+ const char *error_string;
+ const char *realm;
+ char *upper_realm;
+ struct ldb_message_element *spn_el = ldb_msg_find_element(p->msg, "servicePrincipalName");
+ const char **SPNs = NULL;
+ int num_SPNs = 0;
+ int i;
+
+ realm = ldb_msg_find_attr_as_string(p->msg, "realm", NULL);
+
+ if (spn_el) {
+ upper_realm = strupper_talloc(tmp_ctx, realm);
+ if (!upper_realm) {
+ ldb_oom(ldb);
+ goto fail;
+ }
+
+ num_SPNs = spn_el->num_values;
+ SPNs = talloc_array(tmp_ctx, const char *, num_SPNs);
+ if (!SPNs) {
+ ldb_oom(ldb);
+ goto fail;
+ }
+ for (i = 0; i < num_SPNs; i++) {
+ SPNs[i] = talloc_asprintf(SPNs, "%*.*s@%s",
+ (int)spn_el->values[i].length,
+ (int)spn_el->values[i].length,
+ (const char *)spn_el->values[i].data,
+ upper_realm);
+ if (!SPNs[i]) {
+ ldb_oom(ldb);
+ goto fail;
+ }
+ }
+ }
+
+ krb5_ret = smb_krb5_update_keytab(tmp_ctx, smb_krb5_context->krb5_context,
+ keytab_name_from_msg(tmp_ctx, ldb, p->msg),
+ ldb_msg_find_attr_as_string(p->msg, "samAccountName", NULL),
+ realm, SPNs, num_SPNs,
+ ldb_msg_find_attr_as_string(p->msg, "saltPrincipal", NULL),
+ ldb_msg_find_attr_as_string(p->msg, "secret", NULL),
+ ldb_msg_find_attr_as_string(p->msg, "priorSecret", NULL),
+ ldb_msg_find_attr_as_int(p->msg, "msDS-KeyVersionNumber", 0),
+ (uint32_t)ldb_msg_find_attr_as_int(p->msg, "msDS-SupportedEncryptionTypes", ENC_ALL_TYPES),
+ p->do_delete, NULL, &error_string);
+ if (krb5_ret != 0) {
+ ldb_asprintf_errstring(ldb, "Failed to update keytab from entry %s in %s: %s",
+ ldb_dn_get_linearized(p->msg->dn),
+ (const char *)ldb_get_opaque(ldb, "ldb_url"),
+ error_string);
+ goto fail;
+ }
+ }
+
+ talloc_free(data->changed_dns);
+ data->changed_dns = NULL;
+ talloc_free(tmp_ctx);
+
+ return ldb_next_prepare_commit(module);
+
+fail:
+ talloc_free(data->changed_dns);
+ data->changed_dns = NULL;
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+}
+
+/* end a transaction */
+static int update_kt_del_trans(struct ldb_module *module)
+{
+ struct update_kt_private *data = talloc_get_type(ldb_module_get_private(module), 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 ldb_context *ldb;
+ struct update_kt_private *data;
+
+ ldb = ldb_module_get_ctx(module);
+
+ data = talloc(module, struct update_kt_private);
+ if (data == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ data->changed_dns = NULL;
+
+ ldb_module_set_private(module, data);
+
+ return ldb_next_init(module);
+}
+
+static 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,
+ .prepare_commit = update_kt_prepare_commit,
+ .del_transaction = update_kt_del_trans,
+};
+
+int ldb_update_keytab_module_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_update_keytab_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/util.c b/source4/dsdb/samdb/ldb_modules/util.c
new file mode 100644
index 0000000..c2949f0
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/util.c
@@ -0,0 +1,1890 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+
+ Copyright (C) Andrew Tridgell 2009
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009
+ Copyright (C) Matthieu Patou <mat@matws.net> 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ldb.h"
+#include "ldb_module.h"
+#include "librpc/ndr/libndr.h"
+#include "dsdb/samdb/ldb_modules/util.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
+#include "libcli/security/security.h"
+
+#undef strcasecmp
+
+/*
+ search for attrs on one DN, in the modules below
+ */
+int dsdb_module_search_dn(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_result **_res,
+ struct ldb_dn *basedn,
+ const char * const *attrs,
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
+{
+ int ret;
+ struct ldb_request *req;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_result *res;
+
+ tmp_ctx = talloc_new(mem_ctx);
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ ret = ldb_build_search_req(&req, ldb_module_get_ctx(module), tmp_ctx,
+ basedn,
+ LDB_SCOPE_BASE,
+ NULL,
+ attrs,
+ NULL,
+ res,
+ ldb_search_default_callback,
+ parent);
+ LDB_REQ_SET_LOCATION(req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_request_add_controls(req, dsdb_flags);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(req);
+ }
+
+ /* Run the new request */
+ if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
+ ret = ldb_next_request(module, req);
+ } else if (dsdb_flags & DSDB_FLAG_TOP_MODULE) {
+ ret = ldb_request(ldb_module_get_ctx(module), req);
+ } else {
+ const struct ldb_module_ops *ops = ldb_module_get_ops(module);
+ SMB_ASSERT(dsdb_flags & DSDB_FLAG_OWN_MODULE);
+ ret = ops->search(module, req);
+ }
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (res->count != 1) {
+ /* we may be reading a DB that does not have the 'check base on search' option... */
+ ret = LDB_ERR_NO_SUCH_OBJECT;
+ ldb_asprintf_errstring(ldb_module_get_ctx(module),
+ "dsdb_module_search_dn: did not find base dn %s (%d results)",
+ ldb_dn_get_linearized(basedn), res->count);
+ } else {
+ *_res = talloc_steal(mem_ctx, res);
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+int dsdb_module_search_tree(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_result **_res,
+ struct ldb_dn *basedn,
+ enum ldb_scope scope,
+ struct ldb_parse_tree *tree,
+ const char * const *attrs,
+ int dsdb_flags,
+ struct ldb_request *parent)
+{
+ int ret;
+ struct ldb_request *req;
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_result *res;
+
+ tmp_ctx = talloc_new(mem_ctx);
+
+ /* cross-partitions searches with a basedn break multi-domain support */
+ SMB_ASSERT(basedn == NULL || (dsdb_flags & DSDB_SEARCH_SEARCH_ALL_PARTITIONS) == 0);
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ ret = ldb_build_search_req_ex(&req, ldb_module_get_ctx(module), tmp_ctx,
+ basedn,
+ scope,
+ tree,
+ attrs,
+ NULL,
+ res,
+ ldb_search_default_callback,
+ parent);
+ LDB_REQ_SET_LOCATION(req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_request_add_controls(req, dsdb_flags);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(req);
+ }
+
+ if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
+ ret = ldb_next_request(module, req);
+ } else if (dsdb_flags & DSDB_FLAG_TOP_MODULE) {
+ ret = ldb_request(ldb_module_get_ctx(module), req);
+ } else {
+ const struct ldb_module_ops *ops = ldb_module_get_ops(module);
+ SMB_ASSERT(dsdb_flags & DSDB_FLAG_OWN_MODULE);
+ ret = ops->search(module, req);
+ }
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ if (dsdb_flags & DSDB_SEARCH_ONE_ONLY) {
+ if (res->count == 0) {
+ talloc_free(tmp_ctx);
+ return ldb_error(ldb_module_get_ctx(module), LDB_ERR_NO_SUCH_OBJECT, __func__);
+ }
+ if (res->count != 1) {
+ talloc_free(tmp_ctx);
+ ldb_reset_err_string(ldb_module_get_ctx(module));
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ }
+
+ talloc_free(req);
+ if (ret == LDB_SUCCESS) {
+ *_res = talloc_steal(mem_ctx, res);
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ search for attrs in the modules below
+ */
+int dsdb_module_search(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_result **_res,
+ struct ldb_dn *basedn, enum ldb_scope scope,
+ const char * const *attrs,
+ int dsdb_flags,
+ struct ldb_request *parent,
+ const char *format, ...) _PRINTF_ATTRIBUTE(9, 10)
+{
+ int ret;
+ TALLOC_CTX *tmp_ctx;
+ va_list ap;
+ char *expression;
+ struct ldb_parse_tree *tree;
+
+ /* cross-partitions searches with a basedn break multi-domain support */
+ SMB_ASSERT(basedn == NULL || (dsdb_flags & DSDB_SEARCH_SEARCH_ALL_PARTITIONS) == 0);
+
+ tmp_ctx = talloc_new(mem_ctx);
+
+ if (format) {
+ va_start(ap, format);
+ expression = talloc_vasprintf(tmp_ctx, format, ap);
+ va_end(ap);
+
+ if (!expression) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+ } else {
+ expression = NULL;
+ }
+
+ tree = ldb_parse_tree(tmp_ctx, expression);
+ if (tree == NULL) {
+ talloc_free(tmp_ctx);
+ ldb_set_errstring(ldb_module_get_ctx(module),
+ "Unable to parse search expression");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = dsdb_module_search_tree(module,
+ mem_ctx,
+ _res,
+ basedn,
+ scope,
+ tree,
+ attrs,
+ dsdb_flags,
+ parent);
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ find a DN given a GUID. This searches across all partitions
+ */
+int dsdb_module_dn_by_guid(struct ldb_module *module, TALLOC_CTX *mem_ctx,
+ const struct GUID *guid, struct ldb_dn **dn,
+ struct ldb_request *parent)
+{
+ struct ldb_result *res;
+ const char *attrs[] = { NULL };
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ int ret;
+
+ ret = dsdb_module_search(module, tmp_ctx, &res, NULL, LDB_SCOPE_SUBTREE,
+ attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SEARCH_ALL_PARTITIONS |
+ DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+ parent,
+ "objectGUID=%s", GUID_string(tmp_ctx, guid));
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+ if (res->count == 0) {
+ talloc_free(tmp_ctx);
+ return ldb_error(ldb_module_get_ctx(module), LDB_ERR_NO_SUCH_OBJECT, __func__);
+ }
+ if (res->count != 1) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module), "More than one object found matching objectGUID %s\n",
+ GUID_string(tmp_ctx, guid));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *dn = talloc_steal(mem_ctx, res->msgs[0]->dn);
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+/*
+ find a GUID given a DN.
+ */
+int dsdb_module_guid_by_dn(struct ldb_module *module, struct ldb_dn *dn, struct GUID *guid,
+ struct ldb_request *parent)
+{
+ const char *attrs[] = { NULL };
+ struct ldb_result *res;
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ int ret;
+ NTSTATUS status;
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res, dn, attrs,
+ DSDB_FLAG_NEXT_MODULE |
+ DSDB_SEARCH_SHOW_RECYCLED |
+ DSDB_SEARCH_SHOW_EXTENDED_DN,
+ parent);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb_module_get_ctx(module), "Failed to find GUID for %s",
+ ldb_dn_get_linearized(dn));
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ status = dsdb_get_extended_dn_guid(res->msgs[0]->dn, guid, "GUID");
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+
+/*
+ a ldb_extended request operating on modules below the
+ current module
+
+ Note that this does not automatically start a transaction. If you
+ need a transaction the caller needs to start it as needed.
+ */
+int dsdb_module_extended(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_result **_res,
+ const char* oid, void* data,
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
+{
+ struct ldb_request *req;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct ldb_result *res;
+
+ if (_res != NULL) {
+ (*_res) = NULL;
+ }
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ ret = ldb_build_extended_req(&req, ldb,
+ tmp_ctx,
+ oid,
+ data,
+ NULL,
+ res, ldb_extended_default_callback,
+ parent);
+
+ LDB_REQ_SET_LOCATION(req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_request_add_controls(req, dsdb_flags);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(req);
+ }
+
+ /* Run the new request */
+ if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
+ ret = ldb_next_request(module, req);
+ } else if (dsdb_flags & DSDB_FLAG_TOP_MODULE) {
+ ret = ldb_request(ldb_module_get_ctx(module), req);
+ } else {
+ const struct ldb_module_ops *ops = ldb_module_get_ops(module);
+ SMB_ASSERT(dsdb_flags & DSDB_FLAG_OWN_MODULE);
+ ret = ops->extended(module, req);
+ }
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ if (_res != NULL && ret == LDB_SUCCESS) {
+ (*_res) = talloc_steal(mem_ctx, res);
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+
+/*
+ a ldb_modify request operating on modules below the
+ current module
+ */
+int dsdb_module_modify(struct ldb_module *module,
+ const struct ldb_message *message,
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
+{
+ struct ldb_request *mod_req;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct ldb_result *res;
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ ret = ldb_build_mod_req(&mod_req, ldb, tmp_ctx,
+ message,
+ NULL,
+ res,
+ ldb_modify_default_callback,
+ parent);
+ LDB_REQ_SET_LOCATION(mod_req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_request_add_controls(mod_req, dsdb_flags);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(mod_req);
+ }
+
+ /* Run the new request */
+ if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
+ ret = ldb_next_request(module, mod_req);
+ } else if (dsdb_flags & DSDB_FLAG_TOP_MODULE) {
+ ret = ldb_request(ldb_module_get_ctx(module), mod_req);
+ } else {
+ const struct ldb_module_ops *ops = ldb_module_get_ops(module);
+ SMB_ASSERT(dsdb_flags & DSDB_FLAG_OWN_MODULE);
+ ret = ops->modify(module, mod_req);
+ }
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(mod_req->handle, LDB_WAIT_ALL);
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+
+
+/*
+ a ldb_rename request operating on modules below the
+ current module
+ */
+int dsdb_module_rename(struct ldb_module *module,
+ struct ldb_dn *olddn, struct ldb_dn *newdn,
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
+{
+ struct ldb_request *req;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct ldb_result *res;
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ ret = ldb_build_rename_req(&req, ldb, tmp_ctx,
+ olddn,
+ newdn,
+ NULL,
+ res,
+ ldb_modify_default_callback,
+ parent);
+ LDB_REQ_SET_LOCATION(req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_request_add_controls(req, dsdb_flags);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(req);
+ }
+
+ /* Run the new request */
+ if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
+ ret = ldb_next_request(module, req);
+ } else if (dsdb_flags & DSDB_FLAG_TOP_MODULE) {
+ ret = ldb_request(ldb_module_get_ctx(module), req);
+ } else {
+ const struct ldb_module_ops *ops = ldb_module_get_ops(module);
+ SMB_ASSERT(dsdb_flags & DSDB_FLAG_OWN_MODULE);
+ ret = ops->rename(module, req);
+ }
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ a ldb_add request operating on modules below the
+ current module
+ */
+int dsdb_module_add(struct ldb_module *module,
+ const struct ldb_message *message,
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
+{
+ struct ldb_request *req;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct ldb_result *res;
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb_module_get_ctx(module));
+ }
+
+ ret = ldb_build_add_req(&req, ldb, tmp_ctx,
+ message,
+ NULL,
+ res,
+ ldb_modify_default_callback,
+ parent);
+ LDB_REQ_SET_LOCATION(req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_request_add_controls(req, dsdb_flags);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(req);
+ }
+
+ /* Run the new request */
+ if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
+ ret = ldb_next_request(module, req);
+ } else if (dsdb_flags & DSDB_FLAG_TOP_MODULE) {
+ ret = ldb_request(ldb_module_get_ctx(module), req);
+ } else {
+ const struct ldb_module_ops *ops = ldb_module_get_ops(module);
+ SMB_ASSERT(dsdb_flags & DSDB_FLAG_OWN_MODULE);
+ ret = ops->add(module, req);
+ }
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ a ldb_delete request operating on modules below the
+ current module
+ */
+int dsdb_module_del(struct ldb_module *module,
+ struct ldb_dn *dn,
+ uint32_t dsdb_flags,
+ struct ldb_request *parent)
+{
+ struct ldb_request *req;
+ int ret;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct ldb_result *res;
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ talloc_free(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ ret = ldb_build_del_req(&req, ldb, tmp_ctx,
+ dn,
+ NULL,
+ res,
+ ldb_modify_default_callback,
+ parent);
+ LDB_REQ_SET_LOCATION(req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ ret = dsdb_request_add_controls(req, dsdb_flags);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (dsdb_flags & DSDB_FLAG_TRUSTED) {
+ ldb_req_mark_trusted(req);
+ }
+
+ /* Run the new request */
+ if (dsdb_flags & DSDB_FLAG_NEXT_MODULE) {
+ ret = ldb_next_request(module, req);
+ } else if (dsdb_flags & DSDB_FLAG_TOP_MODULE) {
+ ret = ldb_request(ldb_module_get_ctx(module), req);
+ } else {
+ const struct ldb_module_ops *ops = ldb_module_get_ops(module);
+ SMB_ASSERT(dsdb_flags & DSDB_FLAG_OWN_MODULE);
+ ret = ops->del(module, req);
+ }
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ check if a single valued link has multiple non-deleted values
+
+ This is needed when we will be using the RELAX control to stop
+ ldb_tdb from checking single valued links
+ */
+int dsdb_check_single_valued_link(const struct dsdb_attribute *attr,
+ const struct ldb_message_element *el)
+{
+ bool found_active = false;
+ unsigned int i;
+
+ if (!(attr->ldb_schema_attribute->flags & LDB_ATTR_FLAG_SINGLE_VALUE) ||
+ el->num_values < 2) {
+ return LDB_SUCCESS;
+ }
+
+ for (i=0; i<el->num_values; i++) {
+ if (!dsdb_dn_is_deleted_val(&el->values[i])) {
+ if (found_active) {
+ return LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS;
+ }
+ found_active = true;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+
+int dsdb_check_samba_compatible_feature(struct ldb_module *module,
+ const char *feature,
+ bool *found)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_result *res;
+ static const char *samba_dsdb_attrs[] = {
+ SAMBA_COMPATIBLE_FEATURES_ATTR,
+ NULL
+ };
+ int ret;
+ struct ldb_dn *samba_dsdb_dn = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_new(ldb);
+ if (tmp_ctx == NULL) {
+ *found = false;
+ return ldb_oom(ldb);
+ }
+ *found = false;
+
+ samba_dsdb_dn = ldb_dn_new(tmp_ctx, ldb, "@SAMBA_DSDB");
+ if (samba_dsdb_dn == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return ldb_oom(ldb);
+ }
+
+ ret = dsdb_module_search_dn(module,
+ tmp_ctx,
+ &res,
+ samba_dsdb_dn,
+ samba_dsdb_attrs,
+ DSDB_FLAG_NEXT_MODULE,
+ NULL);
+ if (ret == LDB_SUCCESS) {
+ *found = ldb_msg_check_string_attribute(
+ res->msgs[0],
+ SAMBA_COMPATIBLE_FEATURES_ATTR,
+ feature);
+ } else if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ /* it is not an error not to find it */
+ ret = LDB_SUCCESS;
+ }
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+
+/*
+ check if an optional feature is enabled on our own NTDS DN
+
+ Note that features can be marked as enabled in more than one
+ place. For example, the recyclebin feature is marked as enabled both
+ on the CN=Partitions,CN=Configurration object and on the NTDS DN of
+ each DC in the forest. It seems likely that it is the job of the KCC
+ to propagate between the two
+ */
+int dsdb_check_optional_feature(struct ldb_module *module, struct GUID op_feature_guid, bool *feature_enabled)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_result *res;
+ struct ldb_dn *search_dn;
+ struct GUID search_guid;
+ const char *attrs[] = {"msDS-EnabledFeature", NULL};
+ int ret;
+ unsigned int i;
+ struct ldb_message_element *el;
+ struct ldb_dn *feature_dn;
+
+ tmp_ctx = talloc_new(ldb);
+
+ feature_dn = samdb_ntds_settings_dn(ldb_module_get_ctx(module), tmp_ctx);
+ if (feature_dn == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ *feature_enabled = false;
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res, feature_dn, attrs, DSDB_FLAG_NEXT_MODULE, NULL);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "Could not find the feature object - dn: %s\n",
+ ldb_dn_get_linearized(feature_dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+ if (res->msgs[0]->num_elements > 0) {
+ const char *attrs2[] = {"msDS-OptionalFeatureGUID", NULL};
+
+ el = ldb_msg_find_element(res->msgs[0],"msDS-EnabledFeature");
+
+ for (i=0; i<el->num_values; i++) {
+ search_dn = ldb_dn_from_ldb_val(tmp_ctx, ldb, &el->values[i]);
+
+ ret = dsdb_module_search_dn(module, tmp_ctx, &res,
+ search_dn, attrs2, DSDB_FLAG_NEXT_MODULE, NULL);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb,
+ "Could no find object dn: %s\n",
+ ldb_dn_get_linearized(search_dn));
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ search_guid = samdb_result_guid(res->msgs[0], "msDS-OptionalFeatureGUID");
+
+ if (GUID_equal(&search_guid, &op_feature_guid)) {
+ *feature_enabled = true;
+ break;
+ }
+ }
+ }
+ talloc_free(tmp_ctx);
+ return LDB_SUCCESS;
+}
+
+/*
+ find the NTDS GUID from a computers DN record
+ */
+int dsdb_module_find_ntdsguid_for_computer(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *computer_dn,
+ struct GUID *ntds_guid,
+ struct ldb_request *parent)
+{
+ int ret;
+ struct ldb_dn *dn;
+
+ *ntds_guid = GUID_zero();
+
+ ret = dsdb_module_reference_dn(module, mem_ctx, computer_dn,
+ "serverReferenceBL", &dn, parent);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ if (!ldb_dn_add_child_fmt(dn, "CN=NTDS Settings")) {
+ talloc_free(dn);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = dsdb_module_guid_by_dn(module, dn, ntds_guid, parent);
+ talloc_free(dn);
+ return ret;
+}
+
+/*
+ find a 'reference' DN that points at another object
+ (eg. serverReference, rIDManagerReference etc)
+ */
+int dsdb_module_reference_dn(struct ldb_module *module, TALLOC_CTX *mem_ctx, struct ldb_dn *base,
+ const char *attribute, struct ldb_dn **dn, struct ldb_request *parent)
+{
+ const char *attrs[2];
+ struct ldb_result *res;
+ int ret;
+
+ attrs[0] = attribute;
+ attrs[1] = NULL;
+
+ ret = dsdb_module_search_dn(module, mem_ctx, &res, base, attrs,
+ DSDB_FLAG_NEXT_MODULE | DSDB_SEARCH_SHOW_EXTENDED_DN, parent);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ *dn = ldb_msg_find_attr_as_dn(ldb_module_get_ctx(module),
+ mem_ctx, res->msgs[0], attribute);
+ if (!*dn) {
+ ldb_reset_err_string(ldb_module_get_ctx(module));
+ talloc_free(res);
+ return LDB_ERR_NO_SUCH_ATTRIBUTE;
+ }
+
+ talloc_free(res);
+ return LDB_SUCCESS;
+}
+
+/*
+ find the RID Manager$ DN via the rIDManagerReference attribute in the
+ base DN
+ */
+int dsdb_module_rid_manager_dn(struct ldb_module *module, TALLOC_CTX *mem_ctx, struct ldb_dn **dn,
+ struct ldb_request *parent)
+{
+ return dsdb_module_reference_dn(module, mem_ctx,
+ ldb_get_default_basedn(ldb_module_get_ctx(module)),
+ "rIDManagerReference", dn, parent);
+}
+
+/*
+ used to chain to the callers callback
+ */
+int dsdb_next_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct ldb_request *up_req = talloc_get_type(req->context, struct ldb_request);
+
+ if (!ares) {
+ return ldb_module_done(up_req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+
+ if (ares->error != LDB_SUCCESS || ares->type == LDB_REPLY_DONE) {
+ return ldb_module_done(up_req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ /* Otherwise pass on the callback */
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ return ldb_module_send_entry(up_req, ares->message,
+ ares->controls);
+
+ case LDB_REPLY_REFERRAL:
+ return ldb_module_send_referral(up_req,
+ ares->referral);
+ default:
+ /* Can't happen */
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+}
+
+/*
+ load the uSNHighest and the uSNUrgent attributes from the @REPLCHANGED
+ object for a partition
+ */
+int dsdb_module_load_partition_usn(struct ldb_module *module, struct ldb_dn *dn,
+ uint64_t *uSN, uint64_t *urgent_uSN, struct ldb_request *parent)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_request *req;
+ int ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ struct dsdb_control_current_partition *p_ctrl;
+ struct ldb_result *res;
+
+ res = talloc_zero(tmp_ctx, struct ldb_result);
+ if (!res) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+
+ ret = ldb_build_search_req(&req, ldb, tmp_ctx,
+ ldb_dn_new(tmp_ctx, ldb, "@REPLCHANGED"),
+ LDB_SCOPE_BASE,
+ NULL, NULL,
+ NULL,
+ res, ldb_search_default_callback,
+ parent);
+ LDB_REQ_SET_LOCATION(req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ p_ctrl = talloc(req, struct dsdb_control_current_partition);
+ if (p_ctrl == NULL) {
+ talloc_free(tmp_ctx);
+ return ldb_module_oom(module);
+ }
+ p_ctrl->version = DSDB_CONTROL_CURRENT_PARTITION_VERSION;
+ p_ctrl->dn = dn;
+
+
+ ret = ldb_request_add_control(req,
+ DSDB_CONTROL_CURRENT_PARTITION_OID,
+ false, p_ctrl);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ /* Run the new request */
+ ret = ldb_next_request(module, req);
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ if (ret == LDB_ERR_NO_SUCH_OBJECT || ret == LDB_ERR_INVALID_DN_SYNTAX) {
+ /* it hasn't been created yet, which means
+ an implicit value of zero */
+ *uSN = 0;
+ talloc_free(tmp_ctx);
+ ldb_reset_err_string(ldb);
+ return LDB_SUCCESS;
+ }
+
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ret;
+ }
+
+ if (res->count != 1) {
+ *uSN = 0;
+ if (urgent_uSN) {
+ *urgent_uSN = 0;
+ }
+ } else {
+ *uSN = ldb_msg_find_attr_as_uint64(res->msgs[0], "uSNHighest", 0);
+ if (urgent_uSN) {
+ *urgent_uSN = ldb_msg_find_attr_as_uint64(res->msgs[0], "uSNUrgent", 0);
+ }
+ }
+
+ talloc_free(tmp_ctx);
+
+ return LDB_SUCCESS;
+}
+
+/*
+ save uSNHighest and uSNUrgent attributes in the @REPLCHANGED object for a
+ partition
+ */
+int dsdb_module_save_partition_usn(struct ldb_module *module, struct ldb_dn *dn,
+ uint64_t uSN, uint64_t urgent_uSN,
+ struct ldb_request *parent)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct ldb_request *req;
+ struct ldb_message *msg;
+ struct dsdb_control_current_partition *p_ctrl;
+ int ret;
+ struct ldb_result *res;
+
+ msg = ldb_msg_new(module);
+ if (msg == NULL) {
+ return ldb_module_oom(module);
+ }
+
+ msg->dn = ldb_dn_new(msg, ldb, "@REPLCHANGED");
+ if (msg->dn == NULL) {
+ talloc_free(msg);
+ return ldb_operr(ldb_module_get_ctx(module));
+ }
+
+ res = talloc_zero(msg, struct ldb_result);
+ if (!res) {
+ talloc_free(msg);
+ return ldb_module_oom(module);
+ }
+
+ ret = samdb_msg_add_uint64(ldb, msg, msg, "uSNHighest", uSN);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(msg);
+ return ret;
+ }
+ msg->elements[0].flags = LDB_FLAG_MOD_REPLACE;
+
+ /* urgent_uSN is optional so may not be stored */
+ if (urgent_uSN) {
+ ret = samdb_msg_add_uint64(ldb, msg, msg, "uSNUrgent",
+ urgent_uSN);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(msg);
+ return ret;
+ }
+ msg->elements[1].flags = LDB_FLAG_MOD_REPLACE;
+ }
+
+
+ p_ctrl = talloc(msg, struct dsdb_control_current_partition);
+ if (p_ctrl == NULL) {
+ talloc_free(msg);
+ return ldb_oom(ldb);
+ }
+ p_ctrl->version = DSDB_CONTROL_CURRENT_PARTITION_VERSION;
+ p_ctrl->dn = dn;
+ ret = ldb_build_mod_req(&req, ldb, msg,
+ msg,
+ NULL,
+ res,
+ ldb_modify_default_callback,
+ parent);
+ LDB_REQ_SET_LOCATION(req);
+again:
+ if (ret != LDB_SUCCESS) {
+ talloc_free(msg);
+ return ret;
+ }
+
+ ret = ldb_request_add_control(req,
+ DSDB_CONTROL_CURRENT_PARTITION_OID,
+ false, p_ctrl);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(msg);
+ return ret;
+ }
+
+ /* Run the new request */
+ ret = ldb_next_request(module, req);
+
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ret = ldb_build_add_req(&req, ldb, msg,
+ msg,
+ NULL,
+ res,
+ ldb_modify_default_callback,
+ parent);
+ LDB_REQ_SET_LOCATION(req);
+ goto again;
+ }
+
+ talloc_free(msg);
+
+ return ret;
+}
+
+bool dsdb_module_am_system(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct auth_session_info *session_info
+ = talloc_get_type(
+ ldb_get_opaque(ldb, DSDB_SESSION_INFO),
+ struct auth_session_info);
+ return security_session_user_level(session_info, NULL) == SECURITY_SYSTEM;
+}
+
+bool dsdb_module_am_administrator(struct ldb_module *module)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct auth_session_info *session_info
+ = talloc_get_type(
+ ldb_get_opaque(ldb, DSDB_SESSION_INFO),
+ struct auth_session_info);
+ return security_session_user_level(session_info, NULL) == SECURITY_ADMINISTRATOR;
+}
+
+/*
+ check if the recyclebin is enabled
+ */
+int dsdb_recyclebin_enabled(struct ldb_module *module, bool *enabled)
+{
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ struct GUID recyclebin_guid;
+ int ret;
+
+ GUID_from_string(DS_GUID_FEATURE_RECYCLE_BIN, &recyclebin_guid);
+
+ ret = dsdb_check_optional_feature(module, recyclebin_guid, enabled);
+ if (ret != LDB_SUCCESS) {
+ ldb_asprintf_errstring(ldb, "Could not verify if Recycle Bin is enabled \n");
+ return ret;
+ }
+
+ return LDB_SUCCESS;
+}
+
+int dsdb_msg_constrainted_update_int32(struct ldb_module *module,
+ struct ldb_message *msg,
+ const char *attr,
+ const int32_t *old_val,
+ const int32_t *new_val)
+{
+ struct ldb_message_element *el;
+ int ret;
+ char *vstring;
+
+ if (old_val) {
+ ret = ldb_msg_add_empty(msg, attr, LDB_FLAG_MOD_DELETE, &el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ el->num_values = 1;
+ el->values = talloc_array(msg, struct ldb_val, el->num_values);
+ if (!el->values) {
+ return ldb_module_oom(module);
+ }
+ vstring = talloc_asprintf(el->values, "%ld", (long)*old_val);
+ if (!vstring) {
+ return ldb_module_oom(module);
+ }
+ *el->values = data_blob_string_const(vstring);
+ }
+
+ if (new_val) {
+ ret = ldb_msg_add_empty(msg, attr, LDB_FLAG_MOD_ADD, &el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ el->num_values = 1;
+ el->values = talloc_array(msg, struct ldb_val, el->num_values);
+ if (!el->values) {
+ return ldb_module_oom(module);
+ }
+ vstring = talloc_asprintf(el->values, "%ld", (long)*new_val);
+ if (!vstring) {
+ return ldb_module_oom(module);
+ }
+ *el->values = data_blob_string_const(vstring);
+ }
+
+ return LDB_SUCCESS;
+}
+
+int dsdb_msg_constrainted_update_uint32(struct ldb_module *module,
+ struct ldb_message *msg,
+ const char *attr,
+ const uint32_t *old_val,
+ const uint32_t *new_val)
+{
+ return dsdb_msg_constrainted_update_int32(module, msg, attr,
+ (const int32_t *)old_val,
+ (const int32_t *)new_val);
+}
+
+int dsdb_msg_constrainted_update_int64(struct ldb_module *module,
+ struct ldb_message *msg,
+ const char *attr,
+ const int64_t *old_val,
+ const int64_t *new_val)
+{
+ struct ldb_message_element *el;
+ int ret;
+ char *vstring;
+
+ if (old_val) {
+ ret = ldb_msg_add_empty(msg, attr, LDB_FLAG_MOD_DELETE, &el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ el->num_values = 1;
+ el->values = talloc_array(msg, struct ldb_val, el->num_values);
+ if (!el->values) {
+ return ldb_module_oom(module);
+ }
+ vstring = talloc_asprintf(el->values, "%lld", (long long)*old_val);
+ if (!vstring) {
+ return ldb_module_oom(module);
+ }
+ *el->values = data_blob_string_const(vstring);
+ }
+
+ if (new_val) {
+ ret = ldb_msg_add_empty(msg, attr, LDB_FLAG_MOD_ADD, &el);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ el->num_values = 1;
+ el->values = talloc_array(msg, struct ldb_val, el->num_values);
+ if (!el->values) {
+ return ldb_module_oom(module);
+ }
+ vstring = talloc_asprintf(el->values, "%lld", (long long)*new_val);
+ if (!vstring) {
+ return ldb_module_oom(module);
+ }
+ *el->values = data_blob_string_const(vstring);
+ }
+
+ return LDB_SUCCESS;
+}
+
+int dsdb_msg_constrainted_update_uint64(struct ldb_module *module,
+ struct ldb_message *msg,
+ const char *attr,
+ const uint64_t *old_val,
+ const uint64_t *new_val)
+{
+ return dsdb_msg_constrainted_update_int64(module, msg, attr,
+ (const int64_t *)old_val,
+ (const int64_t *)new_val);
+}
+
+/*
+ update an int32 attribute safely via a constrained delete/add
+ */
+int dsdb_module_constrainted_update_int32(struct ldb_module *module,
+ struct ldb_dn *dn,
+ const char *attr,
+ const int32_t *old_val,
+ const int32_t *new_val,
+ struct ldb_request *parent)
+{
+ struct ldb_message *msg;
+ int ret;
+
+ msg = ldb_msg_new(module);
+ if (msg == NULL) {
+ return ldb_module_oom(module);
+ }
+ msg->dn = dn;
+
+ ret = dsdb_msg_constrainted_update_int32(module,
+ msg, attr,
+ old_val,
+ new_val);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(msg);
+ return ret;
+ }
+
+ ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE, parent);
+ talloc_free(msg);
+ return ret;
+}
+
+int dsdb_module_constrainted_update_uint32(struct ldb_module *module,
+ struct ldb_dn *dn,
+ const char *attr,
+ const uint32_t *old_val,
+ const uint32_t *new_val,
+ struct ldb_request *parent)
+{
+ return dsdb_module_constrainted_update_int32(module, dn, attr,
+ (const int32_t *)old_val,
+ (const int32_t *)new_val, parent);
+}
+
+/*
+ update an int64 attribute safely via a constrained delete/add
+ */
+int dsdb_module_constrainted_update_int64(struct ldb_module *module,
+ struct ldb_dn *dn,
+ const char *attr,
+ const int64_t *old_val,
+ const int64_t *new_val,
+ struct ldb_request *parent)
+{
+ struct ldb_message *msg;
+ int ret;
+
+ msg = ldb_msg_new(module);
+ if (msg == NULL) {
+ return ldb_module_oom(module);
+ }
+ msg->dn = dn;
+
+ ret = dsdb_msg_constrainted_update_int64(module,
+ msg, attr,
+ old_val,
+ new_val);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(msg);
+ return ret;
+ }
+
+ ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE, parent);
+ talloc_free(msg);
+ return ret;
+}
+
+int dsdb_module_constrainted_update_uint64(struct ldb_module *module,
+ struct ldb_dn *dn,
+ const char *attr,
+ const uint64_t *old_val,
+ const uint64_t *new_val,
+ struct ldb_request *parent)
+{
+ return dsdb_module_constrainted_update_int64(module, dn, attr,
+ (const int64_t *)old_val,
+ (const int64_t *)new_val,
+ parent);
+}
+
+
+const struct ldb_val *dsdb_module_find_dsheuristics(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx, struct ldb_request *parent)
+{
+ int ret;
+ struct ldb_dn *new_dn;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+ static const char *attrs[] = { "dSHeuristics", NULL };
+ struct ldb_result *res;
+
+ new_dn = ldb_dn_copy(mem_ctx, ldb_get_config_basedn(ldb));
+ if (!ldb_dn_add_child_fmt(new_dn,
+ "CN=Directory Service,CN=Windows NT,CN=Services")) {
+ talloc_free(new_dn);
+ return NULL;
+ }
+ ret = dsdb_module_search_dn(module, mem_ctx, &res,
+ new_dn,
+ attrs,
+ DSDB_FLAG_NEXT_MODULE,
+ parent);
+ if (ret == LDB_SUCCESS && res->count == 1) {
+ talloc_free(new_dn);
+ return ldb_msg_find_ldb_val(res->msgs[0],
+ "dSHeuristics");
+ }
+ talloc_free(new_dn);
+ return NULL;
+}
+
+bool dsdb_block_anonymous_ops(struct ldb_module *module, struct ldb_request *parent)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(module);
+ bool result;
+ const struct ldb_val *hr_val = dsdb_module_find_dsheuristics(module,
+ tmp_ctx, parent);
+ if (hr_val == NULL || hr_val->length < DS_HR_BLOCK_ANONYMOUS_OPS) {
+ result = true;
+ } else if (hr_val->data[DS_HR_BLOCK_ANONYMOUS_OPS -1] == '2') {
+ result = false;
+ } else {
+ result = true;
+ }
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+bool dsdb_user_password_support(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_request *parent)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ bool result;
+ const struct ldb_val *hr_val = dsdb_module_find_dsheuristics(module,
+ tmp_ctx,
+ parent);
+ if (hr_val == NULL || hr_val->length < DS_HR_USER_PASSWORD_SUPPORT) {
+ result = false;
+ } else if ((hr_val->data[DS_HR_USER_PASSWORD_SUPPORT -1] == '2') ||
+ (hr_val->data[DS_HR_USER_PASSWORD_SUPPORT -1] == '0')) {
+ result = false;
+ } else {
+ result = true;
+ }
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+bool dsdb_do_list_object(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_request *parent)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ bool result;
+ const struct ldb_val *hr_val = dsdb_module_find_dsheuristics(module,
+ tmp_ctx,
+ parent);
+ if (hr_val == NULL || hr_val->length < DS_HR_DOLISTOBJECT) {
+ result = false;
+ } else if (hr_val->data[DS_HR_DOLISTOBJECT -1] == '1') {
+ result = true;
+ } else {
+ result = false;
+ }
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+bool dsdb_attribute_authz_on_ldap_add(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_request *parent)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ bool result = false;
+ const struct ldb_val *hr_val = dsdb_module_find_dsheuristics(module,
+ tmp_ctx,
+ parent);
+ if (hr_val != NULL && hr_val->length >= DS_HR_ATTR_AUTHZ_ON_LDAP_ADD) {
+ uint8_t val = hr_val->data[DS_HR_ATTR_AUTHZ_ON_LDAP_ADD - 1];
+ if (val != '0' && val != '2') {
+ result = true;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+bool dsdb_block_owner_implicit_rights(struct ldb_module *module,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_request *parent)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ bool result = false;
+ const struct ldb_val *hr_val = dsdb_module_find_dsheuristics(module,
+ tmp_ctx,
+ parent);
+ if (hr_val != NULL && hr_val->length >= DS_HR_BLOCK_OWNER_IMPLICIT_RIGHTS) {
+ uint8_t val = hr_val->data[DS_HR_BLOCK_OWNER_IMPLICIT_RIGHTS - 1];
+ if (val != '0' && val != '2') {
+ result = true;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+/*
+ show the chain of requests, useful for debugging async requests
+ */
+void dsdb_req_chain_debug(struct ldb_request *req, int level)
+{
+ char *s = ldb_module_call_chain(req, req);
+ DEBUG(level, ("%s\n", s));
+ talloc_free(s);
+}
+
+/*
+ * Get all the values that *might* be added by an ldb message, as a composite
+ * ldb element.
+ *
+ * This is useful when we need to check all the possible values against some
+ * criteria.
+ *
+ * In cases where a modify message mixes multiple ADDs, DELETEs, and REPLACES,
+ * the returned element might contain more values than would actually end up
+ * in the database if the message was run to its conclusion.
+ *
+ * If the operation is not LDB_ADD or LDB_MODIFY, an operations error is
+ * returned.
+ *
+ * The returned element might not be new, and should not be modified or freed
+ * before the message is finished.
+ */
+
+int dsdb_get_expected_new_values(TALLOC_CTX *mem_ctx,
+ const struct ldb_message *msg,
+ const char *attr_name,
+ struct ldb_message_element **el,
+ enum ldb_request_type operation)
+{
+ unsigned int i;
+ unsigned int el_count = 0;
+ unsigned int val_count = 0;
+ struct ldb_val *v = NULL;
+ struct ldb_message_element *_el = NULL;
+ *el = NULL;
+
+ if (operation != LDB_ADD && operation != LDB_MODIFY) {
+ DBG_ERR("inapplicable operation type: %d\n", operation);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ /* count the adding or replacing elements */
+ for (i = 0; i < msg->num_elements; i++) {
+ if (ldb_attr_cmp(msg->elements[i].name, attr_name) == 0) {
+ unsigned int tmp;
+ if ((operation == LDB_MODIFY) &&
+ (LDB_FLAG_MOD_TYPE(msg->elements[i].flags)
+ == LDB_FLAG_MOD_DELETE)) {
+ continue;
+ }
+ el_count++;
+ tmp = val_count + msg->elements[i].num_values;
+ if (unlikely(tmp < val_count)) {
+ DBG_ERR("too many values for one element!");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ val_count = tmp;
+ }
+ }
+ if (el_count == 0) {
+ /* nothing to see here */
+ return LDB_SUCCESS;
+ }
+
+ if (el_count == 1 || val_count == 0) {
+ /*
+ * There is one effective element, which we can return as-is,
+ * OR there are only elements with zero values -- any of which
+ * will do.
+ */
+ for (i = 0; i < msg->num_elements; i++) {
+ if (ldb_attr_cmp(msg->elements[i].name, attr_name) == 0) {
+ if ((operation == LDB_MODIFY) &&
+ (LDB_FLAG_MOD_TYPE(msg->elements[i].flags)
+ == LDB_FLAG_MOD_DELETE)) {
+ continue;
+ }
+ *el = &msg->elements[i];
+ return LDB_SUCCESS;
+ }
+ }
+ }
+
+ _el = talloc_zero(mem_ctx, struct ldb_message_element);
+ if (_el == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ _el->name = attr_name;
+
+ if (val_count == 0) {
+ /*
+ * Seems unlikely, but sometimes we might be adding zero
+ * values in multiple separate elements. The talloc zero has
+ * already set the expected values = NULL, num_values = 0.
+ */
+ *el = _el;
+ return LDB_SUCCESS;
+ }
+
+ _el->values = talloc_array(_el, struct ldb_val, val_count);
+ if (_el->values == NULL) {
+ talloc_free(_el);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ _el->num_values = val_count;
+
+ v = _el->values;
+
+ for (i = 0; i < msg->num_elements; i++) {
+ if (ldb_attr_cmp(msg->elements[i].name, attr_name) == 0) {
+ const struct ldb_message_element *tmp_el = &msg->elements[i];
+ if ((operation == LDB_MODIFY) &&
+ (LDB_FLAG_MOD_TYPE(tmp_el->flags)
+ == LDB_FLAG_MOD_DELETE)) {
+ continue;
+ }
+ if (tmp_el->values == NULL || tmp_el->num_values == 0) {
+ continue;
+ }
+ memcpy(v,
+ tmp_el->values,
+ tmp_el->num_values * sizeof(*v));
+ v += tmp_el->num_values;
+ }
+ }
+
+ *el = _el;
+ return LDB_SUCCESS;
+}
+
+
+/*
+ * Get the value of a single-valued attribute from an ADDed message. 'val' will only live as
+ * long as 'msg' and 'original_val' do, and must not be freed.
+ */
+int dsdb_msg_add_get_single_value(const struct ldb_message *msg,
+ const char *attr_name,
+ const struct ldb_val **val)
+{
+ const struct ldb_message_element *el = NULL;
+
+ /*
+ * The ldb_msg_normalize() call in ldb_request() ensures that
+ * there is at most one message element for each
+ * attribute. Thus, we don't need a loop to deal with an
+ * LDB_ADD.
+ */
+ el = ldb_msg_find_element(msg, attr_name);
+ if (el == NULL) {
+ *val = NULL;
+ return LDB_SUCCESS;
+ }
+ if (el->num_values != 1) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ *val = &el->values[0];
+ return LDB_SUCCESS;
+}
+
+/*
+ * Get the value of a single-valued attribute after processing a
+ * message. 'operation' is either LDB_ADD or LDB_MODIFY. 'val' will only live as
+ * long as 'msg' and 'original_val' do, and must not be freed.
+ */
+int dsdb_msg_get_single_value(const struct ldb_message *msg,
+ const char *attr_name,
+ const struct ldb_val *original_val,
+ const struct ldb_val **val,
+ enum ldb_request_type operation)
+{
+ unsigned idx;
+
+ *val = NULL;
+
+ if (operation == LDB_ADD) {
+ if (original_val != NULL) {
+ /* This is an error on the caller's part. */
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ return dsdb_msg_add_get_single_value(msg, attr_name, val);
+ }
+
+ SMB_ASSERT(operation == LDB_MODIFY);
+
+ *val = original_val;
+
+ for (idx = 0; idx < msg->num_elements; ++idx) {
+ const struct ldb_message_element *el = &msg->elements[idx];
+
+ if (ldb_attr_cmp(el->name, attr_name) != 0) {
+ continue;
+ }
+
+ switch (el->flags & LDB_FLAG_MOD_MASK) {
+ case LDB_FLAG_MOD_ADD:
+ if (el->num_values != 1) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+ if (*val != NULL) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ *val = &el->values[0];
+
+ break;
+
+ case LDB_FLAG_MOD_REPLACE:
+ if (el->num_values > 1) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ *val = el->num_values ? &el->values[0] : NULL;
+
+ break;
+
+ case LDB_FLAG_MOD_DELETE:
+ if (el->num_values > 1) {
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ /*
+ * If a value was specified for the delete, we don't
+ * bother checking it matches the value we currently
+ * have. Any mismatch will be caught later (e.g. in
+ * ldb_kv_modify_internal).
+ */
+
+ *val = NULL;
+
+ break;
+ }
+ }
+
+ return LDB_SUCCESS;
+}
+
+/*
+ * This function determines the (last) structural or 88 object class of a passed
+ * "objectClass" attribute - per MS-ADTS 3.1.1.1.4 this is the last value.
+ * Without schema this does not work and hence NULL is returned.
+ */
+const struct dsdb_class *dsdb_get_last_structural_class(const struct dsdb_schema *schema,
+ const struct ldb_message_element *element)
+{
+ const struct dsdb_class *last_class;
+
+ if (schema == NULL) {
+ return NULL;
+ }
+
+ if (element->num_values == 0) {
+ return NULL;
+ }
+
+ last_class = dsdb_class_by_lDAPDisplayName_ldb_val(schema,
+ &element->values[element->num_values-1]);
+ if (last_class == NULL) {
+ return NULL;
+ }
+ if (last_class->objectClassCategory > 1) {
+ return NULL;
+ }
+
+ return last_class;
+}
+
+const struct dsdb_class *dsdb_get_structural_oc_from_msg(const struct dsdb_schema *schema,
+ const struct ldb_message *msg)
+{
+ struct ldb_message_element *oc_el;
+
+ oc_el = ldb_msg_find_element(msg, "objectClass");
+ if (!oc_el) {
+ return NULL;
+ }
+
+ return dsdb_get_last_structural_class(schema, oc_el);
+}
+
+/*
+ Get the parent class of an objectclass, or NULL if none exists.
+ */
+const struct dsdb_class *dsdb_get_parent_class(const struct dsdb_schema *schema,
+ const struct dsdb_class *objectclass)
+{
+ if (ldb_attr_cmp(objectclass->lDAPDisplayName, "top") == 0) {
+ return NULL;
+ }
+
+ if (objectclass->subClassOf == NULL) {
+ return NULL;
+ }
+
+ return dsdb_class_by_lDAPDisplayName(schema, objectclass->subClassOf);
+}
+
+/*
+ Return true if 'struct_objectclass' is a subclass of 'other_objectclass'. The
+ two objectclasses must originate from the same schema, to allow for
+ pointer-based identity comparison.
+ */
+bool dsdb_is_subclass_of(const struct dsdb_schema *schema,
+ const struct dsdb_class *struct_objectclass,
+ const struct dsdb_class *other_objectclass)
+{
+ while (struct_objectclass != NULL) {
+ /* Pointer comparison can be used due to the same schema str. */
+ if (struct_objectclass == other_objectclass) {
+ return true;
+ }
+
+ struct_objectclass = dsdb_get_parent_class(schema, struct_objectclass);
+ }
+
+ return false;
+}
+
+/* Fix the DN so that the relative attribute names are in upper case so that the DN:
+ cn=Adminstrator,cn=users,dc=samba,dc=example,dc=com becomes
+ CN=Adminstrator,CN=users,DC=samba,DC=example,DC=com
+*/
+int dsdb_fix_dn_rdncase(struct ldb_context *ldb, 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_oom(ldb);
+ }
+ 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;
+}
+
+/**
+ * Make most specific objectCategory for the objectClass of passed object
+ * NOTE: In this implementation we count that it is called on already
+ * verified objectClass attribute value. See objectclass.c thorough
+ * implementation for all the magic that involves
+ *
+ * @param ldb ldb context
+ * @param schema cached schema for ldb. We may get it, but it is very time consuming.
+ * Hence leave the responsibility to the caller.
+ * @param obj AD object to determint objectCategory for
+ * @param mem_ctx Memory context - usually it is obj actually
+ * @param pobjectcategory location to store found objectCategory
+ *
+ * @return LDB_SUCCESS or error including out of memory error
+ */
+int dsdb_make_object_category(struct ldb_context *ldb, const struct dsdb_schema *schema,
+ const struct ldb_message *obj,
+ TALLOC_CTX *mem_ctx, const char **pobjectcategory)
+{
+ const struct dsdb_class *objectclass;
+ struct ldb_message_element *objectclass_element;
+ struct dsdb_extended_dn_store_format *dn_format;
+
+ objectclass_element = ldb_msg_find_element(obj, "objectClass");
+ if (!objectclass_element) {
+ ldb_asprintf_errstring(ldb, "dsdb: Cannot add %s, no objectclass specified!",
+ ldb_dn_get_linearized(obj->dn));
+ return LDB_ERR_OBJECT_CLASS_VIOLATION;
+ }
+ if (objectclass_element->num_values == 0) {
+ ldb_asprintf_errstring(ldb, "dsdb: Cannot add %s, at least one (structural) objectclass has to be specified!",
+ ldb_dn_get_linearized(obj->dn));
+ return LDB_ERR_CONSTRAINT_VIOLATION;
+ }
+
+ /*
+ * Get the new top-most structural object class and check for
+ * unrelated structural classes
+ */
+ objectclass = dsdb_get_last_structural_class(schema,
+ objectclass_element);
+ if (objectclass == NULL) {
+ ldb_asprintf_errstring(ldb,
+ "Failed to find a structural class for %s",
+ ldb_dn_get_linearized(obj->dn));
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ dn_format = talloc_get_type(ldb_get_opaque(ldb, DSDB_EXTENDED_DN_STORE_FORMAT_OPAQUE_NAME),
+ struct dsdb_extended_dn_store_format);
+ if (dn_format && dn_format->store_extended_dn_in_ldb == false) {
+ /* Strip off extended components */
+ struct ldb_dn *dn = ldb_dn_new(mem_ctx, ldb,
+ objectclass->defaultObjectCategory);
+ *pobjectcategory = ldb_dn_alloc_linearized(mem_ctx, dn);
+ talloc_free(dn);
+ } else {
+ *pobjectcategory = talloc_strdup(mem_ctx, objectclass->defaultObjectCategory);
+ }
+
+ if (*pobjectcategory == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ return LDB_SUCCESS;
+}
diff --git a/source4/dsdb/samdb/ldb_modules/util.h b/source4/dsdb/samdb/ldb_modules/util.h
new file mode 100644
index 0000000..937767a
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/util.h
@@ -0,0 +1,42 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+
+ Copyright (C) Andrew Tridgell 2009
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2009
+
+ 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/>.
+*/
+
+/* predeclare some structures used by utility functions */
+struct dsdb_schema;
+struct dsdb_attribute;
+struct dsdb_fsmo_extended_op;
+struct security_descriptor;
+struct dom_sid;
+struct netlogon_samlogon_response;
+
+#include "librpc/gen_ndr/misc.h"
+#include "dsdb/samdb/ldb_modules/util_proto.h"
+#include "dsdb/common/util.h"
+#include "../libcli/netlogon/netlogon.h"
+
+/* extend the dsdb_request_add_controls() flags for module
+ specific functions */
+#define DSDB_FLAG_NEXT_MODULE 0x00100000
+#define DSDB_FLAG_OWN_MODULE 0x00400000
+#define DSDB_FLAG_TOP_MODULE 0x00800000
+#define DSDB_FLAG_TRUSTED 0x01000000
+#define DSDB_FLAG_REPLICATED_UPDATE 0x02000000
+#define DSDB_FLAG_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE 0x04000000
diff --git a/source4/dsdb/samdb/ldb_modules/vlv_pagination.c b/source4/dsdb/samdb/ldb_modules/vlv_pagination.c
new file mode 100644
index 0000000..b389d3f
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/vlv_pagination.c
@@ -0,0 +1,946 @@
+/*
+ ldb database library
+
+ Copyright (C) Simo Sorce 2005-2008
+ Copyright (C) Catalyst IT 2016
+
+ ** 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: vlv_pagination
+ *
+ * Component: ldb vlv pagination control module
+ *
+ * Description: this module caches a complete search and sends back
+ * results in chunks as asked by the client
+ *
+ * Originally based on paged_results.c by Simo Sorce
+ * Modified by Douglas Bagnall and Garming Sam for Catalyst.
+ */
+
+#include "includes.h"
+#include "auth/auth.h"
+#include <ldb.h>
+#include "dsdb/samdb/samdb.h"
+#include "libcli/security/security.h"
+#include "libcli/ldap/ldap_errors.h"
+#include <ldb.h>
+#include "replace.h"
+#include "system/filesys.h"
+#include "system/time.h"
+#include "ldb_module.h"
+#include "dsdb/samdb/samdb.h"
+
+#include "dsdb/common/util.h"
+#include "lib/util/binsearch.h"
+
+/* This is the number of concurrent searches per connection to cache. */
+#define VLV_N_SEARCHES 5
+
+
+struct results_store {
+ uint32_t contextId;
+ time_t timestamp;
+
+ struct GUID *results;
+ size_t num_entries;
+ size_t result_array_size;
+
+ struct referral_store *first_ref;
+ struct referral_store *last_ref;
+
+ struct ldb_control **controls;
+ struct ldb_control **down_controls;
+ struct ldb_vlv_req_control *vlv_details;
+ struct ldb_server_sort_control *sort_details;
+};
+
+struct private_data {
+ uint32_t next_free_id;
+ struct results_store **store;
+ int n_stores;
+};
+
+
+struct vlv_context {
+ struct ldb_module *module;
+ struct ldb_request *req;
+ struct results_store *store;
+ struct ldb_control **controls;
+ struct private_data *priv;
+};
+
+
+static struct results_store *new_store(struct private_data *priv)
+{
+ struct results_store *store;
+ int i;
+ int best = 0;
+ time_t oldest = TIME_T_MAX;
+ for (i = 0; i < priv->n_stores; i++) {
+ if (priv->store[i] == NULL) {
+ best = i;
+ break;
+ } else if (priv->store[i]->timestamp < oldest){
+ best = i;
+ oldest = priv->store[i]->timestamp;
+ }
+ }
+
+ store = talloc_zero(priv, struct results_store);
+ if (store == NULL) {
+ return NULL;
+ }
+ if (priv->store[best] != NULL) {
+ TALLOC_FREE(priv->store[best]);
+ }
+ priv->store[best] = store;
+ store->timestamp = time(NULL);
+ return store;
+}
+
+
+struct vlv_sort_context {
+ struct ldb_context *ldb;
+ ldb_attr_comparison_t comparison_fn;
+ const char *attr;
+ struct vlv_context *ac;
+ int status;
+ struct ldb_val value;
+};
+
+
+/* Referrals are temporarily stored in a linked list */
+struct referral_store {
+ char *ref;
+ struct referral_store *next;
+};
+
+/*
+ search for attrs on one DN, by the GUID of the DN, with true
+ LDB controls
+ */
+
+static int vlv_search_by_dn_guid(struct ldb_module *module,
+ struct vlv_context *ac,
+ struct ldb_result **result,
+ const struct GUID *guid,
+ const char * const *attrs)
+{
+ struct ldb_dn *dn;
+ struct ldb_request *req;
+ struct ldb_result *res;
+ int ret;
+ struct GUID_txt_buf guid_str;
+ struct ldb_control **controls = ac->store->down_controls;
+ struct ldb_context *ldb = ldb_module_get_ctx(module);
+
+ dn = ldb_dn_new_fmt(ac, ldb, "<GUID=%s>",
+ GUID_buf_string(guid, &guid_str));
+ if (dn == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ res = talloc_zero(ac, struct ldb_result);
+ if (res == NULL) {
+ return ldb_oom(ldb);
+ }
+
+ ret = ldb_build_search_req(&req, ldb, ac,
+ dn,
+ LDB_SCOPE_BASE,
+ NULL,
+ attrs,
+ controls,
+ res,
+ ldb_search_default_callback,
+ ac->req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(res);
+ return ret;
+ }
+
+ ret = ldb_request(ldb, req);
+ if (ret == LDB_SUCCESS) {
+ ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+ }
+
+ talloc_free(req);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(res);
+ return ret;
+ }
+
+ *result = res;
+ return ret;
+}
+
+
+static int save_referral(struct results_store *store, char *ref)
+{
+ struct referral_store *node = talloc(store,
+ struct referral_store);
+ if (node == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ node->next = NULL;
+ node->ref = talloc_steal(node, ref);
+
+ if (store->first_ref == NULL) {
+ store->first_ref = node;
+ } else {
+ store->last_ref->next = node;
+ }
+ store->last_ref = node;
+ return LDB_SUCCESS;
+}
+
+static int send_referrals(struct results_store *store,
+ struct ldb_request *req)
+{
+ int ret;
+ struct referral_store *node;
+ while (store->first_ref != NULL) {
+ node = store->first_ref;
+ ret = ldb_module_send_referral(req, node->ref);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ store->first_ref = node->next;
+ talloc_free(node);
+ }
+ return LDB_SUCCESS;
+}
+
+
+/* vlv_value_compare() is used in a binary search */
+
+static int vlv_value_compare(struct vlv_sort_context *target,
+ struct GUID *guid)
+{
+ struct ldb_result *result = NULL;
+ struct ldb_message_element *el = NULL;
+ struct vlv_context *ac = target->ac;
+ int ret;
+ const char *attrs[2] = {
+ target->attr,
+ NULL
+ };
+
+ ret = vlv_search_by_dn_guid(ac->module, ac, &result, guid, attrs);
+
+ if (ret != LDB_SUCCESS) {
+ target->status = ret;
+ /* returning 0 ends the search. */
+ return 0;
+ }
+
+ el = ldb_msg_find_element(result->msgs[0], target->attr);
+ return target->comparison_fn(target->ldb, ac,
+ &target->value, &el->values[0]);
+
+}
+
+/* The same as vlv_value_compare() but sorting in the opposite direction. */
+static int vlv_value_compare_rev(struct vlv_sort_context *target,
+ struct GUID *guid)
+{
+ return -vlv_value_compare(target, guid);
+}
+
+
+
+/* Convert a "greater than or equal to" VLV query into an index. This is
+ zero-based, so one less than the equivalent VLV offset query.
+
+ If the query value is greater than (or less than in the reverse case) all
+ the items, An index just beyond the last position is used.
+
+ If an error occurs during the search for the index, we stash it in the
+ status argument.
+ */
+
+static int vlv_gt_eq_to_index(struct vlv_context *ac,
+ struct GUID *guid_array,
+ struct ldb_vlv_req_control *vlv_details,
+ struct ldb_server_sort_control *sort_details,
+ int *status)
+{
+ /* this has a >= comparison string, which needs to be
+ * converted into indices.
+ */
+ size_t len = ac->store->num_entries;
+ struct ldb_context *ldb;
+ const struct ldb_schema_attribute *a;
+ struct GUID *result = NULL;
+ struct vlv_sort_context context;
+ struct ldb_val value = {
+ .data = (uint8_t *)vlv_details->match.gtOrEq.value,
+ .length = vlv_details->match.gtOrEq.value_len
+ };
+ ldb = ldb_module_get_ctx(ac->module);
+ a = ldb_schema_attribute_by_name(ldb, sort_details->attributeName);
+
+ context = (struct vlv_sort_context){
+ .ldb = ldb,
+ .comparison_fn = a->syntax->comparison_fn,
+ .attr = sort_details->attributeName,
+ .ac = ac,
+ .status = LDB_SUCCESS,
+ .value = value
+ };
+
+ if (sort_details->reverse) {
+ /* when the sort is reversed, "gtOrEq" means
+ "less than or equal" */
+ BINARY_ARRAY_SEARCH_GTE(guid_array, len, &context,
+ vlv_value_compare_rev,
+ result, result);
+ } else {
+ BINARY_ARRAY_SEARCH_GTE(guid_array, len, &context,
+ vlv_value_compare,
+ result, result);
+ }
+ if (context.status != LDB_SUCCESS) {
+ *status = context.status;
+ return len;
+ }
+ *status = LDB_SUCCESS;
+
+ if (result == NULL) {
+ /* the target is beyond the end of the array */
+ return len;
+ }
+ return result - guid_array;
+
+}
+
+/* return the zero-based index into the sorted results, or -1 on error.
+
+ The VLV index is one-base, so one greater than this.
+ */
+
+static int vlv_calc_real_offset(int offset, int denominator, int n_entries)
+{
+ double fraction;
+
+ /* An offset of 0 (or less) is an error, unless the denominator is
+ also zero. */
+ if (offset <= 0 && denominator != 0) {
+ return -1;
+ }
+
+ /* a denominator of zero means the server should use the estimated
+ number of entries. */
+ if (denominator == 0) {
+ if (offset == 0) {
+ /* 0/0 means the last one */
+ return n_entries - 1;
+ }
+ denominator = n_entries;
+ }
+
+ if (denominator == 1) {
+ /* The 1/1 case means the LAST index.
+ Strangely, for n > 1, n/1 means the FIRST index.
+ */
+ if (offset == 1) {
+ return n_entries - 1;
+ }
+ return 0;
+ }
+
+ if (offset >= denominator) {
+ /* we want the last one */
+ return n_entries - 1;
+ }
+ /* if the denominator is exactly the number of entries, the offset is
+ already correct. */
+
+ if (denominator == n_entries) {
+ return offset - 1;
+ }
+
+ /* The following formula was discovered by probing Windows. */
+ fraction = (offset - 1.0) / (denominator - 1.0);
+ return (int)(fraction * (n_entries - 1.0) + 0.5);
+}
+
+
+/* vlv_results() is called when there is a valid contextID -- meaning the search
+ has been prepared earlier and saved -- or by vlv_search_callback() when a
+ search has just been completed. */
+
+static int vlv_results(struct vlv_context *ac, struct ldb_reply *ares)
+{
+ struct ldb_extended *response = (ares != NULL ? ares->response : NULL);
+ struct ldb_vlv_resp_control *vlv;
+ unsigned int num_ctrls;
+ int ret, i, first_i, last_i;
+ struct ldb_vlv_req_control *vlv_details;
+ struct ldb_server_sort_control *sort_details;
+ int target = 0;
+
+ if (ac->store == NULL) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ return ldb_module_done(
+ ac->req, ac->controls, response, ret);
+ }
+
+ if (ac->store->first_ref) {
+ /* There is no right place to put references in the sorted
+ results, so we send them as soon as possible.
+ */
+ ret = send_referrals(ac->store, ac->req);
+ if (ret != LDB_SUCCESS) {
+ /*
+ * send_referrals will have called ldb_module_done
+ * if there was an error.
+ */
+ return ret;
+ }
+ }
+
+ vlv_details = ac->store->vlv_details;
+ sort_details = ac->store->sort_details;
+
+ if (ac->store->num_entries != 0) {
+ if (vlv_details->type == 1) {
+ target = vlv_gt_eq_to_index(ac, ac->store->results,
+ vlv_details,
+ sort_details, &ret);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(
+ ac->req,
+ ac->controls,
+ response,
+ ret);
+ }
+ } else {
+ target = vlv_calc_real_offset(vlv_details->match.byOffset.offset,
+ vlv_details->match.byOffset.contentCount,
+ ac->store->num_entries);
+ if (target == -1) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ return ldb_module_done(
+ ac->req,
+ ac->controls,
+ response,
+ ret);
+ }
+ }
+
+ /* send the results */
+ first_i = MAX(target - vlv_details->beforeCount, 0);
+ last_i = MIN(target + vlv_details->afterCount,
+ ac->store->num_entries - 1);
+
+ for (i = first_i; i <= last_i; i++) {
+ struct ldb_result *result = NULL;
+ struct GUID *guid = &ac->store->results[i];
+
+ ret = vlv_search_by_dn_guid(ac->module, ac, &result, guid,
+ ac->req->op.search.attrs);
+
+ if (ret == LDAP_NO_SUCH_OBJECT
+ || result->count != 1) {
+ /*
+ * The thing isn't there, which we quietly
+ * ignore and go on to send an extra one
+ * instead.
+ *
+ * result->count == 0 or > 1 can only
+ * happen if ASQ (which breaks all the
+ * rules) is somehow invoked (as this
+ * is a BASE search).
+ *
+ * (We skip the ASQ cookie for the
+ * GUID searches)
+ */
+ if (last_i < ac->store->num_entries - 1) {
+ last_i++;
+ }
+ continue;
+ } else if (ret != LDB_SUCCESS) {
+ return ldb_module_done(
+ ac->req,
+ ac->controls,
+ response,
+ ret);
+ }
+
+ ret = ldb_module_send_entry(ac->req, result->msgs[0],
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ /*
+ * ldb_module_send_entry will have called
+ * ldb_module_done if there was an error
+ */
+ return ret;
+ }
+ }
+ } else {
+ target = -1;
+ }
+
+ /* return result done */
+ num_ctrls = 1;
+ i = 0;
+
+ if (ac->store->controls != NULL) {
+ while (ac->store->controls[i]){
+ i++; /* counting */
+ }
+ num_ctrls += i;
+ }
+
+ ac->controls = talloc_array(ac, struct ldb_control *, num_ctrls + 1);
+ if (ac->controls == NULL) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ return ldb_module_done(
+ ac->req, ac->controls, response, ret);
+ }
+ ac->controls[num_ctrls] = NULL;
+
+ for (i = 0; i < (num_ctrls -1); i++) {
+ ac->controls[i] = talloc_reference(ac->controls, ac->store->controls[i]);
+ }
+
+ ac->controls[i] = talloc(ac->controls, struct ldb_control);
+ if (ac->controls[i] == NULL) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ return ldb_module_done(
+ ac->req, ac->controls, response, ret);
+ }
+
+ ac->controls[i]->oid = talloc_strdup(ac->controls[i],
+ LDB_CONTROL_VLV_RESP_OID);
+ if (ac->controls[i]->oid == NULL) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ return ldb_module_done(
+ ac->req, ac->controls, response, ret);
+ }
+
+ ac->controls[i]->critical = 0;
+
+ vlv = talloc(ac->controls[i], struct ldb_vlv_resp_control);
+ if (vlv == NULL) {
+ ret = LDB_ERR_OPERATIONS_ERROR;
+ return ldb_module_done(
+ ac->req, ac->controls, response, ret);
+ }
+ ac->controls[i]->data = vlv;
+
+ ac->store->timestamp = time(NULL);
+
+ ac->store->contextId = ac->priv->next_free_id;
+ ac->priv->next_free_id++;
+ vlv->contextId = talloc_memdup(vlv, &ac->store->contextId, sizeof(uint32_t));
+ vlv->ctxid_len = sizeof(uint32_t);
+ vlv->vlv_result = 0;
+ vlv->contentCount = ac->store->num_entries;
+ if (target >= 0) {
+ vlv->targetPosition = target + 1;
+ } else if (vlv_details->type == 1) {
+ vlv->targetPosition = ac->store->num_entries + 1;
+ } else {
+ vlv->targetPosition = 0;
+ }
+ return LDB_SUCCESS;
+}
+
+
+/* vlv_search_callback() collects GUIDs found by the original search */
+
+static int vlv_search_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+ struct vlv_context *ac;
+ struct results_store *store;
+ int ret;
+
+ ac = talloc_get_type(req->context, struct vlv_context);
+ store = ac->store;
+
+ if (!ares) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ if (ares->error != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, ares->controls,
+ ares->response, ares->error);
+ }
+
+ switch (ares->type) {
+ case LDB_REPLY_ENTRY:
+ if (store->results == NULL) {
+ store->num_entries = 0;
+ store->result_array_size = 16;
+ store->results = talloc_array(store, struct GUID,
+ store->result_array_size);
+ if (store->results == NULL) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ } else if (store->num_entries == store->result_array_size) {
+ store->result_array_size *= 2;
+ store->results = talloc_realloc(store, store->results,
+ struct GUID,
+ store->result_array_size);
+ if (store->results == NULL) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+ store->results[store->num_entries] = \
+ samdb_result_guid(ares->message, "objectGUID");
+ store->num_entries++;
+ break;
+
+ case LDB_REPLY_REFERRAL:
+ ret = save_referral(store, ares->referral);
+ if (ret != LDB_SUCCESS) {
+ return ldb_module_done(ac->req, NULL, NULL, ret);
+ }
+ break;
+
+ case LDB_REPLY_DONE:
+ if (store->num_entries != 0) {
+ store->results = talloc_realloc(store, store->results,
+ struct GUID,
+ store->num_entries);
+ if (store->results == NULL) {
+ return ldb_module_done(ac->req, NULL, NULL,
+ LDB_ERR_OPERATIONS_ERROR);
+ }
+ }
+ store->result_array_size = store->num_entries;
+
+ ac->store->controls = talloc_move(ac->store, &ares->controls);
+ ret = vlv_results(ac, ares);
+ if (ret != LDB_SUCCESS) {
+ /* vlv_results will have called ldb_module_done
+ * if there was an error.
+ */
+ return ret;
+ }
+ return ldb_module_done(ac->req, ac->controls,
+ ares->response, ret);
+ }
+
+ return LDB_SUCCESS;
+}
+
+static int copy_search_details(struct results_store *store,
+ struct ldb_vlv_req_control *vlv_ctrl,
+ struct ldb_server_sort_control *sort_ctrl)
+{
+ /* free the old details which are no longer going to be reachable. */
+ if (store->vlv_details != NULL){
+ TALLOC_FREE(store->vlv_details);
+ }
+
+ if (store->sort_details != NULL){
+ TALLOC_FREE(store->sort_details);
+ }
+
+ store->vlv_details = talloc(store, struct ldb_vlv_req_control);
+ if (store->vlv_details == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ *store->vlv_details = *vlv_ctrl;
+ store->vlv_details->contextId = talloc_memdup(store, vlv_ctrl->contextId,
+ vlv_ctrl->ctxid_len);
+ if (store->vlv_details->contextId == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (vlv_ctrl->type == 1) {
+ char *v = talloc_array(store, char,
+ vlv_ctrl->match.gtOrEq.value_len + 1);
+
+ if (v == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ memcpy(v, vlv_ctrl->match.gtOrEq.value, vlv_ctrl->match.gtOrEq.value_len);
+ v[vlv_ctrl->match.gtOrEq.value_len] = '\0';
+
+ store->vlv_details->match.gtOrEq.value = v;
+ }
+
+ store->sort_details = talloc(store, struct ldb_server_sort_control);
+ if (store->sort_details == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ store->sort_details->attributeName = talloc_strdup(store,
+ sort_ctrl->attributeName);
+ if (store->sort_details->attributeName == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (sort_ctrl->orderingRule == NULL) {
+ store->sort_details->orderingRule = NULL;
+ } else {
+ store->sort_details->orderingRule = talloc_strdup(store,
+ sort_ctrl->orderingRule);
+ if (store->sort_details->orderingRule == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ }
+ store->sort_details->reverse = sort_ctrl->reverse;
+
+ return LDB_SUCCESS;
+}
+
+
+static struct ldb_control **
+vlv_copy_down_controls(TALLOC_CTX *mem_ctx, struct ldb_control **controls)
+{
+
+ struct ldb_control **new_controls;
+ unsigned int i, j, num_ctrls;
+ if (controls == NULL) {
+ return NULL;
+ }
+
+ for (num_ctrls = 0; controls[num_ctrls]; num_ctrls++);
+
+ new_controls = talloc_array(mem_ctx, struct ldb_control *, num_ctrls);
+ if (new_controls == NULL) {
+ return NULL;
+ }
+
+ for (j = 0, i = 0; i < (num_ctrls); i++) {
+ struct ldb_control *control = controls[i];
+ if (control->oid == NULL) {
+ break;
+ }
+ /*
+ * Do not re-use VLV, nor the server-sort, both are
+ * already handled here.
+ */
+ if (strcmp(control->oid, LDB_CONTROL_VLV_REQ_OID) == 0 ||
+ strcmp(control->oid, LDB_CONTROL_SERVER_SORT_OID) == 0) {
+ continue;
+ }
+ /*
+ * ASQ changes everything, do not copy it down for the
+ * per-GUID search
+ */
+ if (strcmp(control->oid, LDB_CONTROL_ASQ_OID) == 0) {
+ continue;
+ }
+ new_controls[j] = talloc_steal(new_controls, control);
+ /*
+ * Sadly the caller is not obliged to make this a
+ * proper talloc tree, so we do so here.
+ */
+ if (control->data) {
+ talloc_steal(control, control->data);
+ }
+ j++;
+ }
+ new_controls[j] = NULL;
+ return new_controls;
+}
+
+static int vlv_search(struct ldb_module *module, struct ldb_request *req)
+{
+ struct ldb_context *ldb;
+ struct ldb_control *control;
+ struct ldb_control *sort_control;
+ struct private_data *priv;
+ struct ldb_vlv_req_control *vlv_ctrl;
+ struct ldb_server_sort_control **sort_ctrl;
+ struct ldb_request *search_req;
+ struct vlv_context *ac;
+ int ret, i, critical;
+
+ ldb = ldb_module_get_ctx(module);
+
+ control = ldb_request_get_control(req, LDB_CONTROL_VLV_REQ_OID);
+ if (control == NULL) {
+ /* There is no VLV. go on */
+ return ldb_next_request(module, req);
+ }
+ critical = control->critical;
+ control->critical = 0;
+
+ sort_control = ldb_request_get_control(req, LDB_CONTROL_SERVER_SORT_OID);
+ if (sort_control == NULL) {
+ /* VLV needs sort */
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ vlv_ctrl = talloc_get_type(control->data, struct ldb_vlv_req_control);
+ if (vlv_ctrl == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ sort_ctrl = talloc_get_type(sort_control->data, struct ldb_server_sort_control *);
+ if (sort_ctrl == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ priv = talloc_get_type(ldb_module_get_private(module),
+ struct private_data);
+
+ ac = talloc_zero(req, struct vlv_context);
+ if (ac == NULL) {
+ ldb_set_errstring(ldb, "Out of Memory");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->module = module;
+ ac->req = req;
+ ac->priv = priv;
+ /* If there is no cookie, this is a new request, and we need to do the
+ * search in the database. Otherwise we try to refer to a previously
+ * saved search.
+ */
+ if (vlv_ctrl->ctxid_len == 0) {
+ static const char * const attrs[2] = {
+ "objectGUID", NULL
+ };
+
+ ac->store = new_store(priv);
+ if (ac->store == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = copy_search_details(ac->store, vlv_ctrl, sort_ctrl[0]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = ldb_build_search_req_ex(&search_req, ldb, ac,
+ req->op.search.base,
+ req->op.search.scope,
+ req->op.search.tree,
+ attrs,
+ req->controls,
+ ac,
+ vlv_search_callback,
+ req);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+ /* 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 (!ldb_save_controls(control, search_req, NULL)) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ac->store->down_controls = vlv_copy_down_controls(ac->store,
+ req->controls);
+
+ if (ac->store->down_controls == NULL) {
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ return ldb_next_request(module, search_req);
+
+ } else {
+ struct results_store *current = NULL;
+ uint8_t *id = vlv_ctrl->contextId;
+
+ if (vlv_ctrl->ctxid_len != sizeof(uint32_t)){
+ return LDB_ERR_UNWILLING_TO_PERFORM;
+ }
+
+ for (i = 0; i < priv->n_stores; i++) {
+ current = priv->store[i];
+ if (current == NULL) {
+ continue;
+ }
+ if (memcmp(&current->contextId, id, sizeof(uint32_t)) == 0) {
+ current->timestamp = time(NULL);
+ break;
+ }
+ }
+ if (i == priv->n_stores) {
+ /* We were given a context id that we don't know about. */
+ if (critical) {
+ return LDAP_UNAVAILABLE_CRITICAL_EXTENSION;
+ } else {
+ return ldb_next_request(module, req);
+ }
+ }
+
+ ac->store = current;
+ ret = copy_search_details(ac->store, vlv_ctrl, sort_ctrl[0]);
+ if (ret != LDB_SUCCESS) {
+ return ret;
+ }
+
+ ret = vlv_results(ac, NULL);
+ if (ret != LDB_SUCCESS) {
+ /*
+ * vlv_results() will have called ldb_module_done
+ * if there was an error.
+ */
+ return ret;
+ }
+ return ldb_module_done(req, ac->controls, NULL,
+ LDB_SUCCESS);
+ }
+}
+
+
+static int vlv_request_init(struct ldb_module *module)
+{
+ struct ldb_context *ldb;
+ struct private_data *data;
+ int ret;
+
+ ldb = ldb_module_get_ctx(module);
+
+ data = talloc(module, struct private_data);
+ if (data == NULL) {
+ return LDB_ERR_OTHER;
+ }
+
+ data->next_free_id = 1;
+ data->n_stores = VLV_N_SEARCHES;
+ data->store = talloc_zero_array(data, struct results_store *, data->n_stores);
+
+ ldb_module_set_private(module, data);
+
+ ret = ldb_mod_register_control(module, LDB_CONTROL_VLV_REQ_OID);
+ if (ret != LDB_SUCCESS) {
+ ldb_debug(ldb, LDB_DEBUG_WARNING,
+ "vlv:"
+ "Unable to register control with rootdse!");
+ }
+
+ return ldb_next_init(module);
+}
+
+static const struct ldb_module_ops ldb_vlv_module_ops = {
+ .name = "vlv",
+ .search = vlv_search,
+ .init_context = vlv_request_init
+};
+
+int ldb_vlv_init(const char *version)
+{
+ LDB_MODULE_CHECK_VERSION(version);
+ return ldb_register_module(&ldb_vlv_module_ops);
+}
diff --git a/source4/dsdb/samdb/ldb_modules/wscript b/source4/dsdb/samdb/ldb_modules/wscript
new file mode 100644
index 0000000..ce7f48d
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/wscript
@@ -0,0 +1,38 @@
+
+import sys
+from waflib import Logs, Options
+import samba3
+
+def options(opt):
+ help = "Build with gpgme support (default=auto). "
+ help += "This requires gpgme devel and python packages "
+ help += "(e.g. libgpgme11-dev, python-gpgme on debian/ubuntu)."
+
+ opt.samba_add_onoff_option('gpgme', default=True, help=(help))
+
+ return
+
+def configure(conf):
+ conf.SET_TARGET_TYPE('gpgme', 'EMPTY')
+
+ if not Options.options.without_ad_dc \
+ and Options.options.with_gpgme != False:
+ conf.find_program('gpgme-config', var='GPGME_CONFIG')
+
+ if conf.env.GPGME_CONFIG:
+ conf.CHECK_CFG(path=conf.env.GPGME_CONFIG, args="--cflags --libs",
+ package="", uselib_store="gpgme",
+ msg='Checking for gpgme support')
+
+ if conf.CHECK_FUNCS_IN('gpgme_new', 'gpgme', headers='gpgme.h'):
+ conf.DEFINE('ENABLE_GPGME', '1')
+
+ if not conf.CONFIG_SET('ENABLE_GPGME'):
+ conf.fatal("GPGME support not found. "
+ "Try installing libgpgme11-dev or gpgme-devel "
+ "and python-gpgme. "
+ "Otherwise, use --without-gpgme to build without "
+ "GPGME support or --without-ad-dc to build without "
+ "the Samba AD DC. "
+ "GPGME support is required for the GPG encrypted "
+ "password sync feature")
diff --git a/source4/dsdb/samdb/ldb_modules/wscript_build b/source4/dsdb/samdb/ldb_modules/wscript_build
new file mode 100644
index 0000000..c3e8b54
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/wscript_build
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+
+bld.SAMBA_LIBRARY('dsdb-module',
+ source=[],
+ deps='DSDB_MODULE_HELPERS DSDB_MODULE_HELPER_RIDALLOC',
+ private_library=True,
+ grouping_library=True)
+
+bld.SAMBA_SUBSYSTEM('DSDB_MODULE_HELPERS',
+ source='util.c acl_util.c schema_util.c netlogon.c',
+ autoproto='util_proto.h',
+ deps='ldb ndr samdb-common samba-security'
+ )
+
+bld.SAMBA_SUBSYSTEM('DSDB_MODULE_HELPER_RIDALLOC',
+ source='ridalloc.c',
+ autoproto='ridalloc.h',
+ deps='MESSAGING',
+ )
+
+# Build the cmocka unit tests
+bld.SAMBA_BINARY('test_unique_object_sids',
+ source='tests/test_unique_object_sids.c',
+ deps='''
+ talloc
+ samdb
+ cmocka
+ DSDB_MODULE_HELPERS
+ ''',
+ for_selftest=True)
+bld.SAMBA_BINARY('test_encrypted_secrets_tdb',
+ source='tests/test_encrypted_secrets.c',
+ cflags='-DTEST_BE=\"tdb\"',
+ deps='''
+ talloc
+ samba-util
+ samdb-common
+ samdb
+ cmocka
+ gnutls
+ DSDB_MODULE_HELPERS
+ ''',
+ for_selftest=True)
+if conf.env.HAVE_LMDB:
+ bld.SAMBA_BINARY('test_encrypted_secrets_mdb',
+ source='tests/test_encrypted_secrets.c',
+ cflags='-DTEST_BE=\"mdb\"',
+ deps='''
+ talloc
+ samba-util
+ samdb-common
+ samdb
+ cmocka
+ gnutls
+ DSDB_MODULE_HELPERS
+ ''',
+ for_selftest=True)
+
+if bld.AD_DC_BUILD_IS_ENABLED():
+ bld.PROCESS_SEPARATE_RULE("server")
diff --git a/source4/dsdb/samdb/ldb_modules/wscript_build_server b/source4/dsdb/samdb/ldb_modules/wscript_build_server
new file mode 100644
index 0000000..7a63f43
--- /dev/null
+++ b/source4/dsdb/samdb/ldb_modules/wscript_build_server
@@ -0,0 +1,539 @@
+#!/usr/bin/env python
+
+bld.SAMBA_SUBSYSTEM('DSDB_MODULE_HELPERS_AUDIT',
+ source='audit_util.c',
+ autoproto='audit_util_proto.h',
+ deps='DSDB_MODULE_HELPERS audit_logging')
+
+#
+# These tests require JANSSON, so we only build them if we are doing a
+# build on the AD DC (where Jansson is required).
+#
+
+bld.SAMBA_BINARY('test_audit_util',
+ source='tests/test_audit_util.c',
+ deps='''
+ talloc
+ samba-util
+ samdb-common
+ samdb
+ cmocka
+ audit_logging
+ DSDB_MODULE_HELPERS
+ ''',
+ for_selftest=True)
+
+bld.SAMBA_BINARY('test_audit_log',
+ source='tests/test_audit_log.c',
+ deps='''
+ talloc
+ samba-util
+ samdb-common
+ samdb
+ cmocka
+ audit_logging
+ DSDB_MODULE_HELPERS
+ DSDB_MODULE_HELPERS_AUDIT
+ ''',
+ for_selftest=True)
+
+bld.SAMBA_BINARY('test_audit_log_errors',
+ source='tests/test_audit_log_errors.c',
+ deps='''
+ talloc
+ samba-util
+ samdb-common
+ samdb
+ cmocka
+ audit_logging
+ DSDB_MODULE_HELPERS
+ DSDB_MODULE_HELPERS_AUDIT
+ ''',
+ ldflags='''
+ -Wl,--wrap,json_new_object
+ -Wl,--wrap,json_add_version
+ -Wl,--wrap,json_add_timestamp
+ ''',
+ for_selftest=True)
+
+bld.SAMBA_BINARY('test_group_audit',
+ source='tests/test_group_audit.c',
+ deps='''
+ talloc
+ samba-util
+ samdb-common
+ samdb
+ cmocka
+ audit_logging
+ DSDB_MODULE_HELPERS
+ DSDB_MODULE_HELPERS_AUDIT
+ ''',
+ for_selftest=True)
+
+bld.SAMBA_BINARY('test_group_audit_errors',
+ source='tests/test_group_audit_errors.c',
+ deps='''
+ talloc
+ samba-util
+ samdb-common
+ samdb
+ cmocka
+ audit_logging
+ DSDB_MODULE_HELPERS
+ DSDB_MODULE_HELPERS_AUDIT
+ ''',
+ ldflags='''
+ -Wl,--wrap,json_new_object
+ -Wl,--wrap,json_add_version
+ -Wl,--wrap,json_add_timestamp
+ ''',
+ for_selftest=True)
+
+bld.SAMBA_MODULE('ldb_samba_dsdb',
+ source='samba_dsdb.c',
+ subsystem='ldb',
+ init_function='ldb_samba_dsdb_module_init',
+ module_init_name='ldb_init_module',
+ deps='samdb talloc ndr DSDB_MODULE_HELPERS',
+ internal_module=False,
+ )
+
+
+bld.SAMBA_MODULE('ldb_samba_secrets',
+ source='samba_secrets.c',
+ subsystem='ldb',
+ init_function='ldb_samba_secrets_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='samdb talloc ndr'
+ )
+
+
+bld.SAMBA_MODULE('ldb_objectguid',
+ source='objectguid.c',
+ subsystem='ldb',
+ init_function='ldb_objectguid_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='samdb talloc ndr DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_repl_meta_data',
+ source='repl_meta_data.c',
+ subsystem='ldb',
+ init_function='ldb_repl_meta_data_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='samdb talloc ndr NDR_DRSUAPI NDR_DRSBLOBS ndr DSDB_MODULE_HELPERS samba-security'
+ )
+
+
+bld.SAMBA_MODULE('ldb_schema_load',
+ source='schema_load.c',
+ subsystem='ldb',
+ init_function='ldb_schema_load_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='samdb talloc DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_schema_data',
+ source='schema_data.c',
+ subsystem='ldb',
+ init_function='ldb_schema_data_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='samdb talloc DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_samldb',
+ source='samldb.c',
+ subsystem='ldb',
+ init_function='ldb_samldb_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samdb DSDB_MODULE_HELPERS DSDB_MODULE_HELPER_RIDALLOC'
+ )
+
+
+bld.SAMBA_MODULE('ldb_samba3sam',
+ source='samba3sam.c',
+ subsystem='ldb',
+ init_function='ldb_samba3sam_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc ldb smbpasswdparser samba-security NDR_SECURITY'
+ )
+
+
+bld.SAMBA_MODULE('ldb_samba3sid',
+ source='samba3sid.c',
+ subsystem='ldb',
+ init_function='ldb_samba3sid_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc ldb samba-security NDR_SECURITY ldbsamba DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_rootdse',
+ source='rootdse.c',
+ subsystem='ldb',
+ init_function='ldb_rootdse_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samdb MESSAGING samba-security DSDB_MODULE_HELPERS RPC_NDR_IRPC'
+ )
+
+
+bld.SAMBA_MODULE('ldb_password_hash',
+ source='password_hash.c',
+ subsystem='ldb',
+ init_function='ldb_password_hash_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samdb LIBCLI_AUTH NDR_DRSBLOBS authkrb5 krb5 gpgme DSDB_MODULE_HELPERS crypt db-glue'
+ )
+
+
+bld.SAMBA_MODULE('ldb_extended_dn_in',
+ source='extended_dn_in.c',
+ subsystem='ldb',
+ init_function='ldb_extended_dn_in_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='ldb talloc samba-util DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_extended_dn_out',
+ source='extended_dn_out.c',
+ init_function='ldb_extended_dn_out_module_init',
+ module_init_name='ldb_init_module',
+ subsystem='ldb',
+ deps='talloc ndr samba-util samdb DSDB_MODULE_HELPERS',
+ internal_module=False,
+ )
+
+
+bld.SAMBA_MODULE('ldb_extended_dn_store',
+ source='extended_dn_store.c',
+ subsystem='ldb',
+ init_function='ldb_extended_dn_store_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-util samdb DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_show_deleted',
+ source='show_deleted.c',
+ subsystem='ldb',
+ init_function='ldb_show_deleted_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-util DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_partition',
+ source='partition.c partition_init.c partition_metadata.c',
+ autoproto='partition_proto.h',
+ subsystem='ldb',
+ init_function='ldb_partition_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samdb DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_new_partition',
+ source='new_partition.c',
+ subsystem='ldb',
+ init_function='ldb_new_partition_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samdb DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_update_keytab',
+ source='update_keytab.c',
+ subsystem='ldb',
+ init_function='ldb_update_keytab_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-credentials ldb com_err KERBEROS_SRV_KEYTAB SECRETS DSDB_MODULE_HELPERS'
+ )
+
+bld.SAMBA_MODULE('ldb_secrets_tdb_sync',
+ source='secrets_tdb_sync.c',
+ subsystem='ldb',
+ init_function='ldb_secrets_tdb_sync_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc secrets3 DSDB_MODULE_HELPERS dbwrap gssapi'
+ )
+
+
+bld.SAMBA_MODULE('ldb_objectclass',
+ source='objectclass.c',
+ subsystem='ldb',
+ init_function='ldb_objectclass_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samdb DSDB_MODULE_HELPERS samba-util'
+ )
+
+
+bld.SAMBA_MODULE('ldb_objectclass_attrs',
+ source='objectclass_attrs.c',
+ subsystem='ldb',
+ init_function='ldb_objectclass_attrs_module_init',
+ module_init_name='ldb_init_module',
+ deps='talloc samdb DSDB_MODULE_HELPERS samba-util',
+ internal_module=False,
+ )
+
+
+bld.SAMBA_MODULE('ldb_subtree_rename',
+ source='subtree_rename.c',
+ subsystem='ldb',
+ init_function='ldb_subtree_rename_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-util ldb samdb-common DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_subtree_delete',
+ source='subtree_delete.c',
+ subsystem='ldb',
+ init_function='ldb_subtree_delete_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-util DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_linked_attributes',
+ source='linked_attributes.c',
+ subsystem='ldb',
+ init_function='ldb_linked_attributes_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samdb DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_ranged_results',
+ source='ranged_results.c',
+ subsystem='ldb',
+ init_function='ldb_ranged_results_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-util ldb'
+ )
+
+
+bld.SAMBA_MODULE('ldb_anr',
+ source='anr.c',
+ subsystem='ldb',
+ init_function='ldb_anr_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-util samdb'
+ )
+
+
+bld.SAMBA_MODULE('ldb_instancetype',
+ source='instancetype.c',
+ subsystem='ldb',
+ init_function='ldb_instancetype_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-util samdb DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_operational',
+ source='operational.c',
+ subsystem='ldb',
+ init_function='ldb_operational_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-util samdb-common DSDB_MODULE_HELPERS samdb'
+ )
+
+
+bld.SAMBA_MODULE('ldb_descriptor',
+ source='descriptor.c',
+ subsystem='ldb',
+ init_function='ldb_descriptor_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-security NDR_SECURITY samdb DSDB_MODULE_HELPERS'
+ )
+
+
+bld.SAMBA_MODULE('ldb_resolve_oids',
+ source='resolve_oids.c',
+ subsystem='ldb',
+ init_function='ldb_resolve_oids_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='samdb talloc ndr'
+ )
+
+
+bld.SAMBA_MODULE('ldb_acl',
+ source='acl.c',
+ subsystem='ldb',
+ init_function='ldb_acl_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-util samba-security samdb DSDB_MODULE_HELPERS krb5samba'
+ )
+
+
+bld.SAMBA_MODULE('ldb_lazy_commit',
+ source='lazy_commit.c',
+ subsystem='ldb',
+ internal_module=False,
+ module_init_name='ldb_init_module',
+ init_function='ldb_lazy_commit_module_init',
+ deps='samdb DSDB_MODULE_HELPERS'
+ )
+
+bld.SAMBA_MODULE('ldb_aclread',
+ source='acl_read.c',
+ subsystem='ldb',
+ init_function='ldb_aclread_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-security samdb DSDB_MODULE_HELPERS',
+ )
+
+bld.SAMBA_MODULE('ldb_dirsync',
+ source='dirsync.c',
+ subsystem='ldb',
+ init_function='ldb_dirsync_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-security samdb DSDB_MODULE_HELPERS'
+ )
+
+bld.SAMBA_MODULE('ldb_dsdb_notification',
+ source='dsdb_notification.c',
+ subsystem='ldb',
+ init_function='ldb_dsdb_notification_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-security samdb DSDB_MODULE_HELPERS'
+ )
+
+bld.SAMBA_MODULE('ldb_dns_notify',
+ source='dns_notify.c',
+ subsystem='ldb',
+ init_function='ldb_dns_notify_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samdb DSDB_MODULE_HELPERS MESSAGING RPC_NDR_IRPC'
+ )
+
+bld.SAMBA_MODULE('tombstone_reanimate',
+ source='tombstone_reanimate.c',
+ subsystem='ldb',
+ init_function='ldb_tombstone_reanimate_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='talloc samba-util DSDB_MODULE_HELPERS'
+ )
+
+bld.SAMBA_MODULE('ldb_vlv',
+ 'vlv_pagination.c',
+ init_function='ldb_vlv_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='samdb-common',
+ subsystem='ldb'
+ )
+
+bld.SAMBA_MODULE('ldb_paged_results',
+ 'paged_results.c',
+ init_function='ldb_dsdb_paged_results_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='samdb-common',
+ subsystem='ldb'
+ )
+
+bld.SAMBA_MODULE('ldb_unique_object_sids',
+ 'unique_object_sids.c',
+ init_function='ldb_unique_object_sids_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='samdb-common DSDB_MODULE_HELPERS',
+ subsystem='ldb'
+ )
+
+bld.SAMBA_MODULE('ldb_encrypted_secrets',
+ source='encrypted_secrets.c',
+ subsystem='ldb',
+ init_function='ldb_encrypted_secrets_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='''
+ talloc
+ samba-util
+ samdb-common
+ DSDB_MODULE_HELPERS
+ samdb
+ gnutls
+ '''
+ )
+
+bld.SAMBA_MODULE('ldb_audit_log',
+ source='audit_log.c',
+ subsystem='ldb',
+ init_function='ldb_audit_log_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='''
+ audit_logging
+ talloc
+ samba-util
+ samdb-common
+ DSDB_MODULE_HELPERS_AUDIT
+ samdb
+ '''
+ )
+
+bld.SAMBA_MODULE('ldb_group_audit_log',
+ source='group_audit.c',
+ subsystem='ldb',
+ init_function='ldb_group_audit_log_module_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='''
+ audit_logging
+ talloc
+ samba-util
+ samdb-common
+ DSDB_MODULE_HELPERS_AUDIT
+ samdb
+ '''
+ )
+
+
+bld.SAMBA_MODULE('count_attrs',
+ 'count_attrs.c',
+ init_function='ldb_count_attrs_init',
+ module_init_name='ldb_init_module',
+ internal_module=False,
+ deps='samdb-common DSDB_MODULE_HELPERS',
+ subsystem='ldb'
+)