static int check_spn_alias_collision(struct ldb_context *ldb,
				     TALLOC_CTX *mem_ctx,
				     const char *spn,
				     struct ldb_dn *target_dn)
{
	int ret;
	char *service_class = NULL;
	char *spn_tail = NULL;
	char *p = NULL;
	char **aliases = NULL;
	size_t n_aliases = 0;
	size_t i, len;
	TALLOC_CTX *tmp_ctx = NULL;
	const char *target_dnstr = ldb_dn_get_linearized(target_dn);
	int link_direction;

	tmp_ctx = talloc_new(mem_ctx);
	if (tmp_ctx == NULL) {
		return ldb_oom(ldb);
	}

	/*
	 * "dns/example.com/xxx"  gives
	 *    service_class = "dns"
	 *    spn_tail      = "example.com/xxx"
	 */
	p = strchr(spn, '/');
	if (p == NULL) {
		/* bad SPN */
		talloc_free(tmp_ctx);
		return ldb_error(ldb,
				 LDB_ERR_OPERATIONS_ERROR,
				 "malformed servicePrincipalName");
	}
	len = p - spn;

	service_class = talloc_strndup(tmp_ctx, spn, len);
	if (service_class == NULL) {
		talloc_free(tmp_ctx);
		return ldb_oom(ldb);
	}
	spn_tail = p + 1;

	ret = find_spn_aliases(ldb,
			       tmp_ctx,
			       service_class,
			       &aliases,
			       &n_aliases,
			       &link_direction);
	if (ret != LDB_SUCCESS) {
		talloc_free(tmp_ctx);
		return ret;
	}

	/*
	 * we have the list of aliases, and now we need to combined them with
	 * spn_tail and see if we can find the SPN.
	 */
	for (i = 0; i < n_aliases; i++) {
		struct ldb_dn *colliding_dn = NULL;
		const char *colliding_dnstr = NULL;

		char *candidate = talloc_asprintf(tmp_ctx,
						  "%s/%s",
						  aliases[i],
						  spn_tail);
		if (candidate == NULL) {
			talloc_free(tmp_ctx);
			return ldb_oom(ldb);
		}

		ret = get_spn_dn(ldb, tmp_ctx, candidate, &colliding_dn);
		if (ret == LDB_ERR_NO_SUCH_OBJECT) {
			DBG_DEBUG("SPN alias '%s' not found (good)\n",
				  candidate);
			talloc_free(candidate);
			continue;
		}
		if (ret != LDB_SUCCESS) {
			DBG_ERR("SPN '%s' search error %d\n", candidate, ret);
			talloc_free(tmp_ctx);
			return ret;
		}

		target_dnstr = ldb_dn_get_linearized(target_dn);
		/*
		 * We have found an existing SPN that matches the alias. That
		 * is OK only if it is on the object we are trying to add to,
		 * or if the SPN on the other side is a more generic alias for
		 * this one and we also have rights to modify it.
		 *
		 * That is, we can put "host/X" and "cifs/X" on the same
		 * object, but not on different objects, unless we put the
		 * host/X on first, and could also change that object when we
		 * add cifs/X. It is forbidden to add the objects in the other
		 * order.
		 *
		 * The rationale for this is that adding "cifs/X" effectively
		 * changes "host/X" by diverting traffic. If "host/X" can be
		 * added after "cifs/X", a sneaky person could get "cifs/X" in
		 * first, making "host/X" have less effect than intended.
		 *
		 * Note: we also can't have "host/X" and "Host/X" on the same
		 * object, but that is not relevant here.
		 */

		ret = ldb_dn_compare(colliding_dn, target_dn);
		if (ret != 0) {
			colliding_dnstr = ldb_dn_get_linearized(colliding_dn);
			DBG_ERR("trying to add SPN '%s' on '%s' when '%s' is "
				"on '%s'\n",
				spn,
				target_dnstr,
				candidate,
				colliding_dnstr);

			if (link_direction == SPN_ALIAS_LINK) {
				/* we don't allow host/X if there is a
				 * cifs/X */
				talloc_free(tmp_ctx);
				return LDB_ERR_CONSTRAINT_VIOLATION;
			}
			ret = check_spn_write_rights(ldb,
						     tmp_ctx,
						     candidate,
						     colliding_dn);
			if (ret != LDB_SUCCESS) {
				DBG_ERR("SPN '%s' is on '%s' so '%s' can't be "
					"added to '%s'\n",
					candidate,
					colliding_dnstr,
					spn,
					target_dnstr);
				talloc_free(tmp_ctx);
				ldb_asprintf_errstring(ldb,
						       "samldb: spn[%s] would cause a conflict",
						       spn);
				return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
			}
		} else {
			DBG_INFO("SPNs '%s' and '%s' alias both on '%s'\n",
				 candidate, spn, target_dnstr);
		}
		talloc_free(candidate);
	}

	talloc_free(tmp_ctx);
	return LDB_SUCCESS;
}