/*
   Unix SMB/CIFS implementation.

   DNS Server

   Copyright (C) Amitay Isaacs 2011

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "includes.h"
#include "dnsserver.h"
#include "lib/util/dlinklist.h"
#include "librpc/gen_ndr/ndr_dnsp.h"
#include "librpc/gen_ndr/ndr_security.h"
#include "librpc/gen_ndr/ndr_misc.h"
#include "dsdb/samdb/samdb.h"
#include "libcli/security/security.h"
#include "dsdb/common/util.h"

/* There are only 2 fixed partitions for DNS */
struct dnsserver_partition *dnsserver_db_enumerate_partitions(TALLOC_CTX *mem_ctx,
							struct dnsserver_serverinfo *serverinfo,
							struct ldb_context *samdb)
{
	struct dnsserver_partition *partitions, *p;

	partitions = NULL;

	/* Domain partition */
	p = talloc_zero(mem_ctx, struct dnsserver_partition);
	if (p == NULL) {
		goto failed;
	}

	p->partition_dn = ldb_dn_new(p, samdb, serverinfo->pszDomainDirectoryPartition);
	if (p->partition_dn == NULL) {
		goto failed;
	}

	p->pszDpFqdn = samdb_dn_to_dns_domain(p, p->partition_dn);
	p->dwDpFlags = DNS_DP_AUTOCREATED | DNS_DP_DOMAIN_DEFAULT | DNS_DP_ENLISTED;
	p->is_forest = false;

	DLIST_ADD_END(partitions, p);

	/* Forest Partition */
	p = talloc_zero(mem_ctx, struct dnsserver_partition);
	if (p == NULL) {
		goto failed;
	}

	p->partition_dn = ldb_dn_new(p, samdb, serverinfo->pszForestDirectoryPartition);
	if (p->partition_dn == NULL) {
		goto failed;
	}

	p->pszDpFqdn = samdb_dn_to_dns_domain(p, p->partition_dn);
	p->dwDpFlags = DNS_DP_AUTOCREATED | DNS_DP_FOREST_DEFAULT | DNS_DP_ENLISTED;
	p->is_forest = true;

	DLIST_ADD_END(partitions, p);

	return partitions;

failed:
	return NULL;

}


/* Search for all dnsZone records */
struct dnsserver_zone *dnsserver_db_enumerate_zones(TALLOC_CTX *mem_ctx,
						struct ldb_context *samdb,
						struct dnsserver_partition *p)
{
	TALLOC_CTX *tmp_ctx;
	const char * const attrs[] = {"name", "dNSProperty", NULL};
	struct ldb_dn *dn;
	struct ldb_result *res;
	struct dnsserver_zone *zones, *z;
	int i, j, ret;

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

	dn = ldb_dn_copy(tmp_ctx, p->partition_dn);
	if (dn == NULL) {
		goto failed;
	}
	if (!ldb_dn_add_child_fmt(dn, "CN=MicrosoftDNS")) {
		goto failed;
	}

	ret = ldb_search(samdb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE,
			  attrs, "(objectClass=dnsZone)");
	if (ret != LDB_SUCCESS) {
		DEBUG(0, ("dnsserver: Failed to find DNS Zones in %s\n",
			ldb_dn_get_linearized(dn)));
		goto failed;
	}

	zones = NULL;
	for(i=0; i<res->count; i++) {
		char *name;
		struct ldb_message_element *element = NULL;
		struct dnsp_DnsProperty *props = NULL;
		enum ndr_err_code err;
		z = talloc_zero(mem_ctx, struct dnsserver_zone);
		if (z == NULL) {
			goto failed;
		}

		z->partition = p;
		name = talloc_strdup(z,
				ldb_msg_find_attr_as_string(res->msgs[i],
							    "name", NULL));
		if (strcmp(name, "..TrustAnchors") == 0) {
			talloc_free(z);
			continue;
		}
		if (strcmp(name, "RootDNSServers") == 0) {
			talloc_free(name);
			z->name = talloc_strdup(z, ".");
		} else {
			z->name = name;
		}
		z->zone_dn = talloc_steal(z, res->msgs[i]->dn);

		DLIST_ADD_END(zones, z);
		DEBUG(2, ("dnsserver: Found DNS zone %s\n", z->name));

		element = ldb_msg_find_element(res->msgs[i], "dNSProperty");
		if(element != NULL){
			props = talloc_zero_array(tmp_ctx,
						  struct dnsp_DnsProperty,
						  element->num_values);
			for (j = 0; j < element->num_values; j++ ) {
				err = ndr_pull_struct_blob(
					&(element->values[j]),
					mem_ctx,
					&props[j],
					(ndr_pull_flags_fn_t)
						ndr_pull_dnsp_DnsProperty);
				if (!NDR_ERR_CODE_IS_SUCCESS(err)){
					goto failed;
				}
			}
			z->tmp_props = props;
			z->num_props = element->num_values;
		}
	}
	return zones;

failed:
	talloc_free(tmp_ctx);
	return NULL;
}


