/*
   Unix SMB/CIFS implementation.

   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.h"
#include "../lib/util/util_ldb.h"
#include "dsdb/samdb/samdb.h"
#include "libcli/security/security.h"
#include "librpc/gen_ndr/ndr_security.h"
#include "librpc/gen_ndr/ndr_misc.h"
#include "../libds/common/flags.h"
#include "dsdb/common/proto.h"
#include "param/param.h"
#include "librpc/gen_ndr/ndr_drsblobs.h"
#include "lib/util/tsort.h"
#include "dsdb/common/util.h"
#include "libds/common/flag_mapping.h"
#include "../lib/util/dlinklist.h"
#include "lib/crypto/md4.h"
#include "libcli/ldap/ldap_ndr.h"

#undef strcasecmp

NTSTATUS dsdb_trust_forest_info_from_lsa(TALLOC_CTX *mem_ctx,
				const struct lsa_ForestTrustInformation *lfti,
				struct ForestTrustInfo **_fti)
{
	struct ForestTrustInfo *fti;
	uint32_t i;

	*_fti = NULL;

	fti = talloc_zero(mem_ctx, struct ForestTrustInfo);
	if (fti == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	fti->version = 1;
	fti->count = lfti->count;
	fti->records = talloc_zero_array(mem_ctx,
					 struct ForestTrustInfoRecordArmor,
					 fti->count);
	if (fti->records == NULL) {
		TALLOC_FREE(fti);
		return NT_STATUS_NO_MEMORY;
	}

	for (i = 0; i < fti->count; i++) {
		const struct lsa_ForestTrustRecord *lftr = lfti->entries[i];
		struct ForestTrustInfoRecord *ftr = &fti->records[i].record;
		struct ForestTrustString *str = NULL;
		const struct lsa_StringLarge *lstr = NULL;
		const struct lsa_ForestTrustDomainInfo *linfo = NULL;
		struct ForestTrustDataDomainInfo *info = NULL;

		if (lftr == NULL) {
			TALLOC_FREE(fti);
			return NT_STATUS_INVALID_PARAMETER;
		}

		ftr->flags = lftr->flags;
		ftr->timestamp = lftr->time;
		ftr->type = (enum ForestTrustInfoRecordType)lftr->type;

		switch (lftr->type) {
		case LSA_FOREST_TRUST_TOP_LEVEL_NAME:
			lstr = &lftr->forest_trust_data.top_level_name;
			str = &ftr->data.name;

			str->string = talloc_strdup(mem_ctx, lstr->string);
			if (str->string == NULL) {
				TALLOC_FREE(fti);
				return NT_STATUS_NO_MEMORY;
			}

			break;

		case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
			lstr = &lftr->forest_trust_data.top_level_name_ex;
			str = &ftr->data.name;

			str->string = talloc_strdup(mem_ctx, lstr->string);
			if (str->string == NULL) {
				TALLOC_FREE(fti);
				return NT_STATUS_NO_MEMORY;
			}

			break;

		case LSA_FOREST_TRUST_DOMAIN_INFO:
			linfo = &lftr->forest_trust_data.domain_info;
			info = &ftr->data.info;

			if (linfo->domain_sid == NULL) {
				TALLOC_FREE(fti);
				return NT_STATUS_INVALID_PARAMETER;
			}
			info->sid = *linfo->domain_sid;

			lstr = &linfo->dns_domain_name;
			str = &info->dns_name;
			str->string = talloc_strdup(mem_ctx, lstr->string);
			if (str->string == NULL) {
				TALLOC_FREE(fti);
				return NT_STATUS_NO_MEMORY;
			}

			lstr = &linfo->netbios_domain_name;
			str = &info->netbios_name;
			str->string = talloc_strdup(mem_ctx, lstr->string);
			if (str->string == NULL) {
				TALLOC_FREE(fti);
				return NT_STATUS_NO_MEMORY;
			}

			break;

		default:
			return NT_STATUS_NOT_SUPPORTED;
		}
	}

	*_fti = fti;
	return NT_STATUS_OK;
}

static NTSTATUS dsdb_trust_forest_record_to_lsa(TALLOC_CTX *mem_ctx,
					 const struct ForestTrustInfoRecord *ftr,
					 struct lsa_ForestTrustRecord **_lftr)
{
	struct lsa_ForestTrustRecord *lftr = NULL;
	const struct ForestTrustString *str = NULL;
	struct lsa_StringLarge *lstr = NULL;
	const struct ForestTrustDataDomainInfo *info = NULL;
	struct lsa_ForestTrustDomainInfo *linfo = NULL;

	*_lftr = NULL;

	lftr = talloc_zero(mem_ctx, struct lsa_ForestTrustRecord);
	if (lftr == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	lftr->flags = ftr->flags;
	lftr->time = ftr->timestamp;
	lftr->type = (enum lsa_ForestTrustRecordType)ftr->type;

	switch (lftr->type) {
	case LSA_FOREST_TRUST_TOP_LEVEL_NAME:
		lstr = &lftr->forest_trust_data.top_level_name;
		str = &ftr->data.name;

		lstr->string = talloc_strdup(mem_ctx, str->string);
		if (lstr->string == NULL) {
			TALLOC_FREE(lftr);
			return NT_STATUS_NO_MEMORY;
		}

		break;

	case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
		lstr = &lftr->forest_trust_data.top_level_name_ex;
		str = &ftr->data.name;

		lstr->string = talloc_strdup(mem_ctx, str->string);
		if (lstr->string == NULL) {
			TALLOC_FREE(lftr);
			return NT_STATUS_NO_MEMORY;
		}

		break;

	case LSA_FOREST_TRUST_DOMAIN_INFO:
		linfo = &lftr->forest_trust_data.domain_info;
		info = &ftr->data.info;

		linfo->domain_sid = dom_sid_dup(lftr, &info->sid);
		if (linfo->domain_sid == NULL) {
			TALLOC_FREE(lftr);
			return NT_STATUS_NO_MEMORY;
		}

		lstr = &linfo->dns_domain_name;
		str = &info->dns_name;
		lstr->string = talloc_strdup(mem_ctx, str->string);
		if (lstr->string == NULL) {
			TALLOC_FREE(lftr);
			return NT_STATUS_NO_MEMORY;
		}

		lstr = &linfo->netbios_domain_name;
		str = &info->netbios_name;
		lstr->string = talloc_strdup(mem_ctx, str->string);
		if (lstr->string == NULL) {
			TALLOC_FREE(lftr);
			return NT_STATUS_NO_MEMORY;
		}

		break;

	default:
		return NT_STATUS_NOT_SUPPORTED;
	}

	*_lftr = lftr;
	return NT_STATUS_OK;
}

NTSTATUS dsdb_trust_forest_info_to_lsa(TALLOC_CTX *mem_ctx,
				       const struct ForestTrustInfo *fti,
				       struct lsa_ForestTrustInformation **_lfti)
{
	struct lsa_ForestTrustInformation *lfti;
	uint32_t i;

	*_lfti = NULL;

	if (fti->version != 1) {
		return NT_STATUS_INVALID_PARAMETER;
	}

	lfti = talloc_zero(mem_ctx, struct lsa_ForestTrustInformation);
	if (lfti == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	lfti->count = fti->count;
	lfti->entries = talloc_zero_array(mem_ctx,
					  struct lsa_ForestTrustRecord *,
					  lfti->count);
	if (lfti->entries == NULL) {
		TALLOC_FREE(lfti);
		return NT_STATUS_NO_MEMORY;
	}

	for (i = 0; i < fti->count; i++) {
		struct ForestTrustInfoRecord *ftr = &fti->records[i].record;
		struct lsa_ForestTrustRecord *lftr = NULL;
		NTSTATUS status;

		status = dsdb_trust_forest_record_to_lsa(lfti->entries, ftr,
							 &lftr);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(lfti);
			return NT_STATUS_NO_MEMORY;
		}
		lfti->entries[i] = lftr;
	}

	*_lfti = lfti;
	return NT_STATUS_OK;
}

static NTSTATUS dsdb_trust_forest_info_add_record(struct lsa_ForestTrustInformation *fti,
						  const struct lsa_ForestTrustRecord *ftr)
{
	struct lsa_ForestTrustRecord **es = NULL;
	struct lsa_ForestTrustRecord *e = NULL;
	const struct lsa_StringLarge *dns1 = NULL;
	struct lsa_StringLarge *dns2 = NULL;
	const struct lsa_ForestTrustDomainInfo *d1 = NULL;
	struct lsa_ForestTrustDomainInfo *d2 = NULL;
	size_t len = 0;

	es = talloc_realloc(fti, fti->entries,
			    struct lsa_ForestTrustRecord *,
			    fti->count + 1);
	if (!es) {
		return NT_STATUS_NO_MEMORY;
	}
	fti->entries = es;

	e = talloc_zero(es, struct lsa_ForestTrustRecord);
	if (e == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	e->type = ftr->type;
	e->flags = ftr->flags;
	e->time = ftr->time;

	switch (ftr->type) {
	case LSA_FOREST_TRUST_TOP_LEVEL_NAME:
		dns1 = &ftr->forest_trust_data.top_level_name;
		dns2 = &e->forest_trust_data.top_level_name;
		break;

	case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
		dns1 = &ftr->forest_trust_data.top_level_name_ex;
		dns2 = &e->forest_trust_data.top_level_name_ex;
		break;

	case LSA_FOREST_TRUST_DOMAIN_INFO:
		dns1 = &ftr->forest_trust_data.domain_info.dns_domain_name;
		dns2 = &e->forest_trust_data.domain_info.dns_domain_name;
		d1 = &ftr->forest_trust_data.domain_info;
		d2 = &e->forest_trust_data.domain_info;
		break;
	default:
		return NT_STATUS_INVALID_PARAMETER;
	}

	if (dns1->string == NULL) {
		TALLOC_FREE(e);
		return NT_STATUS_INVALID_PARAMETER;
	}

	len = strlen(dns1->string);
	if (len == 0) {
		TALLOC_FREE(e);
		return NT_STATUS_INVALID_PARAMETER;
	}

	dns2->string = talloc_strdup(e, dns1->string);
	if (dns2->string == NULL) {
		TALLOC_FREE(e);
		return NT_STATUS_NO_MEMORY;
	}

	if (d1 != NULL) {
		const struct lsa_StringLarge *nb1 = &d1->netbios_domain_name;
		struct lsa_StringLarge *nb2 = &d2->netbios_domain_name;

		if (nb1->string == NULL) {
			TALLOC_FREE(e);
			return NT_STATUS_INVALID_PARAMETER;
		}

		len = strlen(nb1->string);
		if (len == 0) {
			TALLOC_FREE(e);
			return NT_STATUS_INVALID_PARAMETER;
		}
		if (len > 15) {
			TALLOC_FREE(e);
			return NT_STATUS_INVALID_PARAMETER;
		}

		nb2->string = talloc_strdup(e, nb1->string);
		if (nb2->string == NULL) {
			TALLOC_FREE(e);
			return NT_STATUS_NO_MEMORY;
		}

		if (d1->domain_sid == NULL) {
			TALLOC_FREE(e);
			return NT_STATUS_INVALID_PARAMETER;
		}

		d2->domain_sid = dom_sid_dup(e, d1->domain_sid);
		if (d2->domain_sid == NULL) {
			TALLOC_FREE(e);
			return NT_STATUS_NO_MEMORY;
		}
	}

	fti->entries[fti->count++] = e;
	return NT_STATUS_OK;
}

static NTSTATUS dsdb_trust_parse_crossref_info(TALLOC_CTX *mem_ctx,
					struct ldb_context *sam_ctx,
					const struct ldb_message *msg,
					struct lsa_TrustDomainInfoInfoEx **_tdo)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct lsa_TrustDomainInfoInfoEx *tdo = NULL;
	const char *dns = NULL;
	const char *netbios = NULL;
	struct ldb_dn *nc_dn = NULL;
	struct dom_sid sid = {
		.num_auths = 0,
	};
	NTSTATUS status;

	*_tdo = NULL;
	tdo = talloc_zero(mem_ctx, struct lsa_TrustDomainInfoInfoEx);
	if (tdo == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}
	talloc_steal(frame, tdo);

	dns = ldb_msg_find_attr_as_string(msg, "dnsRoot", NULL);
	if (dns == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_INTERNAL_DB_CORRUPTION;
	}
	tdo->domain_name.string = talloc_strdup(tdo, dns);
	if (tdo->domain_name.string == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	netbios = ldb_msg_find_attr_as_string(msg, "nETBIOSName", NULL);
	if (netbios == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_INTERNAL_DB_CORRUPTION;
	}
	tdo->netbios_name.string = talloc_strdup(tdo, netbios);
	if (tdo->netbios_name.string == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	nc_dn = samdb_result_dn(sam_ctx, frame, msg, "ncName", NULL);
	if (nc_dn == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_INTERNAL_DB_CORRUPTION;
	}

	status = dsdb_get_extended_dn_sid(nc_dn, &sid, "SID");
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(frame);
		return status;
	}
	tdo->sid = dom_sid_dup(tdo, &sid);
	if (tdo->sid == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	tdo->trust_type = LSA_TRUST_TYPE_UPLEVEL;
	tdo->trust_direction = LSA_TRUST_DIRECTION_INBOUND |
			       LSA_TRUST_DIRECTION_OUTBOUND;
	tdo->trust_attributes = LSA_TRUST_ATTRIBUTE_WITHIN_FOREST;

	*_tdo = talloc_move(mem_ctx, &tdo);
	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

static NTSTATUS dsdb_trust_crossref_tdo_info(TALLOC_CTX *mem_ctx,
			struct ldb_context *sam_ctx,
			struct ldb_dn *domain_dn,
			const char *extra_filter,
			struct lsa_TrustDomainInfoInfoEx **_tdo,
			struct lsa_TrustDomainInfoInfoEx **_root_trust_tdo,
			struct lsa_TrustDomainInfoInfoEx **_trust_parent_tdo)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct lsa_TrustDomainInfoInfoEx *tdo = NULL;
	struct lsa_TrustDomainInfoInfoEx *root_trust_tdo = NULL;
	struct lsa_TrustDomainInfoInfoEx *trust_parent_tdo = NULL;
	struct ldb_dn *partitions_dn = NULL;
	const char * const cross_attrs[] = {
		"dnsRoot",
		"nETBIOSName",
		"nCName",
		"rootTrust",
		"trustParent",
		NULL,
	};
	struct ldb_result *cross_res = NULL;
	struct ldb_message *msg = NULL;
	struct ldb_dn *root_trust_dn = NULL;
	struct ldb_dn *trust_parent_dn = NULL;
	NTSTATUS status;
	int ret;

	if (extra_filter == NULL) {
		extra_filter = "";
	}

	*_tdo = NULL;
	if (_root_trust_tdo != NULL) {
		*_root_trust_tdo = NULL;
	}
	if (_trust_parent_tdo != NULL) {
		*_trust_parent_tdo = NULL;
	}

	partitions_dn = samdb_partitions_dn(sam_ctx, frame);
	if (partitions_dn == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	ret = dsdb_search(sam_ctx, partitions_dn, &cross_res,
			  partitions_dn, LDB_SCOPE_ONELEVEL,
			  cross_attrs,
			  DSDB_SEARCH_ONE_ONLY |
			  DSDB_SEARCH_SHOW_EXTENDED_DN,
			  "(&"
			    "(ncName=%s)"
			    "(objectClass=crossRef)"
			    "(systemFlags:%s:=%u)"
			    "%s"
			  ")",
			  ldb_dn_get_linearized(domain_dn),
			  LDB_OID_COMPARATOR_AND,
			  SYSTEM_FLAG_CR_NTDS_DOMAIN,
			  extra_filter);
	if (ret != LDB_SUCCESS) {
		TALLOC_FREE(frame);
		return dsdb_ldb_err_to_ntstatus(ret);
	}
	msg = cross_res->msgs[0];

	status = dsdb_trust_parse_crossref_info(mem_ctx, sam_ctx, msg, &tdo);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(frame);
		return status;
	}
	talloc_steal(frame, tdo);

	if (_root_trust_tdo != NULL) {
		root_trust_dn = samdb_result_dn(sam_ctx, frame, msg,
						"rootTrust", NULL);
	}
	if (_trust_parent_tdo != NULL) {
		trust_parent_dn = samdb_result_dn(sam_ctx, frame, msg,
						   "trustParent", NULL);
	}

	if (root_trust_dn != NULL) {
		struct ldb_message *root_trust_msg = NULL;

		ret = dsdb_search_one(sam_ctx, frame,
				      &root_trust_msg,
				      root_trust_dn,
				      LDB_SCOPE_BASE,
				      cross_attrs,
				      DSDB_SEARCH_NO_GLOBAL_CATALOG,
				      "(objectClass=crossRef)");
		if (ret != LDB_SUCCESS) {
			status = dsdb_ldb_err_to_ntstatus(ret);
			DEBUG(3, ("Failed to search for %s: %s - %s\n",
				  ldb_dn_get_linearized(root_trust_dn),
				  nt_errstr(status), ldb_errstring(sam_ctx)));
			TALLOC_FREE(frame);
			return status;
		}

		status = dsdb_trust_parse_crossref_info(mem_ctx, sam_ctx,
							root_trust_msg,
							&root_trust_tdo);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}
		talloc_steal(frame, root_trust_tdo);
	}

	if (trust_parent_dn != NULL) {
		struct ldb_message *trust_parent_msg = NULL;

		ret = dsdb_search_one(sam_ctx, frame,
				      &trust_parent_msg,
				      trust_parent_dn,
				      LDB_SCOPE_BASE,
				      cross_attrs,
				      DSDB_SEARCH_NO_GLOBAL_CATALOG,
				      "(objectClass=crossRef)");
		if (ret != LDB_SUCCESS) {
			status = dsdb_ldb_err_to_ntstatus(ret);
			DEBUG(3, ("Failed to search for %s: %s - %s\n",
				  ldb_dn_get_linearized(trust_parent_dn),
				  nt_errstr(status), ldb_errstring(sam_ctx)));
			TALLOC_FREE(frame);
			return status;
		}

		status = dsdb_trust_parse_crossref_info(mem_ctx, sam_ctx,
							trust_parent_msg,
							&trust_parent_tdo);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}
		talloc_steal(frame, trust_parent_tdo);
	}

	*_tdo = talloc_move(mem_ctx, &tdo);
	if (_root_trust_tdo != NULL) {
		*_root_trust_tdo = talloc_move(mem_ctx, &root_trust_tdo);
	}
	if (_trust_parent_tdo != NULL) {
		*_trust_parent_tdo = talloc_move(mem_ctx, &trust_parent_tdo);
	}
	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

#define DNS_CMP_FIRST_IS_CHILD -2
#define DNS_CMP_FIRST_IS_LESS -1
#define DNS_CMP_MATCH 0
#define DNS_CMP_SECOND_IS_LESS 1
#define DNS_CMP_SECOND_IS_CHILD 2

#define DNS_CMP_IS_NO_MATCH(__cmp) \
	((__cmp == DNS_CMP_FIRST_IS_LESS) || (__cmp == DNS_CMP_SECOND_IS_LESS))

/*
 * this function assumes names are well formed DNS names.
 * it doesn't validate them
 *
 * It allows strings up to a length of UINT16_MAX - 1
 * with up to UINT8_MAX components. On overflow this
 * just returns the result of strcasecmp_m().
 *
 * Trailing dots (only one) are ignored.
 *
 * The DNS names are compared per component, starting from
 * the last one.
 *
 * The function is usable in a sort, but the return value contains more
 * information than a simple comparison. There are 5 return values, defined
 * above.
 *
 * DNS_CMP_FIRST_IS_CHILD (-2) means the first argument is a sub-domain of the
 * second. e.g. dns_cmp("foo.example.org", "example.org")
 *
 * DNS_CMP_FIRST_IS_LESS (-1) means the first argument sorts before the
 * second, but is not a sub-domain. e.g. dns_cmp("eggsample.org", "example.org").
 *
 * DNS_CMP_SECOND_IS_CHILD (+2) and DNS_CMP_SECOND_IS_LESS (+1) have the
 * similar expected meanings. DNS_CMP_MATCH (0) means equality.
 *
 * NULL values are the parent of all addresses, which means comparisons
 * between a string and NULL will return +2 or -2.
 */
