/*
   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);
	if (tmp_ctx == NULL) {
		return ldb_oom(ldb_module_get_ctx(module));
	}

	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);
	if (tmp_ctx == NULL) {
		return ldb_oom(ldb_module_get_ctx(module));
	}

	/* 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 (tmp_ctx == NULL) {
		return ldb_oom(ldb_module_get_ctx(module));
	}

	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 an object given a GUID. This searches across all partitions
 */
int dsdb_module_obj_by_guid(struct ldb_module *module,
			    TALLOC_CTX *mem_ctx,
			    struct ldb_message **_msg,
			    const struct GUID *guid,
			    const char * const *attrs,
			    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,
				 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;
	}

	*_msg = talloc_steal(mem_ctx, res->msgs[0]);

	talloc_free(tmp_ctx);
	return LDB_SUCCESS;
}

/*
  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_message *msg = NULL;
	static const char * const attrs[] = { NULL };
	TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
	int ret;

	ret = dsdb_module_obj_by_guid(module,
				      tmp_ctx,
				      &msg,
				      guid,
				      attrs,
				      parent);
	if (ret != LDB_SUCCESS) {
		talloc_free(tmp_ctx);
		return ret;
	}

	*dn = talloc_steal(mem_ctx, msg->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)
{
	static const char * const 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 * const 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=Configuration 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;
	static const char * const 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) {
		static const char * const 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) {
		*dn = NULL;
		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;
}

/*
 * Return ‘true’ if the caller has system access. The ‘acl’ module passes
 * SYSTEM_CONTROL_STRIP_CRITICAL when it wants to strip the critical flag.
 */
bool dsdb_have_system_access(
	struct ldb_module *module,
	struct ldb_request *req,
	const enum system_control_strip_critical strip_critical)
{
	struct ldb_control *as_system = NULL;

	as_system = ldb_request_get_control(req, LDB_CONTROL_AS_SYSTEM_OID);
	if (as_system != NULL) {
		switch (strip_critical) {
		case SYSTEM_CONTROL_KEEP_CRITICAL:
			break;
		case SYSTEM_CONTROL_STRIP_CRITICAL:
			as_system->critical = 0;
			break;
		}

		return true;
	}

	return dsdb_module_am_system(module);
}

/*
  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 * const 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!\n");
				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=Administrator,cn=users,dc=samba,dc=example,dc=com becomes
   CN=Administrator,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 determine 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;
}

/*
 * Remove all password related attributes.
 */
void dsdb_remove_password_related_attrs(struct ldb_message *msg,
					bool userPassword)
{
	if (userPassword) {
		ldb_msg_remove_attr(msg, "userPassword");
	}
	ldb_msg_remove_attr(msg, "clearTextPassword");
	ldb_msg_remove_attr(msg, "unicodePwd");
	ldb_msg_remove_attr(msg, "ntPwdHistory");
	ldb_msg_remove_attr(msg, "dBCSPwd");
	ldb_msg_remove_attr(msg, "lmPwdHistory");
	ldb_msg_remove_attr(msg, "supplementalCredentials");
	ldb_msg_remove_attr(msg, "pwdLastSet");
}