/* Find DNS partition information */
struct dnsserver_partition_info *dnsserver_db_partition_info(TALLOC_CTX *mem_ctx,
							struct ldb_context *samdb,
							struct dnsserver_partition *p)
{
	const char * const attrs[] = { "instanceType", "msDs-masteredBy", NULL };
	const char * const attrs_none[] = { NULL };
	struct ldb_result *res;
	struct ldb_message_element *el;
	struct ldb_dn *dn;
	struct dnsserver_partition_info *partinfo;
	int i, ret, instance_type;
	TALLOC_CTX *tmp_ctx;

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

	partinfo = talloc_zero(mem_ctx, struct dnsserver_partition_info);
	if (partinfo == NULL) {
		talloc_free(tmp_ctx);
		return NULL;
	}

	/* Search for the active replica and state */
	ret = ldb_search(samdb, tmp_ctx, &res, p->partition_dn, LDB_SCOPE_BASE,
			attrs, NULL);
	if (ret != LDB_SUCCESS || res->count != 1) {
		goto failed;
	}

	/* Set the state of the partition */
	instance_type = ldb_msg_find_attr_as_int(res->msgs[0], "instanceType", -1);
	if (instance_type == -1) {
		partinfo->dwState = DNS_DP_STATE_UNKNOWN;
	} else if (instance_type & INSTANCE_TYPE_NC_COMING) {
		partinfo->dwState = DNS_DP_STATE_REPL_INCOMING;
	} else if (instance_type & INSTANCE_TYPE_NC_GOING) {
		partinfo->dwState = DNS_DP_STATE_REPL_OUTGOING;
	} else {
		partinfo->dwState = DNS_DP_OKAY;
	}

	el = ldb_msg_find_element(res->msgs[0], "msDs-masteredBy");
	if (el == NULL) {
		partinfo->dwReplicaCount = 0;
		partinfo->ReplicaArray = NULL;
	} else {
		partinfo->dwReplicaCount = el->num_values;
		partinfo->ReplicaArray = talloc_zero_array(partinfo,
							   struct DNS_RPC_DP_REPLICA *,
							   el->num_values);
		if (partinfo->ReplicaArray == NULL) {
			goto failed;
		}
		for (i=0; i<el->num_values; i++) {
			partinfo->ReplicaArray[i] = talloc_zero(partinfo,
							struct DNS_RPC_DP_REPLICA);
			if (partinfo->ReplicaArray[i] == NULL) {
				goto failed;
			}
			partinfo->ReplicaArray[i]->pszReplicaDn = talloc_strdup(
									partinfo,
									(const char *)el->values[i].data);
			if (partinfo->ReplicaArray[i]->pszReplicaDn == NULL) {
				goto failed;
			}
		}
	}
	talloc_free(res);

	/* Search for cross-reference object */
	dn = ldb_dn_copy(tmp_ctx, ldb_get_config_basedn(samdb));
	if (dn == NULL) {
		goto failed;
	}

	ret = ldb_search(samdb, tmp_ctx, &res, dn, LDB_SCOPE_DEFAULT, attrs_none,
			"(nCName=%s)", ldb_dn_get_linearized(p->partition_dn));
	if (ret != LDB_SUCCESS || res->count != 1) {
		goto failed;
	}
	partinfo->pszCrDn = talloc_strdup(partinfo, ldb_dn_get_linearized(res->msgs[0]->dn));
	if (partinfo->pszCrDn == NULL) {
		goto failed;
	}
	talloc_free(res);

	talloc_free(tmp_ctx);
	return partinfo;

failed:
	talloc_free(tmp_ctx);
	talloc_free(partinfo);
	return NULL;
}


/* Increment serial number and update timestamp */
static unsigned int dnsserver_update_soa(TALLOC_CTX *mem_ctx,
				struct ldb_context *samdb,
				struct dnsserver_zone *z,
				WERROR *werr)
{
	const char * const attrs[] = { "dnsRecord", NULL };
	struct ldb_result *res;
	struct dnsp_DnssrvRpcRecord rec;
	struct ldb_message_element *el;
	enum ndr_err_code ndr_err;
	int ret, i, serial = -1;

	*werr = WERR_INTERNAL_DB_ERROR;

	ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
			"(&(objectClass=dnsNode)(name=@))");
	if (ret != LDB_SUCCESS || res->count == 0) {
		return -1;
	}

	el = ldb_msg_find_element(res->msgs[0], "dnsRecord");
	if (el == NULL) {
		return -1;
	}

	for (i=0; i<el->num_values; i++) {
		ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec,
					(ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
			continue;
		}

		if (rec.wType == DNS_TYPE_SOA) {
			serial = rec.data.soa.serial + 1;
			rec.dwSerial = serial;
			rec.dwTimeStamp = 0;
			rec.data.soa.serial = serial;

			ndr_err = ndr_push_struct_blob(&el->values[i], mem_ctx, &rec,
					(ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
			if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
				*werr = WERR_NOT_ENOUGH_MEMORY;
				return -1;
			}
			break;
		}
	}

	if (serial != -1) {
		el->flags = LDB_FLAG_MOD_REPLACE;
		ret = ldb_modify(samdb, res->msgs[0]);
		if (ret != LDB_SUCCESS) {
			if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
				*werr = WERR_ACCESS_DENIED;
			}
			return -1;
		}
	}

	*werr = WERR_OK;

	return serial;
}