static int dns_cmp(const char *s1, const char *s2)
{
	size_t l1 = 0;
	const char *p1 = NULL;
	size_t num_comp1 = 0;
	uint16_t comp1[UINT8_MAX] = {0};
	size_t l2 = 0;
	const char *p2 = NULL;
	size_t num_comp2 = 0;
	uint16_t comp2[UINT8_MAX] = {0};
	size_t i;

	if (s1 == s2) {
		/* this includes the both NULL case */
		return DNS_CMP_MATCH;
	}
	if (s1 == NULL) {
		return DNS_CMP_SECOND_IS_CHILD;
	}
	if (s2 == NULL) {
		return DNS_CMP_FIRST_IS_CHILD;
	}

	l1 = strlen(s1);
	l2 = strlen(s2);

	/*
	 * trailing '.' are ignored.
	 */
	if (l1 > 1 && s1[l1 - 1] == '.') {
		l1--;
	}
	if (l2 > 1 && s2[l2 - 1] == '.') {
		l2--;
	}

	for (i = 0; i < ARRAY_SIZE(comp1); i++) {
		char *p;

		if (i == 0) {
			p1 = s1;

			if (l1 == 0 || l1 >= UINT16_MAX) {
				/* just use one single component on overflow */
				break;
			}
		}

		comp1[num_comp1++] = PTR_DIFF(p1, s1);

		p = strchr_m(p1, '.');
		if (p == NULL) {
			p1 = NULL;
			break;
		}

		p1 = p + 1;
	}

	if (p1 != NULL) {
		/* just use one single component on overflow */
		num_comp1 = 0;
		comp1[num_comp1++] = 0;
		p1 = NULL;
	}

	for (i = 0; i < ARRAY_SIZE(comp2); i++) {
		char *p;

		if (i == 0) {
			p2 = s2;

			if (l2 == 0 || l2 >= UINT16_MAX) {
				/* just use one single component on overflow */
				break;
			}
		}

		comp2[num_comp2++] = PTR_DIFF(p2, s2);

		p = strchr_m(p2, '.');
		if (p == NULL) {
			p2 = NULL;
			break;
		}

		p2 = p + 1;
	}

	if (p2 != NULL) {
		/* just use one single component on overflow */
		num_comp2 = 0;
		comp2[num_comp2++] = 0;
		p2 = NULL;
	}

	for (i = 0; i < UINT8_MAX; i++) {
		int cmp;

		if (i < num_comp1) {
			size_t idx = num_comp1 - (i + 1);
			p1 = s1 + comp1[idx];
		} else {
			p1 = NULL;
		}

		if (i < num_comp2) {
			size_t idx = num_comp2 - (i + 1);
			p2 = s2 + comp2[idx];
		} else {
			p2 = NULL;
		}

		if (p1 == NULL && p2 == NULL) {
			return DNS_CMP_MATCH;
		}
		if (p1 != NULL && p2 == NULL) {
			return DNS_CMP_FIRST_IS_CHILD;
		}
		if (p1 == NULL && p2 != NULL) {
			return DNS_CMP_SECOND_IS_CHILD;
		}

		cmp = strcasecmp_m(p1, p2);
		if (cmp < 0) {
			return DNS_CMP_FIRST_IS_LESS;
		}
		if (cmp > 0) {
			return DNS_CMP_SECOND_IS_LESS;
		}
	}

	smb_panic(__location__);
	return -1;
}

