/*
 * idmap_adex: Domain search interface
 *
 * Copyright (C) Gerald (Jerry) Carter 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 2 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "includes.h"
#include "idmap_adex.h"

#undef DBGC_CLASS
#define DBGC_CLASS DBGC_IDMAP

struct dc_info {
	struct dc_info *prev, *next;
	char *dns_name;
	struct likewise_cell *domain_cell;
};

static struct dc_info *_dc_server_list = NULL;


/**********************************************************************
 *********************************************************************/

static struct dc_info *dc_list_head(void)
{
	return _dc_server_list;
}

/**********************************************************************
 *********************************************************************/

static NTSTATUS dc_add_domain(const char *domain)
{
	NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
	struct dc_info *dc = NULL;

	if (!domain) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	DEBUG(10,("dc_add_domain: Attempting to add domain %s\n", domain));

	/* Check for duplicates */

	dc = dc_list_head();
	while (dc) {
		if (strequal (dc->dns_name, domain))
			break;
		dc = dc->next;
	}

	if (dc) {
		DEBUG(10,("dc_add_domain: %s already in list\n", domain));
		return NT_STATUS_OK;
	}

	dc = TALLOC_ZERO_P(NULL, struct dc_info);
	BAIL_ON_PTR_ERROR(dc, nt_status);

	dc->dns_name = talloc_strdup(dc, domain);
	BAIL_ON_PTR_ERROR(dc->dns_name, nt_status);

	DLIST_ADD_END(_dc_server_list, dc, struct dc_info*);

	nt_status = NT_STATUS_OK;

	DEBUG(5,("dc_add_domain: Successfully added %s\n", domain));

done:
	if (!NT_STATUS_IS_OK(nt_status)) {
		talloc_destroy(dc);
		DEBUG(0,("LWI: Failed to add new DC connection for %s (%s)\n",
			 domain, nt_errstr(nt_status)));
	}

	return nt_status;
}

/**********************************************************************
 *********************************************************************/

static void dc_server_list_destroy(void)
{
	struct dc_info *dc = dc_list_head();

	while (dc) {
		struct dc_info *p = dc->next;

		cell_destroy(dc->domain_cell);
		talloc_destroy(dc);

		dc = p;
	}

	return;
}


/**********************************************************************
 *********************************************************************/

 NTSTATUS domain_init_list(void)
{
	NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
	struct winbindd_tdc_domain *domains = NULL;
	size_t num_domains = 0;
	int i;

	if (_dc_server_list != NULL) {
		dc_server_list_destroy();
	}

	/* Add our domain */

	nt_status = dc_add_domain(lp_realm());
	BAIL_ON_NTSTATUS_ERROR(nt_status);

	if (!wcache_tdc_fetch_list(&domains, &num_domains)) {
		nt_status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
		BAIL_ON_NTSTATUS_ERROR(nt_status);
	}

	/* Add all domains with an incoming trust path */

	for (i=0; i<num_domains; i++) {
		uint32_t flags = (NETR_TRUST_FLAG_INBOUND|NETR_TRUST_FLAG_IN_FOREST);

		/* We just require one of the flags to be set here */

		if (domains[i].trust_flags & flags) {
			nt_status = dc_add_domain(domains[i].dns_name);
			BAIL_ON_NTSTATUS_ERROR(nt_status);
		}
	}

	nt_status = NT_STATUS_OK;

done:
	if (!NT_STATUS_IS_OK(nt_status)) {
		DEBUG(2,("LWI: Failed to initialize DC list (%s)\n",
			 nt_errstr(nt_status)));
	}

	TALLOC_FREE(domains);

	return nt_status;
}

/********************************************************************
 *******************************************************************/

static NTSTATUS dc_do_search(struct dc_info *dc,
			     const char *search_base,
			     int scope,
			     const char *expr,
			     const char **attrs,
			     LDAPMessage ** msg)
{
	ADS_STATUS status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL);
	NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;

	status = cell_do_search(dc->domain_cell, search_base,
				scope, expr, attrs, msg);
	nt_status = ads_ntstatus(status);

	return nt_status;
}

/**********************************************************************
 *********************************************************************/

static struct dc_info *dc_find_domain(const char *dns_domain)
{
	struct dc_info *dc = dc_list_head();

	if (!dc)
		return NULL;

	while (dc) {
		if (strequal(dc->dns_name, dns_domain)) {
			return dc;
		}

		dc = dc->next;
	}

	return NULL;
}

/**********************************************************************
 *********************************************************************/

 NTSTATUS dc_search_domains(struct likewise_cell **cell,
			    LDAPMessage **msg,
			    const char *dn,
			    const DOM_SID *sid)
{
	NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
	TALLOC_CTX *frame = talloc_stackframe();
	char *dns_domain;
	const char *attrs[] = { "*", NULL };
	struct dc_info *dc = NULL;
	const char *base = NULL;

	if (!dn || !*dn) {
		nt_status = NT_STATUS_INVALID_PARAMETER;
		BAIL_ON_NTSTATUS_ERROR(nt_status);
	}

	dns_domain = cell_dn_to_dns(dn);
	BAIL_ON_PTR_ERROR(dns_domain, nt_status);

	if ((dc = dc_find_domain(dns_domain)) == NULL) {
		nt_status = NT_STATUS_TRUSTED_DOMAIN_FAILURE;
		BAIL_ON_NTSTATUS_ERROR(nt_status);
	}

	/* Reparse the cell settings for the domain if necessary */

	if (!dc->domain_cell) {
		char *base_dn;

		base_dn = ads_build_dn(dc->dns_name);
		BAIL_ON_PTR_ERROR(base_dn, nt_status);

		nt_status = cell_connect_dn(&dc->domain_cell, base_dn);
		SAFE_FREE(base_dn);
		BAIL_ON_NTSTATUS_ERROR(nt_status);

		nt_status = cell_lookup_settings(dc->domain_cell);
		BAIL_ON_NTSTATUS_ERROR(nt_status);

		/* By definition this is already part of a larger
		   forest-wide search scope */

		cell_set_flags(dc->domain_cell, LWCELL_FLAG_SEARCH_FOREST);
	}

	/* Check whether we are operating in non-schema or RFC2307
	   mode */

	if (cell_flags(dc->domain_cell) & LWCELL_FLAG_USE_RFC2307_ATTRS) {
		nt_status = dc_do_search(dc, dn, LDAP_SCOPE_BASE,
					 "(objectclass=*)", attrs, msg);
	} else {
		const char *sid_str = NULL;
		char *filter = NULL;

		sid_str = sid_string_talloc(frame, sid);
		BAIL_ON_PTR_ERROR(sid_str, nt_status);

		filter = talloc_asprintf(frame, "(keywords=backLink=%s)",
					 sid_str);
		BAIL_ON_PTR_ERROR(filter, nt_status);

		base = cell_search_base(dc->domain_cell);
		BAIL_ON_PTR_ERROR(base, nt_status);

		nt_status = dc_do_search(dc, base, LDAP_SCOPE_SUBTREE,
					 filter, attrs, msg);
	}
	BAIL_ON_NTSTATUS_ERROR(nt_status);

	*cell = dc->domain_cell;

done:
	talloc_destroy(CONST_DISCARD(char*, base));
	talloc_destroy(frame);

	return nt_status;
}