/* Add DNS record to the database */
static WERROR dnsserver_db_do_add_rec(TALLOC_CTX *mem_ctx,
				struct ldb_context *samdb,
				struct ldb_dn *dn,
				int num_rec,
				struct dnsp_DnssrvRpcRecord *rec)
{
	struct ldb_message *msg;
	struct ldb_val v;
	int ret;
	enum ndr_err_code ndr_err;
	int i;

	msg = ldb_msg_new(mem_ctx);
	W_ERROR_HAVE_NO_MEMORY(msg);

	msg->dn = dn;
	ret = ldb_msg_add_string(msg, "objectClass", "dnsNode");
	if (ret != LDB_SUCCESS) {
		return WERR_NOT_ENOUGH_MEMORY;
	}

	if (num_rec > 0 && rec) {
		for (i=0; i<num_rec; i++) {
			ndr_err = ndr_push_struct_blob(&v, mem_ctx, &rec[i],
					(ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
			if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
				return WERR_GEN_FAILURE;
			}

			ret = ldb_msg_add_value(msg, "dnsRecord", &v, NULL);
			if (ret != LDB_SUCCESS) {
				return WERR_NOT_ENOUGH_MEMORY;
			}
		}
	}

	ret = ldb_add(samdb, msg);
	if (ret != LDB_SUCCESS) {
		return WERR_INTERNAL_DB_ERROR;
	}

	return WERR_OK;
}


/* Add dnsNode record to the database with DNS record */
WERROR dnsserver_db_add_empty_node(TALLOC_CTX *mem_ctx,
					struct ldb_context *samdb,
					struct dnsserver_zone *z,
					const char *name)
{
	const char * const attrs[] = { "name", NULL };
	struct ldb_result *res;
	struct ldb_dn *dn;
	char *encoded_name = ldb_binary_encode_string(mem_ctx, name);
	struct ldb_val name_val = data_blob_string_const(name);
	int ret;

	ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_BASE, attrs,
			"(&(objectClass=dnsNode)(name=%s))",
			 encoded_name);
	if (ret != LDB_SUCCESS) {
		return WERR_INTERNAL_DB_ERROR;
	}

	if (res->count > 0) {
		talloc_free(res);
		return WERR_DNS_ERROR_RECORD_ALREADY_EXISTS;
	}

	dn = ldb_dn_copy(mem_ctx, z->zone_dn);
	W_ERROR_HAVE_NO_MEMORY(dn);

	if (!ldb_dn_add_child_val(dn, "DC", name_val)) {
		return WERR_NOT_ENOUGH_MEMORY;
	}

	return dnsserver_db_do_add_rec(mem_ctx, samdb, dn, 0, NULL);
}


/* Add a DNS record */
WERROR dnsserver_db_add_record(TALLOC_CTX *mem_ctx,
					struct ldb_context *samdb,
					struct dnsserver_zone *z,
					const char *name,
					struct DNS_RPC_RECORD *add_record)
{
	const char * const attrs[] = { "dnsRecord", "dNSTombstoned", NULL };
	struct ldb_result *res;
	struct dnsp_DnssrvRpcRecord *rec = NULL;
	struct ldb_message_element *el;
	struct ldb_dn *dn;
	enum ndr_err_code ndr_err;
	int ret, i;
	int serial;
	WERROR werr;
	bool was_tombstoned = false;
	char *encoded_name = ldb_binary_encode_string(mem_ctx, name);

	werr = dns_to_dnsp_convert(mem_ctx, add_record, &rec, true);
	if (!W_ERROR_IS_OK(werr)) {
		return werr;
	}

	/* Set the correct rank for the record. */
	if (z->zoneinfo->dwZoneType == DNS_ZONE_TYPE_PRIMARY) {
		if (strcmp(name, "@") != 0 && rec->wType == DNS_TYPE_NS) {
			rec->rank = DNS_RANK_NS_GLUE;
		} else {
			rec->rank |= DNS_RANK_ZONE;
		}
	} else if (strcmp(z->name, ".") == 0) {
		rec->rank |= DNS_RANK_ROOT_HINT;
	}

	serial = dnsserver_update_soa(mem_ctx, samdb, z, &werr);
	if (serial < 0) {
		return werr;
	}