static int dsdb_trust_find_tln_match_internal(const struct lsa_ForestTrustInformation *info,
					      enum lsa_ForestTrustRecordType type,
					      uint32_t disable_mask,
					      const char *tln)
{
	uint32_t i;

	for (i = 0; i < info->count; i++) {
		struct lsa_ForestTrustRecord *e = info->entries[i];
		struct lsa_StringLarge *t = NULL;
		int cmp;

		if (e == NULL) {
			continue;
		}

		if (e->type != type) {
			continue;
		}

		if (e->flags & disable_mask) {
			continue;
		}

		switch (type) {
		case LSA_FOREST_TRUST_TOP_LEVEL_NAME:
			t = &e->forest_trust_data.top_level_name;
			break;
		case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
			t = &e->forest_trust_data.top_level_name_ex;
			break;
		default:
			break;
		}

		if (t == NULL) {
			continue;
		}

		cmp = dns_cmp(tln, t->string);
		switch (cmp) {
		case DNS_CMP_MATCH:
		case DNS_CMP_FIRST_IS_CHILD:
			return i;
		}
	}

	return -1;
}

static bool dsdb_trust_find_tln_match(const struct lsa_ForestTrustInformation *info,
				      const char *tln)
{
	int m;

	m = dsdb_trust_find_tln_match_internal(info,
					       LSA_FOREST_TRUST_TOP_LEVEL_NAME,
					       LSA_TLN_DISABLED_MASK,
					       tln);
	if (m != -1) {
		return true;
	}

	return false;
}

static bool dsdb_trust_find_tln_ex_match(const struct lsa_ForestTrustInformation *info,
					 const char *tln)
{
	int m;

	m = dsdb_trust_find_tln_match_internal(info,
					       LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX,
					       0,
					       tln);
	if (m != -1) {
		return true;
	}

	return false;
}

NTSTATUS dsdb_trust_local_tdo_info(TALLOC_CTX *mem_ctx,
				   struct ldb_context *sam_ctx,
				   struct lsa_TrustDomainInfoInfoEx **_tdo)
{
	struct ldb_dn *domain_dn = NULL;

	domain_dn = ldb_get_default_basedn(sam_ctx);
	if (domain_dn == NULL) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	return dsdb_trust_crossref_tdo_info(mem_ctx, sam_ctx,
					    domain_dn, NULL,
					    _tdo, NULL, NULL);
}

NTSTATUS dsdb_trust_xref_tdo_info(TALLOC_CTX *mem_ctx,
				  struct ldb_context *sam_ctx,
				  struct lsa_TrustDomainInfoInfoEx **_tdo)
{
	/*
	 * The extra filter makes sure we only find the forest root domain
	 */
	const char *extra_filter = "(!(|(rootTrust=*)(trustParent=*)))";
	struct ldb_dn *domain_dn = NULL;

	domain_dn = ldb_get_default_basedn(sam_ctx);
	if (domain_dn == NULL) {
		return NT_STATUS_INTERNAL_ERROR;
	}

	return dsdb_trust_crossref_tdo_info(mem_ctx, sam_ctx,
					    domain_dn, extra_filter,
					    _tdo, NULL, NULL);
}

static int dsdb_trust_xref_sort_msgs(struct ldb_message **_m1,
				     struct ldb_message **_m2)
{
	struct ldb_message *m1 = *_m1;
	struct ldb_message *m2 = *_m2;
	const char *dns1 = NULL;
	const char *dns2 = NULL;
	int cmp;
	struct ldb_message_element *rootTrust1 = NULL;
	struct ldb_message_element *trustParent1 = NULL;
	struct ldb_message_element *rootTrust2 = NULL;
	struct ldb_message_element *trustParent2 = NULL;

	dns1 = ldb_msg_find_attr_as_string(m1, "dnsRoot", NULL);
	dns2 = ldb_msg_find_attr_as_string(m2, "dnsRoot", NULL);

	cmp = dns_cmp(dns1, dns2);
	switch (cmp) {
	case DNS_CMP_FIRST_IS_CHILD:
		return -1;
	case DNS_CMP_SECOND_IS_CHILD:
		return 1;
	}

	rootTrust1 = ldb_msg_find_element(m1, "rootTrust");
	trustParent1 = ldb_msg_find_element(m1, "trustParent");
	rootTrust2 = ldb_msg_find_element(m2, "rootTrust");
	trustParent2 = ldb_msg_find_element(m2, "trustParent");

	if (rootTrust1 == NULL && trustParent1 == NULL) {
		/* m1 is the forest root */
		return -1;
	}
	if (rootTrust2 == NULL && trustParent2 == NULL) {
		/* m2 is the forest root */
		return 1;
	}

	return cmp;
}

static int dsdb_trust_xref_sort_vals(struct ldb_val *v1,
				     struct ldb_val *v2)
{
	const char *dns1 = (const char *)v1->data;
	const char *dns2 = (const char *)v2->data;

	return dns_cmp(dns1, dns2);
}

NTSTATUS dsdb_trust_xref_forest_info(TALLOC_CTX *mem_ctx,
				     struct ldb_context *sam_ctx,
				     struct lsa_ForestTrustInformation **_info)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct lsa_ForestTrustInformation *info = NULL;
	struct ldb_dn *partitions_dn = NULL;
	const char * const cross_attrs1[] = {
		"uPNSuffixes",
		"msDS-SPNSuffixes",
		NULL,
	};
	struct ldb_result *cross_res1 = NULL;
	struct ldb_message_element *upn_el = NULL;
	struct ldb_message_element *spn_el = NULL;
	struct ldb_message *tln_msg = NULL;
	struct ldb_message_element *tln_el = NULL;
	const char * const cross_attrs2[] = {
		"dnsRoot",
		"nETBIOSName",
		"nCName",
		"rootTrust",
		"trustParent",
		NULL,
	};
	struct ldb_result *cross_res2 = NULL;
	int ret;
	unsigned int i;
	bool restart = false;

	*_info = NULL;
	info = talloc_zero(mem_ctx, struct lsa_ForestTrustInformation);
	if (info == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}
	talloc_steal(frame, info);

	partitions_dn = samdb_partitions_dn(sam_ctx, frame);
	if (partitions_dn == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	ret = dsdb_search_dn(sam_ctx, partitions_dn, &cross_res1,
			     partitions_dn, cross_attrs1, 0);
	if (ret != LDB_SUCCESS) {
		TALLOC_FREE(frame);
		return dsdb_ldb_err_to_ntstatus(ret);
	}

	ret = dsdb_search(sam_ctx, partitions_dn, &cross_res2,
			  partitions_dn, LDB_SCOPE_ONELEVEL,
			  cross_attrs2,
			  DSDB_SEARCH_SHOW_EXTENDED_DN,
			  "(&(objectClass=crossRef)"
			   "(systemFlags:%s:=%u))",
			  LDB_OID_COMPARATOR_AND,
			  SYSTEM_FLAG_CR_NTDS_DOMAIN);
	if (ret != LDB_SUCCESS) {
		TALLOC_FREE(frame);
		return dsdb_ldb_err_to_ntstatus(ret);
	}

	/*
	 * Sort the domains as trees, starting with the forest root
	 */
	TYPESAFE_QSORT(cross_res2->msgs, cross_res2->count,
		       dsdb_trust_xref_sort_msgs);

	upn_el = ldb_msg_find_element(cross_res1->msgs[0], "uPNSuffixes");
	if (upn_el != NULL) {
		upn_el->name = "__tln__";
	}
	spn_el = ldb_msg_find_element(cross_res1->msgs[0], "msDS-SPNSuffixes");
	if (spn_el != NULL) {
		spn_el->name = "__tln__";
	}
	ret = ldb_msg_normalize(sam_ctx, frame, cross_res1->msgs[0], &tln_msg);
	if (ret != LDB_SUCCESS) {
		TALLOC_FREE(frame);
		return dsdb_ldb_err_to_ntstatus(ret);
	}
	tln_el = ldb_msg_find_element(tln_msg, "__tln__");
	if (tln_el != NULL) {
		/*
		 * Sort the domains as trees
		 */
		TYPESAFE_QSORT(tln_el->values, tln_el->num_values,
			       dsdb_trust_xref_sort_vals);
	}

	for (i=0; i < cross_res2->count; i++) {
		struct ldb_message *m = cross_res2->msgs[i];
		const char *dns = NULL;
		const char *netbios = NULL;
		struct ldb_dn *nc_dn = NULL;
		struct dom_sid sid = {
			.num_auths = 0,
		};
		struct lsa_ForestTrustRecord e = {
			.flags = 0,
		};
		struct lsa_ForestTrustDomainInfo *d = NULL;
		struct lsa_StringLarge *t = NULL;
		bool match = false;
		NTSTATUS status;

		dns = ldb_msg_find_attr_as_string(m, "dnsRoot", NULL);
		if (dns == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_INTERNAL_DB_CORRUPTION;
		}

		netbios = ldb_msg_find_attr_as_string(m, "nETBIOSName", NULL);
		if (netbios == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_INTERNAL_DB_CORRUPTION;
		}

		nc_dn = samdb_result_dn(sam_ctx, m, m, "ncName", NULL);
		if (nc_dn == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_INTERNAL_DB_CORRUPTION;
		}

		status = dsdb_get_extended_dn_sid(nc_dn, &sid, "SID");
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}

		match = dsdb_trust_find_tln_match(info, dns);
		if (!match) {
			/*
			 * First the TOP_LEVEL_NAME, if required
			 */
			e = (struct lsa_ForestTrustRecord) {
				.flags = 0,
				.type = LSA_FOREST_TRUST_TOP_LEVEL_NAME,
				.time = 0, /* so far always 0 in traces. */
			};

			t = &e.forest_trust_data.top_level_name;
			t->string = dns;

			status = dsdb_trust_forest_info_add_record(info, &e);
			if (!NT_STATUS_IS_OK(status)) {
				TALLOC_FREE(frame);
				return status;
			}
		}

		/*
		 * Then the DOMAIN_INFO
		 */
		e = (struct lsa_ForestTrustRecord) {
			.flags = 0,
			.type = LSA_FOREST_TRUST_DOMAIN_INFO,
			.time = 0, /* so far always 0 in traces. */
		};
		d = &e.forest_trust_data.domain_info;
		d->domain_sid = &sid;
		d->dns_domain_name.string = dns;
		d->netbios_domain_name.string = netbios;

		status = dsdb_trust_forest_info_add_record(info, &e);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}
	}

	for (i=0; (tln_el != NULL) && i < tln_el->num_values; i++) {
		const struct ldb_val *v = &tln_el->values[i];
		const char *dns = (const char *)v->data;
		struct lsa_ForestTrustRecord e = {
			.flags = 0,
		};
		struct lsa_StringLarge *t = NULL;
		bool match = false;
		NTSTATUS status;

		if (dns == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_INTERNAL_DB_CORRUPTION;
		}

		match = dsdb_trust_find_tln_match(info, dns);
		if (match) {
			continue;
		}

		/*
		 * an additional the TOP_LEVEL_NAME
		 */
		e = (struct lsa_ForestTrustRecord) {
			.flags = 0,
			.type = LSA_FOREST_TRUST_TOP_LEVEL_NAME,
			.time = 0, /* so far always 0 in traces. */
		};
		t = &e.forest_trust_data.top_level_name;
		t->string = dns;

		status = dsdb_trust_forest_info_add_record(info, &e);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}
	}

	for (i=0; i < info->count; restart ? i=0 : i++) {
		struct lsa_ForestTrustRecord *tr = info->entries[i];
		const struct lsa_StringLarge *ts = NULL;
		uint32_t c;

		restart = false;

		if (tr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) {
			continue;
		}

		ts = &tr->forest_trust_data.top_level_name;

		for (c = i + 1; c < info->count; c++) {
			struct lsa_ForestTrustRecord *cr = info->entries[c];
			const struct lsa_StringLarge *cs = NULL;
			uint32_t j;
			int cmp;

			if (cr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) {
				continue;
			}

			cs = &cr->forest_trust_data.top_level_name;

			cmp = dns_cmp(ts->string, cs->string);
			if (DNS_CMP_IS_NO_MATCH(cmp)) {
				continue;
			}
			if (cmp != DNS_CMP_FIRST_IS_CHILD) {
				/* can't happen ... */
				continue;
			}

			ts = NULL;
			tr = NULL;
			TALLOC_FREE(info->entries[i]);
			info->entries[i] = info->entries[c];

			for (j = c + 1; j < info->count; j++) {
				info->entries[j-1] = info->entries[j];
			}
			info->count -= 1;
			restart = true;
			break;
		}
	}

	*_info = talloc_move(mem_ctx, &info);
	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

