/*
   Unix SMB/CIFS implementation.

   BRIEF FILE DESCRIPTION

   Copyright (C) Kamen Mazdrashki <kamen.mazdrashki@postpath.com> 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.h"
#include "ldb_wrap.h"
#include "lib/cmdline/cmdline.h"
#include "libcli/ldap/ldap_client.h"
#include "torture/torture.h"
#include "torture/ldap/proto.h"

#define torture_assert_res(torture_ctx,expr,cmt,_res) \
	if (!(expr)) { \
		torture_result(torture_ctx, TORTURE_FAIL, __location__": Expression `%s' failed: %s", __STRING(expr), cmt); \
		return _res; \
	}


struct nested_search_context {
	struct torture_context *tctx;
	struct ldb_dn *root_dn;
	struct ldb_context *ldb;
	struct ldb_result *ldb_res;
};

/*
 * ldb_search handler - used to executed a nested
 * ldap search request during LDB_REPLY_ENTRY handling
 */
static int nested_search_callback(struct ldb_request *req,
				  struct ldb_reply *ares)
{
	unsigned int i;
	int res;
	struct nested_search_context *sctx;
	struct ldb_result *ldb_res;
	struct ldb_message *ldb_msg;
	static const char *attrs[] = {
		"rootDomainNamingContext",
		"configurationNamingContext",
		"schemaNamingContext",
		"defaultNamingContext",
		NULL
	};
	enum ldb_reply_type type;

	sctx = talloc_get_type(req->context, struct nested_search_context);

	type = ares->type;
	/* sanity check */
	switch (type) {
	case LDB_REPLY_ENTRY:
		torture_comment(sctx->tctx, "nested_search_callback: LDB_REPLY_ENTRY\n");
		ldb_msg = ares->message;
		torture_assert_res(sctx->tctx, ldb_msg, "ares->message is NULL!", LDB_ERR_OPERATIONS_ERROR);
		torture_assert_res(sctx->tctx, ldb_msg->num_elements, "No elements returned!", LDB_ERR_OPERATIONS_ERROR);
		torture_assert_res(sctx->tctx, ldb_msg->elements, "elements member is NULL!", LDB_ERR_OPERATIONS_ERROR);
		break;
	case LDB_REPLY_DONE:
		torture_comment(sctx->tctx, "nested_search_callback: LDB_REPLY_DONE\n");
		break;
	case LDB_REPLY_REFERRAL:
		torture_comment(sctx->tctx, "nested_search_callback: LDB_REPLY_REFERRAL\n");
		break;
	}

	/* switch context and let default handler do its job */
	req->context = sctx->ldb_res;
	res = ldb_search_default_callback(req, ares);
	req->context = sctx;
	if (res != LDB_SUCCESS) {
		return res;
	}

	/* not a search reply, then get out */
	if (type != LDB_REPLY_ENTRY) {
		return res;
	}


	res = ldb_search(sctx->ldb, sctx, &ldb_res, sctx->root_dn, LDB_SCOPE_BASE, attrs, "(objectClass=*)");
	if (res != LDB_SUCCESS) {
		torture_warning(sctx->tctx,
		                "Search on RootDSE failed in search_entry handler: %s",
		                ldb_errstring(sctx->ldb));
		return LDB_SUCCESS;
	}

	torture_assert_res(sctx->tctx, ldb_res->count == 1, "One message expected here", LDB_ERR_OPERATIONS_ERROR);

	ldb_msg = ldb_res->msgs[0];
	torture_assert_res(sctx->tctx, ldb_msg->num_elements == (ARRAY_SIZE(attrs)-1),
			   "Search returned different number of elts than requested", LDB_ERR_OPERATIONS_ERROR);
	for (i = 0; i < ldb_msg->num_elements; i++) {
		const char *msg;
		struct ldb_message_element *elt1;
		struct ldb_message_element *elt2;

		elt2 = &ldb_msg->elements[i];
		msg = talloc_asprintf(sctx, "Processing element: %s", elt2->name);
		elt1 = ldb_msg_find_element(sctx->ldb_res->msgs[0], elt2->name);
		torture_assert_res(sctx->tctx, elt1, msg, LDB_ERR_OPERATIONS_ERROR);

		/* compare elements */
		torture_assert_res(sctx->tctx, elt2->flags == elt1->flags, "", LDB_ERR_OPERATIONS_ERROR);
		torture_assert_res(sctx->tctx, elt2->num_values == elt1->num_values, "", LDB_ERR_OPERATIONS_ERROR);
	}
	/* TODO: check returned result */

	return LDB_SUCCESS;
}

/**
 * Test nested search execution against RootDSE
 * on remote LDAP server.
 */
bool test_ldap_nested_search(struct torture_context *tctx)
{
	int ret;
	char *url;
	const char *host = torture_setting_string(tctx, "host", NULL);
	struct ldb_request *req;
	struct nested_search_context *sctx;
	static const char *attrs[] = {
/*
		"rootDomainNamingContext",
		"configurationNamingContext",
		"schemaNamingContext",
		"defaultNamingContext",
*/
		"*",
		NULL
	};

	sctx = talloc_zero(tctx, struct nested_search_context);
	torture_assert(tctx, sctx, "Not enough memory");
	sctx->tctx = tctx;

	url = talloc_asprintf(sctx, "ldap://%s/", host);
	if (!url) {
		torture_assert(tctx, url, "Not enough memory");
	}

	torture_comment(tctx, "Connecting to: %s\n", url);
	sctx->ldb = ldb_wrap_connect(sctx, tctx->ev, tctx->lp_ctx, url,
	                             NULL,
	                             samba_cmdline_get_creds(),
	                             0);
	torture_assert(tctx, sctx->ldb, "Failed to create ldb connection");

	/* prepare context for searching */
	sctx->root_dn = ldb_dn_new(sctx, sctx->ldb, NULL);
	sctx->ldb_res = talloc_zero(sctx, struct ldb_result);

	/* build search request */
	ret = ldb_build_search_req(&req,
	                           sctx->ldb,
	                           sctx,
	                           sctx->root_dn, LDB_SCOPE_BASE,
	                           "(objectClass=*)", attrs, NULL,
	                           sctx, nested_search_callback,
	                           NULL);
	if (ret != LDB_SUCCESS) {
		torture_result(tctx, TORTURE_FAIL,
		               __location__ ": Allocating request failed: %s", ldb_errstring(sctx->ldb));
		return false;
	}

	ret = ldb_request(sctx->ldb, req);
	if (ret != LDB_SUCCESS) {
		torture_result(tctx, TORTURE_FAIL,
		               __location__ ": Search failed: %s", ldb_errstring(sctx->ldb));
		return false;
	}

	ret = ldb_wait(req->handle, LDB_WAIT_ALL);
	if (ret != LDB_SUCCESS) {
		torture_result(tctx, TORTURE_FAIL,
		               __location__ ": Search error: %s", ldb_errstring(sctx->ldb));
		return false;
	}

	/* TODO: check returned result */

	talloc_free(sctx);
	return true;
}