	rec->dwSerial = serial;
	rec->dwTimeStamp = 0;

	ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
			"(&(objectClass=dnsNode)(name=%s))",
			 encoded_name);
	if (ret != LDB_SUCCESS) {
		return WERR_INTERNAL_DB_ERROR;
	}

	if (res->count == 0) {
		dn = dnsserver_name_to_dn(mem_ctx, z, name);
		W_ERROR_HAVE_NO_MEMORY(dn);

		return dnsserver_db_do_add_rec(mem_ctx, samdb, dn, 1, rec);
	}

	el = ldb_msg_find_element(res->msgs[0], "dnsRecord");
	if (el == NULL) {
		ret = ldb_msg_add_empty(res->msgs[0], "dnsRecord", 0, &el);
		if (ret != LDB_SUCCESS) {
			return WERR_NOT_ENOUGH_MEMORY;
		}
	}

	was_tombstoned = ldb_msg_find_attr_as_bool(res->msgs[0],
						   "dNSTombstoned", false);
	if (was_tombstoned) {
		el->num_values = 0;
	}

	for (i=0; i<el->num_values; i++) {
		struct dnsp_DnssrvRpcRecord rec2;

		ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2,
						(ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
			return WERR_GEN_FAILURE;
		}

		if (dns_record_match(rec, &rec2)) {
			break;
		}
	}
	if (i < el->num_values) {
		return WERR_DNS_ERROR_RECORD_ALREADY_EXISTS;
	}
	if (i == el->num_values) {
		/* adding a new value */
		el->values = talloc_realloc(el, el->values, struct ldb_val, el->num_values+1);
		W_ERROR_HAVE_NO_MEMORY(el->values);
		el->num_values++;
	}

	ndr_err = ndr_push_struct_blob(&el->values[i], mem_ctx, rec,
					(ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		return WERR_GEN_FAILURE;
	}

	el->flags = LDB_FLAG_MOD_REPLACE;

	el = ldb_msg_find_element(res->msgs[0], "dNSTombstoned");
	if (el != NULL) {
		el->flags = LDB_FLAG_MOD_DELETE;
	}

	ret = ldb_modify(samdb, res->msgs[0]);
	if (ret != LDB_SUCCESS) {
		return WERR_INTERNAL_DB_ERROR;
	}

	return WERR_OK;
}


/* Update a DNS record */
WERROR dnsserver_db_update_record(TALLOC_CTX *mem_ctx,
					struct ldb_context *samdb,
					struct dnsserver_zone *z,
					const char *name,
					struct DNS_RPC_RECORD *add_record,
					struct DNS_RPC_RECORD *del_record)
{
	const char * const attrs[] = { "dnsRecord", NULL };
	struct ldb_result *res;
	struct dnsp_DnssrvRpcRecord *arec = NULL, *drec = NULL;
	struct ldb_message_element *el;
	enum ndr_err_code ndr_err;
	int ret, i;
	int serial;
	WERROR werr;
	char *encoded_name = ldb_binary_encode_string(mem_ctx, name);

	werr = dns_to_dnsp_convert(mem_ctx, add_record, &arec, true);
	if (!W_ERROR_IS_OK(werr)) {
		return werr;
	}

	werr = dns_to_dnsp_convert(mem_ctx, del_record, &drec, true);
	if (!W_ERROR_IS_OK(werr)) {
		return werr;
	}

	arec->dwTimeStamp = 0;

	ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
			"(&(objectClass=dnsNode)(name=%s)(!(dNSTombstoned=TRUE)))",
			 encoded_name);
	if (ret != LDB_SUCCESS) {
		return WERR_INTERNAL_DB_ERROR;
	}

	if (res->count == 0) {
		return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST;
	}

	el = ldb_msg_find_element(res->msgs[0], "dnsRecord");
	if (el == NULL || el->num_values == 0) {
		return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST;
	}

	for (i=0; i<el->num_values; i++) {
		struct dnsp_DnssrvRpcRecord rec2;

		ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2,
						(ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
			return WERR_GEN_FAILURE;
		}

		if (dns_record_match(arec, &rec2)) {
			break;
		}
	}
	if (i < el->num_values) {
		return WERR_DNS_ERROR_RECORD_ALREADY_EXISTS;
	}


	for (i=0; i<el->num_values; i++) {
		struct dnsp_DnssrvRpcRecord rec2;

		ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2,
						(ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
			return WERR_GEN_FAILURE;
		}

		if (dns_record_match(drec, &rec2)) {
			break;
		}
	}
	if (i == el->num_values) {
		return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST;
	}

	/* If updating SOA record, use specified serial, otherwise increment */
	if (arec->wType != DNS_TYPE_SOA) {
		serial = dnsserver_update_soa(mem_ctx, samdb, z, &werr);
		if (serial < 0) {
			return werr;
		}
		arec->dwSerial = serial;
	}

	ndr_err = ndr_push_struct_blob(&el->values[i], mem_ctx, arec,
					(ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		return WERR_GEN_FAILURE;
	}

	el->flags = LDB_FLAG_MOD_REPLACE;
	ret = ldb_modify(samdb, res->msgs[0]);
	if (ret != LDB_SUCCESS) {
		return WERR_INTERNAL_DB_ERROR;
	}

	return WERR_OK;
}