NTSTATUS dsdb_trust_parse_tdo_info(TALLOC_CTX *mem_ctx,
				   struct ldb_message *m,
				   struct lsa_TrustDomainInfoInfoEx **_tdo)
{
	struct lsa_TrustDomainInfoInfoEx *tdo = NULL;
	const char *dns = NULL;
	const char *netbios = NULL;

	*_tdo = NULL;

	tdo = talloc_zero(mem_ctx, struct lsa_TrustDomainInfoInfoEx);
	if (tdo == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	dns = ldb_msg_find_attr_as_string(m, "trustPartner", NULL);
	if (dns == NULL) {
		TALLOC_FREE(tdo);
		return NT_STATUS_INTERNAL_DB_CORRUPTION;
	}
	tdo->domain_name.string = talloc_strdup(tdo, dns);
	if (tdo->domain_name.string == NULL) {
		TALLOC_FREE(tdo);
		return NT_STATUS_NO_MEMORY;
	}

	netbios = ldb_msg_find_attr_as_string(m, "flatName", NULL);
	if (netbios == NULL) {
		TALLOC_FREE(tdo);
		return NT_STATUS_INTERNAL_DB_CORRUPTION;
	}
	tdo->netbios_name.string = talloc_strdup(tdo, netbios);
	if (tdo->netbios_name.string == NULL) {
		TALLOC_FREE(tdo);
		return NT_STATUS_NO_MEMORY;
	}

	tdo->sid = samdb_result_dom_sid(tdo, m, "securityIdentifier");
	if (tdo->sid == NULL) {
		TALLOC_FREE(tdo);
		return NT_STATUS_INTERNAL_DB_CORRUPTION;
	}

	tdo->trust_type = ldb_msg_find_attr_as_uint(m, "trustType", 0);
	tdo->trust_direction = ldb_msg_find_attr_as_uint(m, "trustDirection", 0);
	tdo->trust_attributes = ldb_msg_find_attr_as_uint(m, "trustAttributes", 0);

	*_tdo = tdo;
	return NT_STATUS_OK;
}

NTSTATUS dsdb_trust_parse_forest_info(TALLOC_CTX *mem_ctx,
				      struct ldb_message *m,
				      struct ForestTrustInfo **_fti)
{
	const struct ldb_val *ft_blob = NULL;
	struct ForestTrustInfo *fti = NULL;
	enum ndr_err_code ndr_err;

	*_fti = NULL;

	ft_blob = ldb_msg_find_ldb_val(m, "msDS-TrustForestTrustInfo");
	if (ft_blob == NULL) {
		return NT_STATUS_NOT_FOUND;
	}

	fti = talloc_zero(mem_ctx, struct ForestTrustInfo);
	if (fti == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	/* ldb_val is equivalent to DATA_BLOB */
	ndr_err = ndr_pull_struct_blob_all(ft_blob, fti, fti,
				(ndr_pull_flags_fn_t)ndr_pull_ForestTrustInfo);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		TALLOC_FREE(fti);
		return NT_STATUS_INTERNAL_DB_CORRUPTION;
	}

	*_fti = fti;
	return NT_STATUS_OK;
}

NTSTATUS dsdb_trust_normalize_forest_info_step1(TALLOC_CTX *mem_ctx,
				const struct lsa_ForestTrustInformation *gfti,
				struct lsa_ForestTrustInformation **_nfti)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct lsa_ForestTrustInformation *nfti;
	uint32_t n;

	*_nfti = NULL;

	nfti = talloc_zero(mem_ctx, struct lsa_ForestTrustInformation);
	if (nfti == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}
	talloc_steal(frame, nfti);

	/*
	 * First we copy every record and remove possible trailing dots
	 * from dns names.
	 *
	 * We also NULL out duplicates. The first one wins and
	 * we keep 'count' as is. This is required in order to
	 * provide the correct index for collision records.
	 */
	for (n = 0; n < gfti->count; n++) {
		const struct lsa_ForestTrustRecord *gftr = gfti->entries[n];
		struct lsa_ForestTrustRecord *nftr = NULL;
		struct lsa_ForestTrustDomainInfo *ninfo = NULL;
		struct lsa_StringLarge *ntln = NULL;
		struct lsa_StringLarge *nnb = NULL;
		struct dom_sid *nsid = NULL;
		NTSTATUS status;
		size_t len = 0;
		char *p = NULL;
		uint32_t c;

		if (gftr == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_INVALID_PARAMETER;
		}

		status = dsdb_trust_forest_info_add_record(nfti, gftr);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}

		nftr = nfti->entries[n];

		switch (nftr->type) {
		case LSA_FOREST_TRUST_TOP_LEVEL_NAME:
			ntln = &nftr->forest_trust_data.top_level_name;
			break;

		case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
			ntln = &nftr->forest_trust_data.top_level_name_ex;
			break;

		case LSA_FOREST_TRUST_DOMAIN_INFO:
			ninfo = &nftr->forest_trust_data.domain_info;
			ntln = &ninfo->dns_domain_name;
			nnb = &ninfo->netbios_domain_name;
			nsid = ninfo->domain_sid;
			break;

		default:
			TALLOC_FREE(frame);
			return NT_STATUS_INVALID_PARAMETER;
		}

		/*
		 * We remove one trailing '.' before checking
		 * for invalid dots.
		 *
		 * domain.com.  becomes domain.com
		 * domain.com.. becomes domain.com.
		 *
		 * Then the following is invalid:
		 *
		 * domain..com
		 * .domain.com
		 * domain.com.
		 */
		len = strlen(ntln->string);
		if (len > 1 && ntln->string[len - 1] == '.') {
			const char *cp = &ntln->string[len - 1];
			p = discard_const_p(char, cp);
			*p= '\0';
		}
		if (ntln->string[0] == '.') {
			TALLOC_FREE(frame);
			return NT_STATUS_INVALID_PARAMETER;
		}
		p = strstr_m(ntln->string, "..");
		if (p != NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_INVALID_PARAMETER;
		}

		for (c = 0; c < n; c++) {
			const struct lsa_ForestTrustRecord *cftr = nfti->entries[c];
			const struct lsa_ForestTrustDomainInfo *cinfo = NULL;
			const struct lsa_StringLarge *ctln = NULL;
			const struct lsa_StringLarge *cnb = NULL;
			const struct dom_sid *csid = NULL;
			int cmp;

			if (cftr == NULL) {
				continue;
			}

			if (cftr->type != nftr->type) {
				continue;
			}

			switch (cftr->type) {
			case LSA_FOREST_TRUST_TOP_LEVEL_NAME:
				ctln = &cftr->forest_trust_data.top_level_name;
				break;

			case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
				ctln = &cftr->forest_trust_data.top_level_name_ex;
				break;

			case LSA_FOREST_TRUST_DOMAIN_INFO:
				cinfo = &cftr->forest_trust_data.domain_info;
				ctln = &cinfo->dns_domain_name;
				cnb = &cinfo->netbios_domain_name;
				csid = cinfo->domain_sid;
				break;

			default:
				TALLOC_FREE(frame);
				return NT_STATUS_INVALID_PARAMETER;
			}

			cmp = dns_cmp(ntln->string, ctln->string);
			if (cmp == DNS_CMP_MATCH) {
				nftr = NULL;
				TALLOC_FREE(nfti->entries[n]);
				break;
			}

			if (cinfo == NULL) {
				continue;
			}

			cmp = strcasecmp_m(nnb->string, cnb->string);
			if (cmp == 0) {
				nftr = NULL;
				TALLOC_FREE(nfti->entries[n]);
				break;
			}

			cmp = dom_sid_compare(nsid, csid);
			if (cmp == 0) {
				nftr = NULL;
				TALLOC_FREE(nfti->entries[n]);
				break;
			}
		}
	}

	/*
	 * Now we check that only true top level names are provided
	 */
	for (n = 0; n < nfti->count; n++) {
		const struct lsa_ForestTrustRecord *nftr = nfti->entries[n];
		const struct lsa_StringLarge *ntln = NULL;
		uint32_t c;

		if (nftr == NULL) {
			continue;
		}

		if (nftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) {
			continue;
		}

		ntln = &nftr->forest_trust_data.top_level_name;

		for (c = 0; c < nfti->count; c++) {
			const struct lsa_ForestTrustRecord *cftr = nfti->entries[c];
			const struct lsa_StringLarge *ctln = NULL;
			int cmp;

			if (cftr == NULL) {
				continue;
			}

			if (cftr == nftr) {
				continue;
			}

			if (cftr->type != nftr->type) {
				continue;
			}

			ctln = &cftr->forest_trust_data.top_level_name;

			cmp = dns_cmp(ntln->string, ctln->string);
			if (DNS_CMP_IS_NO_MATCH(cmp)) {
				continue;
			}

			TALLOC_FREE(frame);
			return NT_STATUS_INVALID_PARAMETER;
		}
	}

	/*
	 * Now we check that only true sub level excludes are provided
	 */
	for (n = 0; n < nfti->count; n++) {
		const struct lsa_ForestTrustRecord *nftr = nfti->entries[n];
		const struct lsa_StringLarge *ntln = NULL;
		uint32_t c;
		bool found_tln = false;

		if (nftr == NULL) {
			continue;
		}

		if (nftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX) {
			continue;
		}

		ntln = &nftr->forest_trust_data.top_level_name;

		for (c = 0; c < nfti->count; c++) {
			const struct lsa_ForestTrustRecord *cftr = nfti->entries[c];
			const struct lsa_StringLarge *ctln = NULL;
			int cmp;

			if (cftr == NULL) {
				continue;
			}

			if (cftr == nftr) {
				continue;
			}

			if (cftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) {
				continue;
			}

			ctln = &cftr->forest_trust_data.top_level_name;

			cmp = dns_cmp(ntln->string, ctln->string);
			if (cmp == DNS_CMP_FIRST_IS_CHILD) {
				found_tln = true;
				break;
			}
		}

		if (found_tln) {
			continue;
		}

		TALLOC_FREE(frame);
		return NT_STATUS_INVALID_PARAMETER;
	}

	/*
	 * Now we check that there's a top level name for each domain
	 */
	for (n = 0; n < nfti->count; n++) {
		const struct lsa_ForestTrustRecord *nftr = nfti->entries[n];
		const struct lsa_ForestTrustDomainInfo *ninfo = NULL;
		const struct lsa_StringLarge *ntln = NULL;
		uint32_t c;
		bool found_tln = false;

		if (nftr == NULL) {
			continue;
		}

		if (nftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) {
			continue;
		}

		ninfo = &nftr->forest_trust_data.domain_info;
		ntln = &ninfo->dns_domain_name;

		for (c = 0; c < nfti->count; c++) {
			const struct lsa_ForestTrustRecord *cftr = nfti->entries[c];
			const struct lsa_StringLarge *ctln = NULL;
			int cmp;

			if (cftr == NULL) {
				continue;
			}

			if (cftr == nftr) {
				continue;
			}

			if (cftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) {
				continue;
			}

			ctln = &cftr->forest_trust_data.top_level_name;

			cmp = dns_cmp(ntln->string, ctln->string);
			if (cmp == DNS_CMP_MATCH) {
				found_tln = true;
				break;
			}
			if (cmp == DNS_CMP_FIRST_IS_CHILD) {
				found_tln = true;
				break;
			}
		}

		if (found_tln) {
			continue;
		}

		TALLOC_FREE(frame);
		return NT_STATUS_INVALID_PARAMETER;
	}

	*_nfti = talloc_move(mem_ctx, &nfti);
	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

NTSTATUS dsdb_trust_normalize_forest_info_step2(TALLOC_CTX *mem_ctx,
				const struct lsa_ForestTrustInformation *gfti,
				struct lsa_ForestTrustInformation **_nfti)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct timeval tv = timeval_current();
	NTTIME now = timeval_to_nttime(&tv);
	struct lsa_ForestTrustInformation *nfti;
	uint32_t g;

	*_nfti = NULL;

	nfti = talloc_zero(mem_ctx, struct lsa_ForestTrustInformation);
	if (nfti == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}
	talloc_steal(frame, nfti);

	/*
	 * Now we add TOP_LEVEL_NAME[_EX] in reverse order
	 * followed by LSA_FOREST_TRUST_DOMAIN_INFO in reverse order.
	 *
	 * This also removes the possible NULL entries generated in step1.
	 */

	for (g = 0; g < gfti->count; g++) {
		const struct lsa_ForestTrustRecord *gftr = gfti->entries[gfti->count - (g+1)];
		struct lsa_ForestTrustRecord tftr;
		bool skip = false;
		NTSTATUS status;

		if (gftr == NULL) {
			continue;
		}

		switch (gftr->type) {
		case LSA_FOREST_TRUST_TOP_LEVEL_NAME:
		case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
			break;

		case LSA_FOREST_TRUST_DOMAIN_INFO:
			skip = true;
			break;

		default:
			TALLOC_FREE(frame);
			return NT_STATUS_INVALID_PARAMETER;
		}

		if (skip) {
			continue;
		}

		/* make a copy in order to update the time. */
		tftr = *gftr;
		if (tftr.time == 0) {
			tftr.time = now;
		}

		status = dsdb_trust_forest_info_add_record(nfti, &tftr);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}
	}

	for (g = 0; g < gfti->count; g++) {
		const struct lsa_ForestTrustRecord *gftr = gfti->entries[gfti->count - (g+1)];
		struct lsa_ForestTrustRecord tftr;
		bool skip = false;
		NTSTATUS status;

		if (gftr == NULL) {
			continue;
		}

		switch (gftr->type) {
		case LSA_FOREST_TRUST_TOP_LEVEL_NAME:
		case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
			skip = true;
			break;

		case LSA_FOREST_TRUST_DOMAIN_INFO:
			break;

		default:
			TALLOC_FREE(frame);
			return NT_STATUS_INVALID_PARAMETER;
		}

		if (skip) {
			continue;
		}

		/* make a copy in order to update the time. */
		tftr = *gftr;
		if (tftr.time == 0) {
			tftr.time = now;
		}

		status = dsdb_trust_forest_info_add_record(nfti, &tftr);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}
	}

	*_nfti = talloc_move(mem_ctx, &nfti);
	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