/* Delete a DNS record */
WERROR dnsserver_db_delete_record(TALLOC_CTX *mem_ctx,
					struct ldb_context *samdb,
					struct dnsserver_zone *z,
					const char *name,
					struct DNS_RPC_RECORD *del_record)
{
	const char * const attrs[] = { "dnsRecord", NULL };
	struct ldb_result *res;
	struct dnsp_DnssrvRpcRecord *rec = NULL;
	struct ldb_message_element *el;
	enum ndr_err_code ndr_err;
	int ret, i;
	int serial;
	WERROR werr;

	serial = dnsserver_update_soa(mem_ctx, samdb, z, &werr);
	if (serial < 0) {
		return werr;
	}

	werr = dns_to_dnsp_convert(mem_ctx, del_record, &rec, false);
	if (!W_ERROR_IS_OK(werr)) {
		return werr;
	}

	ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
			"(&(objectClass=dnsNode)(name=%s))",
			 ldb_binary_encode_string(mem_ctx, name));
	if (ret != LDB_SUCCESS) {
		return WERR_INTERNAL_DB_ERROR;
	}

	if (res->count == 0) {
		return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST;
	}
	if (res->count > 1) {
		return WERR_DNS_ERROR_RCODE_SERVER_FAILURE;
	}

	el = ldb_msg_find_element(res->msgs[0], "dnsRecord");
	if (el == NULL || el->num_values == 0) {
		return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST;
	}

	for (i=0; i<el->num_values; i++) {
		struct dnsp_DnssrvRpcRecord rec2;

		ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2,
						(ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
			return WERR_GEN_FAILURE;
		}

		if (dns_record_match(rec, &rec2)) {
			break;
		}
	}
	if (i == el->num_values) {
		return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST;
	}
	if (i < el->num_values-1) {
		memmove(&el->values[i], &el->values[i+1], sizeof(el->values[0])*((el->num_values-1)-i));
	}
	el->num_values--;

	if (el->num_values == 0) {
		ret = ldb_delete(samdb, res->msgs[0]->dn);
	} else {
		el->flags = LDB_FLAG_MOD_REPLACE;
		ret = ldb_modify(samdb, res->msgs[0]);
	}
	if (ret != LDB_SUCCESS) {
		return WERR_INTERNAL_DB_ERROR;
	}

	return WERR_OK;
}


static bool dnsserver_db_msg_add_dnsproperty(TALLOC_CTX *mem_ctx,
					     struct ldb_message *msg,
					     struct dnsp_DnsProperty *prop)
{
	DATA_BLOB *prop_blob;
	enum ndr_err_code ndr_err;
	int ret;

	prop_blob = talloc_zero(mem_ctx, DATA_BLOB);
	if (prop_blob == NULL) return false;

	ndr_err = ndr_push_struct_blob(prop_blob, mem_ctx, prop,
			(ndr_push_flags_fn_t)ndr_push_dnsp_DnsProperty);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		return false;
	}
	ret = ldb_msg_add_steal_value(msg, "dNSProperty", prop_blob);
	if (ret != LDB_SUCCESS) {
		return false;
	}
	return true;
}

WERROR dnsserver_db_do_reset_dword(struct ldb_context *samdb,
				   struct dnsserver_zone *z,
				   struct DNS_RPC_NAME_AND_PARAM *n_p)
{
	struct ldb_message_element *element = NULL;
	struct dnsp_DnsProperty *prop = NULL;
	enum ndr_err_code err;
	TALLOC_CTX *tmp_ctx = NULL;
	const char * const attrs[] = {"dNSProperty", NULL};
	struct ldb_result *res = NULL;
	int i, ret, prop_id;

	if (strcasecmp(n_p->pszNodeName, "Aging") == 0) {
		z->zoneinfo->fAging = n_p->dwParam;
		prop_id = DSPROPERTY_ZONE_AGING_STATE;
	} else if (strcasecmp(n_p->pszNodeName, "RefreshInterval") == 0) {
		z->zoneinfo->dwRefreshInterval = n_p->dwParam;
		prop_id = DSPROPERTY_ZONE_REFRESH_INTERVAL;
	} else if (strcasecmp(n_p->pszNodeName, "NoRefreshInterval") == 0) {
		z->zoneinfo->dwNoRefreshInterval = n_p->dwParam;
		prop_id = DSPROPERTY_ZONE_NOREFRESH_INTERVAL;
	} else if (strcasecmp(n_p->pszNodeName, "AllowUpdate") == 0) {
		z->zoneinfo->fAllowUpdate = n_p->dwParam;
		prop_id = DSPROPERTY_ZONE_ALLOW_UPDATE;
	} else {
		return WERR_UNKNOWN_PROPERTY;
	}

	tmp_ctx = talloc_new(NULL);
	if (tmp_ctx == NULL) {
		return WERR_NOT_ENOUGH_MEMORY;
	}

	ret = ldb_search(samdb, tmp_ctx, &res, z->zone_dn, LDB_SCOPE_BASE,
			 attrs, "(objectClass=dnsZone)");
	if (ret != LDB_SUCCESS) {
		DBG_ERR("dnsserver: no zone: %s\n",
			ldb_dn_get_linearized(z->zone_dn));
		TALLOC_FREE(tmp_ctx);
		return WERR_INTERNAL_DB_ERROR;
	}

	if (res->count != 1) {
		DBG_ERR("dnsserver: duplicate zone: %s\n",
			ldb_dn_get_linearized(z->zone_dn));
		TALLOC_FREE(tmp_ctx);
		return WERR_GEN_FAILURE;
	}

	element = ldb_msg_find_element(res->msgs[0], "dNSProperty");
	if (element == NULL) {
		DBG_ERR("dnsserver: zone %s has no properties.\n",
			ldb_dn_get_linearized(z->zone_dn));
		TALLOC_FREE(tmp_ctx);
		return WERR_INTERNAL_DB_ERROR;
	}

	for (i = 0; i < element->num_values; i++) {
		prop = talloc_zero(element, struct dnsp_DnsProperty);
		if (prop == NULL) {
			TALLOC_FREE(tmp_ctx);
			return WERR_NOT_ENOUGH_MEMORY;
		}
		err = ndr_pull_struct_blob(
			&(element->values[i]),
			tmp_ctx,
			prop,
			(ndr_pull_flags_fn_t)ndr_pull_dnsp_DnsProperty);
		if (!NDR_ERR_CODE_IS_SUCCESS(err)){
			DBG_ERR("dnsserver: couldn't PULL dns property id "
				"%d in zone %s\n",
				prop->id,
				ldb_dn_get_linearized(z->zone_dn));
			TALLOC_FREE(tmp_ctx);
			return WERR_INTERNAL_DB_ERROR;
		}

		if (prop->id == prop_id) {
			switch (prop_id) {
			case DSPROPERTY_ZONE_AGING_STATE:
				prop->data.aging_enabled = n_p->dwParam;
				break;
			case DSPROPERTY_ZONE_NOREFRESH_INTERVAL:
				prop->data.norefresh_hours = n_p->dwParam;
				break;
			case DSPROPERTY_ZONE_REFRESH_INTERVAL:
				prop->data.refresh_hours = n_p->dwParam;
				break;
			case DSPROPERTY_ZONE_ALLOW_UPDATE:
				prop->data.allow_update_flag = n_p->dwParam;
				break;
			}

			err = ndr_push_struct_blob(
				&(element->values[i]),
				tmp_ctx,
				prop,
				(ndr_push_flags_fn_t)ndr_push_dnsp_DnsProperty);
			if (!NDR_ERR_CODE_IS_SUCCESS(err)){
				DBG_ERR("dnsserver: couldn't PUSH dns prop id "
					"%d in zone %s\n",
					prop->id,
					ldb_dn_get_linearized(z->zone_dn));
				TALLOC_FREE(tmp_ctx);
				return WERR_INTERNAL_DB_ERROR;
			}
		}
	}

	element->flags = LDB_FLAG_MOD_REPLACE;
	ret = ldb_modify(samdb, res->msgs[0]);
	if (ret != LDB_SUCCESS) {
		TALLOC_FREE(tmp_ctx);
		DBG_ERR("dnsserver: Failed to modify zone %s prop %s: %s\n",
			z->name,
			n_p->pszNodeName,
			ldb_errstring(samdb));
		return WERR_INTERNAL_DB_ERROR;
	}
	TALLOC_FREE(tmp_ctx);

	return WERR_OK;
}

/* Create dnsZone record to database and set security descriptor */
static WERROR dnsserver_db_do_create_zone(TALLOC_CTX *tmp_ctx,
					  struct ldb_context *samdb,
					  struct ldb_dn *zone_dn,
					  struct dnsserver_zone *z)
{
	const char * const attrs[] = { "objectSID", NULL };
	struct ldb_message *msg;
	struct ldb_result *res;
	struct ldb_message_element *el;
	const char sddl_template[] = "D:AI(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)(A;;CC;;;AU)(A;;RPLCLORC;;;WD)(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;%s)(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)(OA;CIID;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)(A;CIID;LC;;;RU)(A;CIID;RPWPCRCCLCLORCWOWDSDSW;;;BA)S:AI";
	char *sddl;
	struct dom_sid dnsadmins_sid;
	const struct dom_sid *domain_sid;
	struct security_descriptor *secdesc;
	struct dnsp_DnsProperty *prop;
	DATA_BLOB *sd_encoded;
	enum ndr_err_code ndr_err;
	int ret;

	/* Get DnsAdmins SID */
	ret = ldb_search(samdb, tmp_ctx, &res, ldb_get_default_basedn(samdb),
			 LDB_SCOPE_DEFAULT, attrs, "(sAMAccountName=DnsAdmins)");
	if (ret != LDB_SUCCESS || res->count != 1) {
		return WERR_INTERNAL_DB_ERROR;
	}

	el = ldb_msg_find_element(res->msgs[0], "objectSID");
	if (el == NULL || el->num_values != 1) {
		return WERR_INTERNAL_DB_ERROR;
	}