static NTSTATUS dsdb_trust_add_collision(
			struct lsa_ForestTrustCollisionInfo *c_info,
			enum lsa_ForestTrustCollisionRecordType type,
			uint32_t idx, uint32_t flags,
			const char *tdo_name)
{
	struct lsa_ForestTrustCollisionRecord **es;
	uint32_t i = c_info->count;

	es = talloc_realloc(c_info, c_info->entries,
			    struct lsa_ForestTrustCollisionRecord *, i + 1);
	if (es == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	c_info->entries = es;
	c_info->count = i + 1;

	es[i] = talloc_zero(es, struct lsa_ForestTrustCollisionRecord);
	if (es[i] == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	es[i]->index = idx;
	es[i]->type = type;
	es[i]->flags = flags;
	es[i]->name.string = talloc_strdup(es[i], tdo_name);
	if (es[i]->name.string == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	return NT_STATUS_OK;
}

NTSTATUS dsdb_trust_verify_forest_info(const struct lsa_TrustDomainInfoInfoEx *ref_tdo,
				const struct lsa_ForestTrustInformation *ref_fti,
				enum lsa_ForestTrustCollisionRecordType collision_type,
				struct lsa_ForestTrustCollisionInfo *c_info,
				struct lsa_ForestTrustInformation *new_fti)
{
	uint32_t n;

	for (n = 0; n < new_fti->count; n++) {
		struct lsa_ForestTrustRecord *nftr = new_fti->entries[n];
		struct lsa_StringLarge *ntln = NULL;
		bool ntln_excluded = false;
		uint32_t flags = 0;
		uint32_t r;
		NTSTATUS status;

		if (nftr == NULL) {
			continue;
		}

		if (nftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) {
			continue;
		}

		ntln = &nftr->forest_trust_data.top_level_name;
		if (ntln->string == NULL) {
			return NT_STATUS_INVALID_PARAMETER;
		}

		ntln_excluded = dsdb_trust_find_tln_ex_match(ref_fti,
							     ntln->string);

		/* check if this is already taken and not excluded */
		for (r = 0; r < ref_fti->count; r++) {
			const struct lsa_ForestTrustRecord *rftr =
				ref_fti->entries[r];
			const struct lsa_StringLarge *rtln = NULL;
			int cmp;

			if (rftr == NULL) {
				continue;
			}

			if (rftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) {
				continue;
			}

			rtln = &rftr->forest_trust_data.top_level_name;
			if (rtln->string == NULL) {
				continue;
			}

			cmp = dns_cmp(ntln->string, rtln->string);
			if (DNS_CMP_IS_NO_MATCH(cmp)) {
				continue;
			}
			if (cmp == DNS_CMP_MATCH) {
				/* We need to normalize the string */
				ntln->string = talloc_strdup(nftr,
							     rtln->string);
				if (ntln->string == NULL) {
					return NT_STATUS_NO_MEMORY;
				}
			}

			if (ntln_excluded) {
				continue;
			}

			if (rftr->flags & LSA_TLN_DISABLED_MASK) {
				continue;
			}

			if (nftr->flags & LSA_TLN_DISABLED_MASK) {
				continue;
			}

			if (cmp == DNS_CMP_SECOND_IS_CHILD) {
				bool m;

				/*
				 * If the conflicting tln is a child, check if
				 * we have an exclusion record for it.
				 */
				m = dsdb_trust_find_tln_ex_match(new_fti,
								 rtln->string);
				if (m) {
					continue;
				}
			}

			flags |= LSA_TLN_DISABLED_CONFLICT;
		}

		if (flags == 0) {
			continue;
		}

		nftr->flags |= flags;

		status = dsdb_trust_add_collision(c_info,
						  collision_type,
						  n, nftr->flags,
						  ref_tdo->domain_name.string);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	}

	for (n = 0; n < new_fti->count; n++) {
		struct lsa_ForestTrustRecord *nftr = new_fti->entries[n];
		struct lsa_ForestTrustDomainInfo *ninfo = NULL;
		struct lsa_StringLarge *ntln = NULL;
		struct lsa_StringLarge *nnb = NULL;
		struct dom_sid *nsid = NULL;
		bool ntln_found = false;
		uint32_t flags = 0;
		uint32_t r;
		NTSTATUS status;

		if (nftr == NULL) {
			continue;
		}

		if (nftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) {
			continue;
		}

		ninfo = &nftr->forest_trust_data.domain_info;
		ntln = &ninfo->dns_domain_name;
		if (ntln->string == NULL) {
			return NT_STATUS_INVALID_PARAMETER;
		}
		nnb = &ninfo->netbios_domain_name;
		if (nnb->string == NULL) {
			return NT_STATUS_INVALID_PARAMETER;
		}
		nsid = ninfo->domain_sid;
		if (nsid == NULL) {
			return NT_STATUS_INVALID_PARAMETER;
		}

		ntln_found = dsdb_trust_find_tln_match(ref_fti, ntln->string);

		/* check if this is already taken and not excluded */
		for (r = 0; r < ref_fti->count; r++) {
			const struct lsa_ForestTrustRecord *rftr =
				ref_fti->entries[r];
			const struct lsa_ForestTrustDomainInfo *rinfo = NULL;
			const struct lsa_StringLarge *rtln = NULL;
			const struct lsa_StringLarge *rnb = NULL;
			const struct dom_sid *rsid = NULL;
			bool nb_possible = true;
			bool sid_possible = true;
			int cmp;

			if (rftr == NULL) {
				continue;
			}

			if (!ntln_found) {
				/*
				 * If the dns name doesn't match any existing
				 * tln any conflict is ignored, but name
				 * normalization still happens.
				 *
				 * I guess that's a bug in Windows
				 * (tested with Windows 2012r2).
				 */
				nb_possible = false;
				sid_possible = false;
			}

			if (nftr->flags & LSA_SID_DISABLED_MASK) {
				sid_possible = false;
			}

			if (nftr->flags & LSA_NB_DISABLED_MASK) {
				nb_possible = false;
			}

			switch (rftr->type) {
			case LSA_FOREST_TRUST_TOP_LEVEL_NAME:
				rtln = &rftr->forest_trust_data.top_level_name;
				nb_possible = false;
				sid_possible = false;
				break;

			case LSA_FOREST_TRUST_DOMAIN_INFO:
				rinfo = &rftr->forest_trust_data.domain_info;
				rtln = &rinfo->dns_domain_name;
				rnb = &rinfo->netbios_domain_name;
				rsid = rinfo->domain_sid;

				if (rftr->flags & LSA_SID_DISABLED_MASK) {
					sid_possible = false;
				}

				if (rftr->flags & LSA_NB_DISABLED_MASK) {
					nb_possible = false;
				}
				break;

			default:
				break;
			}

			if (rtln == NULL) {
				continue;
			}

			if (rtln->string == NULL) {
				continue;
			}

			cmp = dns_cmp(ntln->string, rtln->string);
			if (DNS_CMP_IS_NO_MATCH(cmp)) {
				nb_possible = false;
				sid_possible = false;
			}
			if (cmp == DNS_CMP_MATCH) {
				/* We need to normalize the string */
				ntln->string = talloc_strdup(nftr,
							     rtln->string);
				if (ntln->string == NULL) {
					return NT_STATUS_NO_MEMORY;
				}
			}

			if (rinfo == NULL) {
				continue;
			}

			if (rsid != NULL) {
				cmp = dom_sid_compare(nsid, rsid);
			} else {
				cmp = -1;
			}
			if (cmp == 0) {
				if (sid_possible) {
					flags |= LSA_SID_DISABLED_CONFLICT;
				}
			}

			if (rnb->string != NULL) {
				cmp = strcasecmp_m(nnb->string, rnb->string);
			} else {
				cmp = -1;
			}
			if (cmp == 0) {
				nnb->string = talloc_strdup(nftr, rnb->string);
				if (nnb->string == NULL) {
					return NT_STATUS_NO_MEMORY;
				}
				if (nb_possible) {
					flags |= LSA_NB_DISABLED_CONFLICT;
				}
			}
		}

		if (flags == 0) {
			continue;
		}

		nftr->flags |= flags;

		status = dsdb_trust_add_collision(c_info,
						  collision_type,
						  n, nftr->flags,
						  ref_tdo->domain_name.string);
		if (!NT_STATUS_IS_OK(status)) {
			return status;
		}
	}

	return NT_STATUS_OK;
}

NTSTATUS dsdb_trust_merge_forest_info(TALLOC_CTX *mem_ctx,
				const struct lsa_TrustDomainInfoInfoEx *tdo,
				const struct lsa_ForestTrustInformation *ofti,
				const struct lsa_ForestTrustInformation *nfti,
				struct lsa_ForestTrustInformation **_mfti)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct lsa_ForestTrustInformation *mfti = NULL;
	uint32_t ni;
	uint32_t oi;
	NTSTATUS status;
	int cmp;

	*_mfti = NULL;
	mfti = talloc_zero(mem_ctx, struct lsa_ForestTrustInformation);
	if (mfti == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}
	talloc_steal(frame, mfti);

	/*
	 * First we add all top unique level names.
	 *
	 * The one matching the tdo dns name, will be
	 * added without further checking. All others
	 * may keep the flags and time values.
	 */
	for (ni = 0; ni < nfti->count; ni++) {
		const struct lsa_ForestTrustRecord *nftr = nfti->entries[ni];
		struct lsa_ForestTrustRecord tftr = {
			.flags = 0,
		};
		const char *ndns = NULL;
		bool ignore_new = false;
		bool found_old = false;
		uint32_t mi;

		if (nftr == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_INVALID_PARAMETER;
		}

		if (nftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) {
			continue;
		}

		ndns = nftr->forest_trust_data.top_level_name.string;
		if (ndns == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_INVALID_PARAMETER;
		}

		cmp = dns_cmp(tdo->domain_name.string, ndns);
		if (cmp == DNS_CMP_MATCH) {
			status = dsdb_trust_forest_info_add_record(mfti, nftr);
			if (!NT_STATUS_IS_OK(status)) {
				TALLOC_FREE(frame);
				return status;
			}
		}

		for (mi = 0; mi < mfti->count; mi++) {
			const struct lsa_ForestTrustRecord *mftr =
				mfti->entries[mi];
			const char *mdns = NULL;

			/*
			 * we just added this above, so we're sure to have a
			 * valid LSA_FOREST_TRUST_TOP_LEVEL_NAME record
			 */
			mdns = mftr->forest_trust_data.top_level_name.string;

			cmp = dns_cmp(mdns, ndns);
			switch (cmp) {
			case DNS_CMP_MATCH:
			case DNS_CMP_SECOND_IS_CHILD:
				ignore_new = true;
				break;
			}

			if (ignore_new) {
				break;
			}
		}

		if (ignore_new) {
			continue;
		}

		/*
		 * make a temporary copy where we can change time and flags
		 */
		tftr = *nftr;

		for (oi = 0; oi < ofti->count; oi++) {
			const struct lsa_ForestTrustRecord *oftr =
				ofti->entries[oi];
			const char *odns = NULL;

			if (oftr == NULL) {
				/*
				 * broken record => ignore...
				 */
				continue;
			}

			if (oftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) {
				continue;
			}

			odns = oftr->forest_trust_data.top_level_name.string;
			if (odns == NULL) {
				/*
				 * broken record => ignore...
				 */
				continue;
			}

			cmp = dns_cmp(odns, ndns);
			if (cmp != DNS_CMP_MATCH) {
				continue;
			}

			found_old = true;
			tftr.flags = oftr->flags;
			tftr.time = oftr->time;
		}

		if (!found_old) {
			tftr.flags = LSA_TLN_DISABLED_NEW;
			tftr.time = 0;
		}

		status = dsdb_trust_forest_info_add_record(mfti, &tftr);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}
	}

	/*
	 * Now we add all unique (based on their SID) domains
	 * and may keep the flags and time values.
	 */
	for (ni = 0; ni < nfti->count; ni++) {
		const struct lsa_ForestTrustRecord *nftr = nfti->entries[ni];
		struct lsa_ForestTrustRecord tftr = {
			.flags = 0,
		};
		const struct lsa_ForestTrustDomainInfo *nd = NULL;
		const char *ndns = NULL;
		const char *nnbt = NULL;
		bool ignore_new = false;
		bool found_old = false;
		uint32_t mi;

		if (nftr == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_INVALID_PARAMETER;
		}

		if (nftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) {
			continue;
		}

		nd = &nftr->forest_trust_data.domain_info;
		if (nd->domain_sid == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_INVALID_PARAMETER;
		}
		ndns = nd->dns_domain_name.string;
		if (ndns == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_INVALID_PARAMETER;
		}
		nnbt = nd->netbios_domain_name.string;
		if (nnbt == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_INVALID_PARAMETER;
		}

		for (mi = 0; mi < mfti->count; mi++) {
			const struct lsa_ForestTrustRecord *mftr =
				mfti->entries[mi];
			const struct lsa_ForestTrustDomainInfo *md = NULL;

			if (mftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) {
				continue;
			}

			/*
			 * we just added this above, so we're sure to have a
			 * valid LSA_FOREST_TRUST_DOMAIN_INFO record
			 */
			md = &mftr->forest_trust_data.domain_info;

			cmp = dom_sid_compare(nd->domain_sid, md->domain_sid);
			if (cmp == 0) {
				ignore_new = true;
				break;
			}
		}

		if (ignore_new) {
			continue;
		}

		/*
		 * make a temporary copy where we can change time and flags
		 */
		tftr = *nftr;

		for (oi = 0; oi < ofti->count; oi++) {
			const struct lsa_ForestTrustRecord *oftr =
				ofti->entries[oi];
			const struct lsa_ForestTrustDomainInfo *od = NULL;
			const char *onbt = NULL;

			if (oftr == NULL) {
				/*
				 * broken record => ignore...
				 */
				continue;
			}

			if (oftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) {
				continue;
			}

			od = &oftr->forest_trust_data.domain_info;
			onbt = od->netbios_domain_name.string;
			if (onbt == NULL) {
				/*
				 * broken record => ignore...
				 */
				continue;
			}

			cmp = strcasecmp(onbt, nnbt);
			if (cmp != 0) {
				continue;
			}

			found_old = true;
			tftr.flags = oftr->flags;
			tftr.time = oftr->time;
		}

		if (!found_old) {
			tftr.flags = 0;
			tftr.time = 0;
		}

		status = dsdb_trust_forest_info_add_record(mfti, &tftr);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}
	}

	/*
	 * We keep old domain records disabled by the admin
	 * if not already in the list.
	 */
	for (oi = 0; oi < ofti->count; oi++) {
		const struct lsa_ForestTrustRecord *oftr =
			ofti->entries[oi];
		const struct lsa_ForestTrustDomainInfo *od = NULL;
		const char *odns = NULL;
		const char *onbt = NULL;
		bool ignore_old = true;
		uint32_t mi;

		if (oftr == NULL) {
			/*
			 * broken record => ignore...
			 */
			continue;
		}

		if (oftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) {
			continue;
		}

		od = &oftr->forest_trust_data.domain_info;
		odns = od->dns_domain_name.string;
		if (odns == NULL) {
			/*
			 * broken record => ignore...
			 */
			continue;
		}
		onbt = od->netbios_domain_name.string;
		if (onbt == NULL) {
			/*
			 * broken record => ignore...
			 */
			continue;
		}
		if (od->domain_sid == NULL) {
			/*
			 * broken record => ignore...
			 */
			continue;
		}

		if (oftr->flags & LSA_NB_DISABLED_ADMIN) {
			ignore_old = false;
		} else if (oftr->flags & LSA_SID_DISABLED_ADMIN) {
			ignore_old = false;
		}

		for (mi = 0; mi < mfti->count; mi++) {
			const struct lsa_ForestTrustRecord *mftr =
				mfti->entries[mi];
			const struct lsa_ForestTrustDomainInfo *md = NULL;

			if (mftr->type != LSA_FOREST_TRUST_DOMAIN_INFO) {
				continue;
			}

			/*
			 * we just added this above, so we're sure to have a
			 * valid LSA_FOREST_TRUST_DOMAIN_INFO record
			 */
			md = &mftr->forest_trust_data.domain_info;

			cmp = dom_sid_compare(od->domain_sid, md->domain_sid);
			if (cmp == 0) {
				ignore_old = true;
				break;
			}
		}

		if (ignore_old) {
			continue;
		}

		status = dsdb_trust_forest_info_add_record(mfti, oftr);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}
	}

	/*
	 * Finally we readd top level exclusions,
	 * if they still match a top level name.
	 */
	for (oi = 0; oi < ofti->count; oi++) {
		const struct lsa_ForestTrustRecord *oftr =
			ofti->entries[oi];
		const char *odns = NULL;
		bool ignore_old = false;
		uint32_t mi;

		if (oftr == NULL) {
			/*
			 * broken record => ignore...
			 */
			continue;
		}

		if (oftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX) {
			continue;
		}

		odns = oftr->forest_trust_data.top_level_name_ex.string;
		if (odns == NULL) {
			/*
			 * broken record => ignore...
			 */
			continue;
		}

		for (mi = 0; mi < mfti->count; mi++) {
			const struct lsa_ForestTrustRecord *mftr =
				mfti->entries[mi];
			const char *mdns = NULL;

			if (mftr->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) {
				continue;
			}

			/*
			 * we just added this above, so we're sure to have a
			 * valid LSA_FOREST_TRUST_TOP_LEVEL_NAME.
			 */
			mdns = mftr->forest_trust_data.top_level_name.string;

			cmp = dns_cmp(mdns, odns);
			switch (cmp) {
			case DNS_CMP_MATCH:
			case DNS_CMP_SECOND_IS_CHILD:
				break;
			default:
				ignore_old = true;
				break;
			}

			if (ignore_old) {
				break;
			}
		}

		if (ignore_old) {
			continue;
		}

		status = dsdb_trust_forest_info_add_record(mfti, oftr);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}
	}

	*_mfti = talloc_move(mem_ctx, &mfti);
	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

NTSTATUS dsdb_trust_search_tdo(struct ldb_context *sam_ctx,
			       const char *netbios, const char *dns,
			       const char * const *attrs,
			       TALLOC_CTX *mem_ctx,
			       struct ldb_message **msg)
{
	TALLOC_CTX *frame = talloc_stackframe();
	int ret;
	struct ldb_dn *system_dn = NULL;
	char *netbios_encoded = NULL;
	char *dns_encoded = NULL;
	char *filter = NULL;

	*msg = NULL;