	ndr_err = ndr_pull_struct_blob(&el->values[0], tmp_ctx, &dnsadmins_sid,
				(ndr_pull_flags_fn_t)ndr_pull_dom_sid);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		return WERR_INTERNAL_DB_ERROR;
	}

	/* create security descriptor with DnsAdmins GUID in sddl template */
	sddl = talloc_asprintf(tmp_ctx, sddl_template,
			       dom_sid_string(tmp_ctx, &dnsadmins_sid));
	if (sddl == NULL) {
		return WERR_NOT_ENOUGH_MEMORY;
	}
	talloc_free(res);

	domain_sid = samdb_domain_sid(samdb);
	if (domain_sid == NULL) {
		return WERR_INTERNAL_DB_ERROR;
	}

	secdesc = sddl_decode(tmp_ctx, sddl, domain_sid);
	if (secdesc == NULL) {
		return WERR_GEN_FAILURE;
	}

	msg = ldb_msg_new(tmp_ctx);
	W_ERROR_HAVE_NO_MEMORY(msg);

	msg->dn = zone_dn;
	ret = ldb_msg_add_string(msg, "objectClass", "dnsZone");
	if (ret != LDB_SUCCESS) {
		return WERR_NOT_ENOUGH_MEMORY;
	}

	sd_encoded = talloc_zero(tmp_ctx, DATA_BLOB);
	W_ERROR_HAVE_NO_MEMORY(sd_encoded);

	ndr_err = ndr_push_struct_blob(sd_encoded, tmp_ctx, secdesc,
			(ndr_push_flags_fn_t)ndr_push_security_descriptor);
	if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
		return WERR_GEN_FAILURE;
	}

	ret = ldb_msg_add_steal_value(msg, "nTSecurityDescriptor", sd_encoded);
	if (ret != LDB_SUCCESS) {
		return WERR_NOT_ENOUGH_MEMORY;
	}

	/* dns zone Properties */
	prop = talloc_zero(tmp_ctx, struct dnsp_DnsProperty);
	W_ERROR_HAVE_NO_MEMORY(prop);

	prop->version = 1;

	/* zone type */
	prop->id = DSPROPERTY_ZONE_TYPE;
	prop->data.zone_type = z->zoneinfo->dwZoneType;
	if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) {
		return WERR_NOT_ENOUGH_MEMORY;
	}

	/* allow update */
	prop->id = DSPROPERTY_ZONE_ALLOW_UPDATE;
	prop->data.allow_update_flag = z->zoneinfo->fAllowUpdate;
	if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) {
		return WERR_NOT_ENOUGH_MEMORY;
	}

	/* secure time */
	prop->id = DSPROPERTY_ZONE_SECURE_TIME;
	prop->data.zone_secure_time = 0;
	if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) {
		return WERR_NOT_ENOUGH_MEMORY;
	}

	/* norefresh interval */
	prop->id = DSPROPERTY_ZONE_NOREFRESH_INTERVAL;
	prop->data.norefresh_hours = 168;
	if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) {
		return WERR_NOT_ENOUGH_MEMORY;
	}

	/* refresh interval */
	prop->id = DSPROPERTY_ZONE_REFRESH_INTERVAL;
	prop->data.refresh_hours = 168;
	if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) {
		return WERR_NOT_ENOUGH_MEMORY;
	}

	/* aging state */
	prop->id = DSPROPERTY_ZONE_AGING_STATE;
	prop->data.aging_enabled = z->zoneinfo->fAging;
	if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) {
		return WERR_NOT_ENOUGH_MEMORY;
	}

	/* aging enabled time */
	prop->id = DSPROPERTY_ZONE_AGING_ENABLED_TIME;
	prop->data.next_scavenging_cycle_hours = 0;
	if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) {
		return WERR_NOT_ENOUGH_MEMORY;
	}

	talloc_free(prop);

	ret = ldb_add(samdb, msg);
	if (ret != LDB_SUCCESS) {
		DEBUG(0, ("dnsserver: Failed to create zone (%s): %s\n",
		      z->name, ldb_errstring(samdb)));

		if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
			return WERR_ACCESS_DENIED;
		}

		return WERR_INTERNAL_DB_ERROR;
	}

	return WERR_OK;
}


/* Create new dnsZone record and @ record (SOA + NS) */
WERROR dnsserver_db_create_zone(struct ldb_context *samdb,
				struct dnsserver_partition *partitions,
				struct dnsserver_zone *zone,
				struct loadparm_context *lp_ctx)
{
	struct dnsserver_partition *p;
	bool in_forest = false;
	WERROR status;
	struct ldb_dn *dn;
	TALLOC_CTX *tmp_ctx;
	struct dnsp_DnssrvRpcRecord *dns_rec;
	struct dnsp_soa soa;
	char *tmpstr, *server_fqdn, *soa_email;
	struct ldb_val name_val = data_blob_string_const(zone->name);

	/* We only support primary zones for now */
	if (zone->zoneinfo->dwZoneType != DNS_ZONE_TYPE_PRIMARY) {
		return WERR_CALL_NOT_IMPLEMENTED;
	}

	/* Get the correct partition */
	if (zone->partition->dwDpFlags & DNS_DP_FOREST_DEFAULT) {
		in_forest = true;
	}
	for (p = partitions; p; p = p->next) {
		if (in_forest == p->is_forest) {
			break;
		}
	}
	if (p == NULL) {
		return WERR_DNS_ERROR_DP_DOES_NOT_EXIST;
	}

	tmp_ctx = talloc_new(NULL);
	W_ERROR_HAVE_NO_MEMORY(tmp_ctx);

	dn = ldb_dn_copy(tmp_ctx, p->partition_dn);
	W_ERROR_HAVE_NO_MEMORY_AND_FREE(dn, tmp_ctx);

	if (!ldb_dn_add_child_fmt(dn, "CN=MicrosoftDNS")) {
		talloc_free(tmp_ctx);
		return WERR_NOT_ENOUGH_MEMORY;
	}

	if (!ldb_dn_add_child_val(dn, "DC", name_val)) {
		talloc_free(tmp_ctx);
		return WERR_NOT_ENOUGH_MEMORY;
	}

	/* Add dnsZone record */
	status = dnsserver_db_do_create_zone(tmp_ctx, samdb, dn, zone);
	if (!W_ERROR_IS_OK(status)) {
		talloc_free(tmp_ctx);
		return status;
	}

	if (!ldb_dn_add_child_fmt(dn, "DC=@")) {
		talloc_free(tmp_ctx);
		return WERR_NOT_ENOUGH_MEMORY;
	}

	dns_rec = talloc_zero_array(tmp_ctx, struct dnsp_DnssrvRpcRecord, 2);
	W_ERROR_HAVE_NO_MEMORY_AND_FREE(dns_rec, tmp_ctx);

	tmpstr = talloc_asprintf(tmp_ctx, "%s.%s",
				 lpcfg_netbios_name(lp_ctx),
				 lpcfg_realm(lp_ctx));
	W_ERROR_HAVE_NO_MEMORY_AND_FREE(tmpstr, tmp_ctx);
	server_fqdn = strlower_talloc(tmp_ctx, tmpstr);
	W_ERROR_HAVE_NO_MEMORY_AND_FREE(server_fqdn, tmp_ctx);
	talloc_free(tmpstr);

	tmpstr = talloc_asprintf(tmp_ctx, "hostmaster.%s",
				  lpcfg_realm(lp_ctx));
	W_ERROR_HAVE_NO_MEMORY_AND_FREE(tmpstr, tmp_ctx);
	soa_email = strlower_talloc(tmp_ctx, tmpstr);
	W_ERROR_HAVE_NO_MEMORY_AND_FREE(soa_email, tmp_ctx);
	talloc_free(tmpstr);

	/* SOA Record - values same as defined in provision/sambadns.py */
	soa.serial = 1;
	soa.refresh = 900;
	soa.retry = 600;
	soa.expire = 86400;
	soa.minimum = 3600;
	soa.mname = server_fqdn;
	soa.rname = soa_email;

	dns_rec[0].wType = DNS_TYPE_SOA;
	dns_rec[0].rank = DNS_RANK_ZONE;
	dns_rec[0].dwSerial = soa.serial;
	dns_rec[0].dwTtlSeconds = 3600;
	dns_rec[0].dwTimeStamp = 0;
	dns_rec[0].data.soa = soa;

	/* NS Record */
	dns_rec[1].wType = DNS_TYPE_NS;
	dns_rec[1].rank = DNS_RANK_ZONE;
	dns_rec[1].dwSerial = soa.serial;
	dns_rec[1].dwTtlSeconds = 3600;
	dns_rec[1].dwTimeStamp = 0;
	dns_rec[1].data.ns = server_fqdn;

	/* Add @ Record */
	status = dnsserver_db_do_add_rec(tmp_ctx, samdb, dn, 2, dns_rec);

	talloc_free(tmp_ctx);
	return status;
}


/* Delete dnsZone record and all DNS records in the zone */
WERROR dnsserver_db_delete_zone(struct ldb_context *samdb,
				struct dnsserver_zone *zone)
{
	int ret;

	ret = ldb_transaction_start(samdb);
	if (ret != LDB_SUCCESS) {
		return WERR_INTERNAL_DB_ERROR;
	}

	ret = dsdb_delete(samdb, zone->zone_dn, DSDB_TREE_DELETE);
	if (ret != LDB_SUCCESS) {
		ldb_transaction_cancel(samdb);

		if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) {
			return WERR_ACCESS_DENIED;
		}
		return WERR_INTERNAL_DB_ERROR;
	}

	ret = ldb_transaction_commit(samdb);
	if (ret != LDB_SUCCESS) {
		return WERR_INTERNAL_DB_ERROR;
	}

	return WERR_OK;
}