	if (netbios == NULL && dns == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	system_dn = samdb_system_container_dn(sam_ctx, frame);
	if (system_dn == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	if (netbios != NULL) {
		netbios_encoded = ldb_binary_encode_string(frame, netbios);
		if (netbios_encoded == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_NO_MEMORY;
		}
	}

	if (dns != NULL) {
		dns_encoded = ldb_binary_encode_string(frame, dns);
		if (dns_encoded == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_NO_MEMORY;
		}
	}

	if (netbios != NULL && dns != NULL) {
		filter = talloc_asprintf(frame,
				"(&(objectClass=trustedDomain)"
				  "(|(trustPartner=%s)(flatName=%s))"
				")",
				dns_encoded, netbios_encoded);
		if (filter == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_NO_MEMORY;
		}
	} else if (netbios != NULL) {
		filter = talloc_asprintf(frame,
				"(&(objectClass=trustedDomain)(flatName=%s))",
				netbios_encoded);
		if (filter == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_NO_MEMORY;
		}
	} else if (dns != NULL) {
		filter = talloc_asprintf(frame,
				"(&(objectClass=trustedDomain)(trustPartner=%s))",
				dns_encoded);
		if (filter == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_NO_MEMORY;
		}
	}

	ret = dsdb_search_one(sam_ctx, mem_ctx, msg,
			      system_dn,
			      LDB_SCOPE_ONELEVEL, attrs,
			      DSDB_SEARCH_NO_GLOBAL_CATALOG,
			      "%s", filter);
	if (ret != LDB_SUCCESS) {
		NTSTATUS status = dsdb_ldb_err_to_ntstatus(ret);
		DEBUG(3, ("Failed to search for %s: %s - %s\n",
			  filter, nt_errstr(status), ldb_errstring(sam_ctx)));
		TALLOC_FREE(frame);
		return status;
	}

	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

NTSTATUS dsdb_trust_search_tdo_by_type(struct ldb_context *sam_ctx,
				       enum netr_SchannelType type,
				       const char *name,
				       const char * const *attrs,
				       TALLOC_CTX *mem_ctx,
				       struct ldb_message **msg)
{
	TALLOC_CTX *frame = talloc_stackframe();
	NTSTATUS status;
	size_t len;
	char trailer = '$';
	bool require_trailer = true;
	char *encoded_name = NULL;
	const char *netbios = NULL;
	const char *dns = NULL;

	if (type != SEC_CHAN_DOMAIN && type != SEC_CHAN_DNS_DOMAIN) {
		TALLOC_FREE(frame);
		return NT_STATUS_INVALID_PARAMETER;
	}

	if (type == SEC_CHAN_DNS_DOMAIN) {
		trailer = '.';
		require_trailer = false;
	}

	encoded_name = ldb_binary_encode_string(frame, name);
	if (encoded_name == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	len = strlen(encoded_name);
	if (len < 2) {
		TALLOC_FREE(frame);
		return NT_STATUS_OBJECT_NAME_NOT_FOUND;
	}

	if (require_trailer && encoded_name[len - 1] != trailer) {
		TALLOC_FREE(frame);
		return NT_STATUS_OBJECT_NAME_NOT_FOUND;
	}
	encoded_name[len - 1] = '\0';

	if (type == SEC_CHAN_DNS_DOMAIN) {
		dns = encoded_name;
	} else {
		netbios = encoded_name;
	}

	status = dsdb_trust_search_tdo(sam_ctx, netbios, dns,
				       attrs, mem_ctx, msg);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(frame);
		return status;
	}

	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

NTSTATUS dsdb_trust_search_tdo_by_sid(struct ldb_context *sam_ctx,
				      const struct dom_sid *sid,
				      const char * const *attrs,
				      TALLOC_CTX *mem_ctx,
				      struct ldb_message **msg)
{
	TALLOC_CTX *frame = talloc_stackframe();
	int ret;
	struct ldb_dn *system_dn = NULL;
	char *encoded_sid = NULL;
	char *filter = NULL;

	*msg = NULL;

	if (sid == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_INVALID_PARAMETER_MIX;
	}

	encoded_sid = ldap_encode_ndr_dom_sid(frame, sid);
	if (encoded_sid == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	system_dn = samdb_system_container_dn(sam_ctx, frame);
	if (system_dn == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	filter = talloc_asprintf(frame,
				"(&"
				  "(objectClass=trustedDomain)"
				  "(securityIdentifier=%s)"
				")",
				encoded_sid);
	if (filter == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	ret = dsdb_search_one(sam_ctx, mem_ctx, msg,
			      system_dn,
			      LDB_SCOPE_ONELEVEL, attrs,
			      DSDB_SEARCH_NO_GLOBAL_CATALOG,
			      "%s", filter);
	if (ret != LDB_SUCCESS) {
		NTSTATUS status = dsdb_ldb_err_to_ntstatus(ret);
		DEBUG(3, ("Failed to search for %s: %s - %s\n",
			  filter, nt_errstr(status), ldb_errstring(sam_ctx)));
		TALLOC_FREE(frame);
		return status;
	}

	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

NTSTATUS dsdb_trust_get_incoming_passwords(const struct ldb_message *msg,
					   TALLOC_CTX *mem_ctx,
					   struct samr_Password **_current,
					   struct samr_Password **_previous)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct samr_Password __current = {
		.hash = {0},
	};
	struct samr_Password __previous = {
		.hash = {0},
	};
	struct samr_Password *current = NULL;
	struct samr_Password *previous = NULL;
	const struct ldb_val *blob = NULL;
	enum ndr_err_code ndr_err;
	struct trustAuthInOutBlob incoming = {
		.count = 0,
	};
	uint32_t i;

	if (_current != NULL) {
		*_current = NULL;
	}
	if (_previous != NULL) {
		*_previous = NULL;
	}

	blob = ldb_msg_find_ldb_val(msg, "trustAuthIncoming");
	if (blob == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_ACCOUNT_DISABLED;
	}

	/* ldb_val is equivalent to DATA_BLOB */
	ndr_err = ndr_pull_struct_blob_all(blob, frame, &incoming,
				(ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		TALLOC_FREE(frame);
		return NT_STATUS_INTERNAL_DB_CORRUPTION;
	}

	for (i = 0; i < incoming.current.count; i++) {
		struct AuthenticationInformation *a =
			&incoming.current.array[i];

		if (current != NULL) {
			break;
		}

		switch (a->AuthType) {
		case TRUST_AUTH_TYPE_NONE:
		case TRUST_AUTH_TYPE_VERSION:
			break;
		case TRUST_AUTH_TYPE_NT4OWF:
			current = &a->AuthInfo.nt4owf.password;
			break;
		case TRUST_AUTH_TYPE_CLEAR:
			mdfour(__current.hash,
			       a->AuthInfo.clear.password,
			       a->AuthInfo.clear.size);
			current = &__current;
			break;
		}
	}

	if (current == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_INTERNAL_DB_CORRUPTION;
	}

	for (i = 0; i < incoming.previous.count; i++) {
		struct AuthenticationInformation *a =
			&incoming.previous.array[i];

		if (previous != NULL) {
			break;
		}

		switch (a->AuthType) {
		case TRUST_AUTH_TYPE_NONE:
		case TRUST_AUTH_TYPE_VERSION:
			break;
		case TRUST_AUTH_TYPE_NT4OWF:
			previous = &a->AuthInfo.nt4owf.password;
			break;
		case TRUST_AUTH_TYPE_CLEAR:
			mdfour(__previous.hash,
			       a->AuthInfo.clear.password,
			       a->AuthInfo.clear.size);
			previous = &__previous;
			break;
		}
	}

	if (previous == NULL) {
		previous = current;
	}

	if (_current != NULL) {
		*_current = talloc_memdup(mem_ctx, current, sizeof(*current));
		if (*_current == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_NO_MEMORY;
		}
	}
	if (_previous != NULL) {
		*_previous =
			talloc_memdup(mem_ctx, previous, sizeof(*previous));
		if (*_previous == NULL) {
			if (_current != NULL) {
				TALLOC_FREE(*_current);
			}
			TALLOC_FREE(frame);
			return NT_STATUS_NO_MEMORY;
		}
	}
	ZERO_STRUCTP(current);
	ZERO_STRUCTP(previous);
	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

NTSTATUS dsdb_trust_search_tdos(struct ldb_context *sam_ctx,
				const char *exclude,
				const char * const *attrs,
				TALLOC_CTX *mem_ctx,
				struct ldb_result **res)
{
	TALLOC_CTX *frame = talloc_stackframe();
	int ret;
	struct ldb_dn *system_dn = NULL;
	const char *filter = NULL;
	char *exclude_encoded = NULL;

	*res = NULL;

	system_dn = samdb_system_container_dn(sam_ctx, frame);
	if (system_dn == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	if (exclude != NULL) {
		exclude_encoded = ldb_binary_encode_string(frame, exclude);
		if (exclude_encoded == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_NO_MEMORY;
		}

		filter = talloc_asprintf(frame,
				"(&(objectClass=trustedDomain)"
				  "(!(|(trustPartner=%s)(flatName=%s)))"
				")",
				exclude_encoded, exclude_encoded);
		if (filter == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_NO_MEMORY;
		}
	} else {
		filter = "(objectClass=trustedDomain)";
	}

	ret = dsdb_search(sam_ctx, mem_ctx, res,
			  system_dn,
			  LDB_SCOPE_ONELEVEL, attrs,
			  DSDB_SEARCH_NO_GLOBAL_CATALOG,
			  "%s", filter);
	if (ret != LDB_SUCCESS) {
		NTSTATUS status = dsdb_ldb_err_to_ntstatus(ret);
		DEBUG(3, ("Failed to search for %s: %s - %s\n",
			  filter, nt_errstr(status), ldb_errstring(sam_ctx)));
		TALLOC_FREE(frame);
		return status;
	}

	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

struct dsdb_trust_routing_domain;

struct dsdb_trust_routing_table {
	struct dsdb_trust_routing_domain *domains;
};

struct dsdb_trust_routing_domain {
	struct dsdb_trust_routing_domain *prev, *next;

	struct lsa_TrustDomainInfoInfoEx *tdo;

	struct lsa_ForestTrustDomainInfo di;

	struct lsa_ForestTrustInformation *fti;
};

NTSTATUS dsdb_trust_routing_table_load(struct ldb_context *sam_ctx,
				       TALLOC_CTX *mem_ctx,
				       struct dsdb_trust_routing_table **_table)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct dsdb_trust_routing_table *table;
	struct dsdb_trust_routing_domain *d = NULL;
	struct ldb_dn *domain_dn = NULL;
	struct lsa_TrustDomainInfoInfoEx *root_trust_tdo = NULL;
	struct lsa_TrustDomainInfoInfoEx *trust_parent_tdo = NULL;
	struct lsa_TrustDomainInfoInfoEx *root_direction_tdo = NULL;
	const char * const trusts_attrs[] = {
		"securityIdentifier",
		"flatName",
		"trustPartner",
		"trustAttributes",
		"trustDirection",
		"trustType",
		"msDS-TrustForestTrustInfo",
		NULL
	};
	struct ldb_result *trusts_res = NULL;
	unsigned int i;
	NTSTATUS status;

	*_table = NULL;

	domain_dn = ldb_get_default_basedn(sam_ctx);
	if (domain_dn == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_INTERNAL_ERROR;
	}

	table = talloc_zero(mem_ctx, struct dsdb_trust_routing_table);
	if (table == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}
	talloc_steal(frame, table);

	d = talloc_zero(table, struct dsdb_trust_routing_domain);
	if (d == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}

	status = dsdb_trust_crossref_tdo_info(d, sam_ctx,
					      domain_dn, NULL,
					      &d->tdo,
					      &root_trust_tdo,
					      &trust_parent_tdo);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(frame);
		return status;
	}

	/*
	 * d->tdo should not be NULL of status above is 'NT_STATUS_OK'
	 * check is needed to satisfy clang static checker
	*/
	if (d->tdo == NULL) {
		TALLOC_FREE(frame);
		return NT_STATUS_NO_MEMORY;
	}
	d->di.domain_sid = d->tdo->sid;
	d->di.netbios_domain_name.string = d->tdo->netbios_name.string;
	d->di.dns_domain_name.string = d->tdo->domain_name.string;

	if (root_trust_tdo != NULL) {
		root_direction_tdo = root_trust_tdo;
	} else if (trust_parent_tdo != NULL) {
		root_direction_tdo = trust_parent_tdo;
	}

	if (root_direction_tdo == NULL) {
		/* we're the forest root */
		status = dsdb_trust_xref_forest_info(d, sam_ctx, &d->fti);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}
	}

	DLIST_ADD(table->domains, d);

	status = dsdb_trust_search_tdos(sam_ctx, NULL, trusts_attrs,
					frame, &trusts_res);
	if (!NT_STATUS_IS_OK(status)) {
		TALLOC_FREE(frame);
		return status;
	}

	for (i = 0; i < trusts_res->count; i++) {
		bool ok;
		int cmp;

		d = talloc_zero(table, struct dsdb_trust_routing_domain);
		if (d == NULL) {
			TALLOC_FREE(frame);
			return NT_STATUS_NO_MEMORY;
		}

		status = dsdb_trust_parse_tdo_info(d,
						   trusts_res->msgs[i],
						   &d->tdo);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}

		d->di.domain_sid = d->tdo->sid;
		d->di.netbios_domain_name.string = d->tdo->netbios_name.string;
		d->di.dns_domain_name.string = d->tdo->domain_name.string;

		DLIST_ADD_END(table->domains, d);

		if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) {
			struct ForestTrustInfo *fti = NULL;

			status = dsdb_trust_parse_forest_info(frame,
							      trusts_res->msgs[i],
							      &fti);
			if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
				fti = NULL;
				status = NT_STATUS_OK;
			}
			if (!NT_STATUS_IS_OK(status)) {
				TALLOC_FREE(frame);
				return status;
			}

			if (fti == NULL) {
				continue;
			}

			status = dsdb_trust_forest_info_to_lsa(d, fti, &d->fti);
			if (!NT_STATUS_IS_OK(status)) {
				TALLOC_FREE(frame);
				return status;
			}

			continue;
		}

		if (!(d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST)) {
			continue;
		}

		if (root_direction_tdo == NULL) {
			continue;
		}

		ok = dom_sid_equal(root_direction_tdo->sid, d->tdo->sid);
		if (!ok) {
			continue;
		}

		cmp = strcasecmp_m(root_direction_tdo->netbios_name.string,
				   d->tdo->netbios_name.string);
		if (cmp != 0) {
			continue;
		}

		cmp = strcasecmp_m(root_direction_tdo->domain_name.string,
				   d->tdo->domain_name.string);
		if (cmp != 0) {
			continue;
		}

		/* this our route to the forest root */
		status = dsdb_trust_xref_forest_info(d, sam_ctx, &d->fti);
		if (!NT_STATUS_IS_OK(status)) {
			TALLOC_FREE(frame);
			return status;
		}
	}

	*_table = talloc_move(mem_ctx, &table);
	TALLOC_FREE(frame);
	return NT_STATUS_OK;
}

static void dsdb_trust_update_best_tln(
	const struct dsdb_trust_routing_domain **best_d,
	const char **best_tln,
	const struct dsdb_trust_routing_domain *d,
	const char *tln)
{
	int cmp;

	if (*best_tln == NULL) {
		*best_tln = tln;
		*best_d = d;
		return;
	}

	cmp = dns_cmp(*best_tln, tln);
	if (cmp != DNS_CMP_FIRST_IS_CHILD) {
		return;
	}

	*best_tln = tln;
	*best_d = d;
}

const struct lsa_TrustDomainInfoInfoEx *dsdb_trust_routing_by_name(
		const struct dsdb_trust_routing_table *table,
		const char *name)
{
	const struct dsdb_trust_routing_domain *best_d = NULL;
	const char *best_tln = NULL;
	const struct dsdb_trust_routing_domain *d = NULL;

	if (name == NULL) {
		return NULL;
	}

	for (d = table->domains; d != NULL; d = d->next) {
		bool transitive = false;
		bool allow_netbios = false;
		bool exclude = false;
		uint32_t i;

		if (d->tdo->trust_type != LSA_TRUST_TYPE_UPLEVEL) {
			/*
			 * Only uplevel trusts have top level names
			 */
			continue;
		}

		if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) {
			transitive = true;
		}

		if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) {
			transitive = true;
		}

		if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE) {
			transitive = false;
		}

		if (d->tdo->trust_type != LSA_TRUST_TYPE_UPLEVEL) {
			transitive = false;
		}

		switch (d->tdo->trust_type) {
		case LSA_TRUST_TYPE_UPLEVEL:
			if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_UPLEVEL_ONLY) {
				break;
			}
			allow_netbios = true;
			break;
		case LSA_TRUST_TYPE_DOWNLEVEL:
			allow_netbios = true;
			break;
		default:
			allow_netbios = false;
			break;
		}

		if (!transitive || d->fti == NULL) {
			int cmp;

			if (allow_netbios) {
				cmp = dns_cmp(name, d->tdo->netbios_name.string);
				if (cmp == DNS_CMP_MATCH) {
					/*
					 * exact match
					 */
					return d->tdo;
				}
			}

			cmp = dns_cmp(name, d->tdo->domain_name.string);
			if (cmp == DNS_CMP_MATCH) {
				/*
				 * exact match
				 */
				return d->tdo;
			}
			if (cmp != DNS_CMP_FIRST_IS_CHILD) {
				continue;
			}

			if (!transitive) {
				continue;
			}

			dsdb_trust_update_best_tln(&best_d, &best_tln, d,
						   d->tdo->domain_name.string);
			continue;
		}

		exclude = dsdb_trust_find_tln_ex_match(d->fti, name);
		if (exclude) {
			continue;
		}

		for (i = 0; i < d->fti->count; i++ ) {
			const struct lsa_ForestTrustRecord *f = d->fti->entries[i];
			const struct lsa_ForestTrustDomainInfo *di = NULL;
			const char *fti_nbt = NULL;
			int cmp;

			if (!allow_netbios) {
				break;
			}

			if (f == NULL) {
				/* broken record */
				continue;
			}

			if (f->type != LSA_FOREST_TRUST_DOMAIN_INFO) {
				continue;
			}

			if (f->flags & LSA_NB_DISABLED_MASK) {
				/*
				 * any flag disables the entry.
				 */
				continue;
			}

			di = &f->forest_trust_data.domain_info;
			fti_nbt = di->netbios_domain_name.string;
			if (fti_nbt == NULL) {
				/* broken record */
				continue;
			}

			cmp = dns_cmp(name, fti_nbt);
			if (cmp == DNS_CMP_MATCH) {
				/*
				 * exact match
				 */
				return d->tdo;
			}
		}

		for (i = 0; i < d->fti->count; i++ ) {
			const struct lsa_ForestTrustRecord *f = d->fti->entries[i];
			const union lsa_ForestTrustData *u = NULL;
			const char *fti_tln = NULL;
			int cmp;

			if (f == NULL) {
				/* broken record */
				continue;
			}

			if (f->type != LSA_FOREST_TRUST_TOP_LEVEL_NAME) {
				continue;
			}

			if (f->flags & LSA_TLN_DISABLED_MASK) {
				/*
				 * any flag disables the entry.
				 */
				continue;
			}

			u = &f->forest_trust_data;
			fti_tln = u->top_level_name.string;
			if (fti_tln == NULL) {
				continue;
			}

			cmp = dns_cmp(name, fti_tln);
			switch (cmp) {
			case DNS_CMP_MATCH:
			case DNS_CMP_FIRST_IS_CHILD:
				dsdb_trust_update_best_tln(&best_d, &best_tln,
							   d, fti_tln);
				break;
			default:
				break;
			}
		}
	}

	if (best_d != NULL) {
		return best_d->tdo;
	}

	return NULL;
}

const struct lsa_TrustDomainInfoInfoEx *dsdb_trust_domain_by_sid(
		const struct dsdb_trust_routing_table *table,
		const struct dom_sid *sid,
		const struct lsa_ForestTrustDomainInfo **pdi)
{
	const struct dsdb_trust_routing_domain *d = NULL;

	if (pdi != NULL) {
		*pdi = NULL;
	}

	if (sid == NULL) {
		return NULL;
	}

	for (d = table->domains; d != NULL; d = d->next) {
		bool transitive = false;
		uint32_t i;

		if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) {
			transitive = true;
		}

		if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) {
			transitive = true;
		}

		if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE) {
			transitive = false;
		}

		if (d->tdo->trust_type != LSA_TRUST_TYPE_UPLEVEL) {
			transitive = false;
		}

		if (!transitive || d->fti == NULL) {
			bool match = false;

			match = dom_sid_equal(d->di.domain_sid, sid);
			if (match) {
				/*
				 * exact match, it's the domain itself.
				 */
				if (pdi != NULL) {
					*pdi = &d->di;
				}
				return d->tdo;
			}
			continue;
		}

		for (i = 0; i < d->fti->count; i++ ) {
			const struct lsa_ForestTrustRecord *f = d->fti->entries[i];
			const struct lsa_ForestTrustDomainInfo *di = NULL;
			const struct dom_sid *fti_sid = NULL;
			bool match = false;

			if (f == NULL) {
				/* broken record */
				continue;
			}

			if (f->type != LSA_FOREST_TRUST_DOMAIN_INFO) {
				continue;
			}

			if (f->flags & LSA_SID_DISABLED_MASK) {
				/*
				 * any flag disables the entry.
				 */
				continue;
			}

			di = &f->forest_trust_data.domain_info;
			fti_sid = di->domain_sid;
			if (fti_sid == NULL) {
				/* broken record */
				continue;
			}

			match = dom_sid_equal(fti_sid, sid);
			if (match) {
				/*
				 * exact match, it's a domain in the forest.
				 */
				if (pdi != NULL) {
					*pdi = di;
				}
				return d->tdo;
			}
		}
	}

	return NULL;
}

const struct lsa_TrustDomainInfoInfoEx *dsdb_trust_domain_by_name(
		const struct dsdb_trust_routing_table *table,
		const char *name,
		const struct lsa_ForestTrustDomainInfo **pdi)
{
	const struct dsdb_trust_routing_domain *d = NULL;

	if (pdi != NULL) {
		*pdi = NULL;
	}

	if (name == NULL) {
		return NULL;
	}

	for (d = table->domains; d != NULL; d = d->next) {
		bool transitive = false;
		uint32_t i;

		if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) {
			transitive = true;
		}

		if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) {
			transitive = true;
		}

		if (d->tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE) {
			transitive = false;
		}

		if (d->tdo->trust_type != LSA_TRUST_TYPE_UPLEVEL) {
			transitive = false;
		}

		if (!transitive || d->fti == NULL) {
			bool match = false;

			match = strequal_m(d->di.netbios_domain_name.string,
					   name);
			if (match) {
				/*
				 * exact match for netbios name,
				 * it's the domain itself.
				 */
				if (pdi != NULL) {
					*pdi = &d->di;
				}
				return d->tdo;
			}
			match = strequal_m(d->di.dns_domain_name.string,
					   name);
			if (match) {
				/*
				 * exact match for dns name,
				 * it's the domain itself.
				 */
				if (pdi != NULL) {
					*pdi = &d->di;
				}
				return d->tdo;
			}
			continue;
		}

		for (i = 0; i < d->fti->count; i++ ) {
			const struct lsa_ForestTrustRecord *f = d->fti->entries[i];
			const struct lsa_ForestTrustDomainInfo *di = NULL;
			bool match = false;

			if (f == NULL) {
				/* broken record */
				continue;
			}

			if (f->type != LSA_FOREST_TRUST_DOMAIN_INFO) {
				continue;
			}
			di = &f->forest_trust_data.domain_info;

			if (!(f->flags & LSA_NB_DISABLED_MASK)) {
				match = strequal_m(di->netbios_domain_name.string,
						   name);
				if (match) {
					/*
					 * exact match for netbios name,
					 * it's a domain in the forest.
					 */
					if (pdi != NULL) {
						*pdi = di;
					}
					return d->tdo;
				}
			}

			if (!(f->flags & LSA_TLN_DISABLED_MASK)) {
				match = strequal_m(di->dns_domain_name.string,
						   name);
				if (match) {
					/*
					 * exact match for dns name,
					 * it's a domain in the forest.
					 */
					if (pdi != NULL) {
						*pdi = di;
					}
					return d->tdo;
				}
			}
		}
	}

	return NULL;
}
