summaryrefslogtreecommitdiffstats
path: root/source4/dns_server/dnsserver_common.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--source4/dns_server/dnsserver_common.c1589
1 files changed, 1589 insertions, 0 deletions
diff --git a/source4/dns_server/dnsserver_common.c b/source4/dns_server/dnsserver_common.c
new file mode 100644
index 0000000..0481b07
--- /dev/null
+++ b/source4/dns_server/dnsserver_common.c
@@ -0,0 +1,1589 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ DNS server utils
+
+ Copyright (C) 2010 Kai Blin
+ Copyright (C) 2014 Stefan Metzmacher
+ Copyright (C) 2015 Andrew Bartlett
+
+ 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 "libcli/util/ntstatus.h"
+#include "libcli/util/werror.h"
+#include "librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ndr_dns.h"
+#include "librpc/gen_ndr/ndr_dnsp.h"
+#include <ldb.h>
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
+#include "dns_server/dnsserver_common.h"
+#include "rpc_server/dnsserver/dnsserver.h"
+#include "lib/util/dlinklist.h"
+#include "system/network.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_DNS
+
+#undef strncasecmp
+
+uint8_t werr_to_dns_err(WERROR werr)
+{
+ if (W_ERROR_EQUAL(WERR_OK, werr)) {
+ return DNS_RCODE_OK;
+ } else if (W_ERROR_EQUAL(DNS_ERR(FORMAT_ERROR), werr)) {
+ return DNS_RCODE_FORMERR;
+ } else if (W_ERROR_EQUAL(DNS_ERR(SERVER_FAILURE), werr)) {
+ return DNS_RCODE_SERVFAIL;
+ } else if (W_ERROR_EQUAL(DNS_ERR(NAME_ERROR), werr)) {
+ return DNS_RCODE_NXDOMAIN;
+ } else if (W_ERROR_EQUAL(WERR_DNS_ERROR_NAME_DOES_NOT_EXIST, werr)) {
+ return DNS_RCODE_NXDOMAIN;
+ } else if (W_ERROR_EQUAL(DNS_ERR(NOT_IMPLEMENTED), werr)) {
+ return DNS_RCODE_NOTIMP;
+ } else if (W_ERROR_EQUAL(DNS_ERR(REFUSED), werr)) {
+ return DNS_RCODE_REFUSED;
+ } else if (W_ERROR_EQUAL(DNS_ERR(YXDOMAIN), werr)) {
+ return DNS_RCODE_YXDOMAIN;
+ } else if (W_ERROR_EQUAL(DNS_ERR(YXRRSET), werr)) {
+ return DNS_RCODE_YXRRSET;
+ } else if (W_ERROR_EQUAL(DNS_ERR(NXRRSET), werr)) {
+ return DNS_RCODE_NXRRSET;
+ } else if (W_ERROR_EQUAL(DNS_ERR(NOTAUTH), werr)) {
+ return DNS_RCODE_NOTAUTH;
+ } else if (W_ERROR_EQUAL(DNS_ERR(NOTZONE), werr)) {
+ return DNS_RCODE_NOTZONE;
+ } else if (W_ERROR_EQUAL(DNS_ERR(BADKEY), werr)) {
+ return DNS_RCODE_BADKEY;
+ }
+ DEBUG(5, ("No mapping exists for %s\n", win_errstr(werr)));
+ return DNS_RCODE_SERVFAIL;
+}
+
+WERROR dns_common_extract(struct ldb_context *samdb,
+ const struct ldb_message_element *el,
+ TALLOC_CTX *mem_ctx,
+ struct dnsp_DnssrvRpcRecord **records,
+ uint16_t *num_records)
+{
+ uint16_t ri;
+ struct dnsp_DnssrvRpcRecord *recs;
+
+ *records = NULL;
+ *num_records = 0;
+
+ recs = talloc_zero_array(mem_ctx, struct dnsp_DnssrvRpcRecord,
+ el->num_values);
+ if (recs == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ for (ri = 0; ri < el->num_values; ri++) {
+ bool am_rodc;
+ int ret;
+ const char *dnsHostName = NULL;
+ struct ldb_val *v = &el->values[ri];
+ enum ndr_err_code ndr_err;
+ ndr_err = ndr_pull_struct_blob(v, recs, &recs[ri],
+ (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ TALLOC_FREE(recs);
+ DEBUG(0, ("Failed to grab dnsp_DnssrvRpcRecord\n"));
+ return DNS_ERR(SERVER_FAILURE);
+ }
+
+ /*
+ * In AD, except on an RODC (where we should list a random RWDC,
+ * we should over-stamp the MNAME with our own hostname
+ */
+ if (recs[ri].wType != DNS_TYPE_SOA) {
+ continue;
+ }
+
+ ret = samdb_rodc(samdb, &am_rodc);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("Failed to confirm we are not an RODC: %s\n",
+ ldb_errstring(samdb)));
+ return DNS_ERR(SERVER_FAILURE);
+ }
+
+ if (am_rodc) {
+ continue;
+ }
+
+ ret = samdb_dns_host_name(samdb, &dnsHostName);
+ if (ret != LDB_SUCCESS || dnsHostName == NULL) {
+ DEBUG(0, ("Failed to get dnsHostName from rootDSE"));
+ return DNS_ERR(SERVER_FAILURE);
+ }
+
+ recs[ri].data.soa.mname = talloc_strdup(recs, dnsHostName);
+ }
+
+ *records = recs;
+ *num_records = el->num_values;
+ return WERR_OK;
+}
+
+/*
+ * Lookup a DNS record, performing an exact match.
+ * i.e. DNS wild card records are not considered.
+ */
+WERROR dns_common_lookup(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn,
+ struct dnsp_DnssrvRpcRecord **records,
+ uint16_t *num_records,
+ bool *tombstoned)
+{
+ const struct timeval start = timeval_current();
+ static const char * const attrs[] = {
+ "dnsRecord",
+ "dNSTombstoned",
+ NULL
+ };
+ int ret;
+ WERROR werr = WERR_OK;
+ struct ldb_message *msg = NULL;
+ struct ldb_message_element *el;
+
+ *records = NULL;
+ *num_records = 0;
+
+ if (tombstoned != NULL) {
+ *tombstoned = false;
+ ret = dsdb_search_one(samdb, mem_ctx, &msg, dn,
+ LDB_SCOPE_BASE, attrs, 0,
+ "(objectClass=dnsNode)");
+ } else {
+ ret = dsdb_search_one(samdb, mem_ctx, &msg, dn,
+ LDB_SCOPE_BASE, attrs, 0,
+ "(&(objectClass=dnsNode)(!(dNSTombstoned=TRUE)))");
+ }
+ if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+ werr = WERR_DNS_ERROR_NAME_DOES_NOT_EXIST;
+ goto exit;
+ }
+ if (ret != LDB_SUCCESS) {
+ /* TODO: we need to check if there's a glue record we need to
+ * create a referral to */
+ werr = DNS_ERR(NAME_ERROR);
+ goto exit;
+ }
+
+ if (tombstoned != NULL) {
+ *tombstoned = ldb_msg_find_attr_as_bool(msg,
+ "dNSTombstoned", false);
+ }
+
+ el = ldb_msg_find_element(msg, "dnsRecord");
+ if (el == NULL) {
+ TALLOC_FREE(msg);
+ /*
+ * records produced by older Samba releases
+ * keep dnsNode objects without dnsRecord and
+ * without setting dNSTombstoned=TRUE.
+ *
+ * We just pretend they're tombstones.
+ */
+ if (tombstoned != NULL) {
+ struct dnsp_DnssrvRpcRecord *recs;
+ recs = talloc_array(mem_ctx,
+ struct dnsp_DnssrvRpcRecord,
+ 1);
+ if (recs == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto exit;
+ }
+ recs[0] = (struct dnsp_DnssrvRpcRecord) {
+ .wType = DNS_TYPE_TOMBSTONE,
+ /*
+ * A value of timestamp != 0
+ * indicated that the object was already
+ * a tombstone, this will be used
+ * in dns_common_replace()
+ */
+ .data.EntombedTime = 1,
+ };
+
+ *tombstoned = true;
+ *records = recs;
+ *num_records = 1;
+ werr = WERR_OK;
+ goto exit;
+ } else {
+ /*
+ * Because we are not looking for a tombstone
+ * in this codepath, we just pretend it does
+ * not exist at all.
+ */
+ werr = WERR_DNS_ERROR_NAME_DOES_NOT_EXIST;
+ goto exit;
+ }
+ }
+
+ werr = dns_common_extract(samdb, el, mem_ctx, records, num_records);
+ TALLOC_FREE(msg);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto exit;
+ }
+
+ werr = WERR_OK;
+exit:
+ DNS_COMMON_LOG_OPERATION(
+ win_errstr(werr),
+ &start,
+ NULL,
+ dn == NULL ? NULL : ldb_dn_get_linearized(dn),
+ NULL);
+ return werr;
+}
+
+/*
+ * Build an ldb_parse_tree node for an equality check
+ *
+ * Note: name is assumed to have been validated by dns_name_check
+ * so will be zero terminated and of a reasonable size.
+ */
+static struct ldb_parse_tree *build_equality_operation(
+ TALLOC_CTX *mem_ctx,
+ bool add_asterix, /* prepend an '*' to the name */
+ const uint8_t *name, /* the value being matched */
+ const char *attr, /* the attribute to check name against */
+ size_t size) /* length of name */
+{
+
+ struct ldb_parse_tree *el = NULL; /* Equality node being built */
+ struct ldb_val *value = NULL; /* Value the attr will be compared
+ with */
+ size_t length = 0; /* calculated length of the value
+ including option '*' prefix and
+ '\0' string terminator */
+
+ el = talloc(mem_ctx, struct ldb_parse_tree);
+ if (el == NULL) {
+ DBG_ERR("Unable to allocate ldb_parse_tree\n");
+ return NULL;
+ }
+
+ el->operation = LDB_OP_EQUALITY;
+ el->u.equality.attr = talloc_strdup(mem_ctx, attr);
+ value = &el->u.equality.value;
+ length = (add_asterix) ? size + 2 : size + 1;
+ value->data = talloc_zero_array(el, uint8_t, length);
+ if (value->data == NULL) {
+ DBG_ERR("Unable to allocate value->data\n");
+ TALLOC_FREE(el);
+ return NULL;
+ }
+
+ value->length = length;
+ if (add_asterix) {
+ value->data[0] = '*';
+ if (name != NULL) {
+ memcpy(&value->data[1], name, size);
+ }
+ } else if (name != NULL) {
+ memcpy(value->data, name, size);
+ }
+ return el;
+}
+
+/*
+ * Determine the number of levels in name
+ * essentially the number of '.'s in the name + 1
+ *
+ * name is assumed to have been validated by dns_name_check
+ */
+static unsigned int number_of_labels(const struct ldb_val *name) {
+ int x = 0;
+ unsigned int labels = 1;
+ for (x = 0; x < name->length; x++) {
+ if (name->data[x] == '.') {
+ labels++;
+ }
+ }
+ return labels;
+}
+/*
+ * Build a query that matches the target name, and any possible
+ * DNS wild card entries
+ *
+ * Builds a parse tree equivalent to the example query.
+ *
+ * x.y.z -> (|(name=x.y.z)(name=\2a.y.z)(name=\2a.z)(name=\2a))
+ *
+ * The attribute 'name' is used as this is what the LDB index is on
+ * (the RDN, being 'dc' in this use case, does not have an index in
+ * the AD schema).
+ *
+ * Returns NULL if unable to build the query.
+ *
+ * The first component of the DN is assumed to be the name being looked up
+ * and also that it has been validated by dns_name_check
+ *
+ */
+#define BASE "(&(objectClass=dnsNode)(!(dNSTombstoned=TRUE))(|(a=b)(c=d)))"
+static struct ldb_parse_tree *build_wildcard_query(
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn)
+{
+ const struct ldb_val *name = NULL; /* The DNS name being
+ queried */
+ const char *attr = "name"; /* The attribute name */
+ struct ldb_parse_tree *query = NULL; /* The constructed query
+ parse tree*/
+ struct ldb_parse_tree *wildcard_query = NULL; /* The parse tree for the
+ name and wild card
+ entries */
+ int labels = 0; /* The number of labels in the name */
+
+ name = ldb_dn_get_rdn_val(dn);
+ if (name == NULL) {
+ DBG_ERR("Unable to get domain name value\n");
+ return NULL;
+ }
+ labels = number_of_labels(name);
+
+ query = ldb_parse_tree(mem_ctx, BASE);
+ if (query == NULL) {
+ DBG_ERR("Unable to parse query %s\n", BASE);
+ return NULL;
+ }
+
+ /*
+ * The 3rd element of BASE is a place holder which is replaced with
+ * the actual wild card query
+ */
+ wildcard_query = query->u.list.elements[2];
+ TALLOC_FREE(wildcard_query->u.list.elements);
+
+ wildcard_query->u.list.num_elements = labels + 1;
+ wildcard_query->u.list.elements = talloc_array(
+ wildcard_query,
+ struct ldb_parse_tree *,
+ labels + 1);
+ /*
+ * Build the wild card query
+ */
+ {
+ int x = 0; /* current character in the name */
+ int l = 0; /* current equality operator index in elements */
+ struct ldb_parse_tree *el = NULL; /* Equality operator being
+ built */
+ bool add_asterix = true; /* prepend an '*' to the value */
+ for (l = 0, x = 0; l < labels && x < name->length; l++) {
+ unsigned int size = name->length - x;
+ add_asterix = (name->data[x] == '.');
+ el = build_equality_operation(
+ mem_ctx,
+ add_asterix,
+ &name->data[x],
+ attr,
+ size);
+ if (el == NULL) {
+ return NULL; /* Reason will have been logged */
+ }
+ wildcard_query->u.list.elements[l] = el;
+
+ /* skip to the start of the next label */
+ x++;
+ for (;x < name->length && name->data[x] != '.'; x++);
+ }
+
+ /* Add the base level "*" only query */
+ el = build_equality_operation(mem_ctx, true, NULL, attr, 0);
+ if (el == NULL) {
+ TALLOC_FREE(query);
+ return NULL; /* Reason will have been logged */
+ }
+ wildcard_query->u.list.elements[l] = el;
+ }
+ return query;
+}
+
+/*
+ * Scan the list of records matching a dns wildcard query and return the
+ * best match.
+ *
+ * The best match is either an exact name match, or the longest wild card
+ * entry returned
+ *
+ * i.e. name = a.b.c candidates *.b.c, *.c, - *.b.c would be selected
+ * name = a.b.c candidates a.b.c, *.b.c, *.c - a.b.c would be selected
+ */
+static struct ldb_message *get_best_match(struct ldb_dn *dn,
+ struct ldb_result *result)
+{
+ int matched = 0; /* Index of the current best match in result */
+ size_t length = 0; /* The length of the current candidate */
+ const struct ldb_val *target = NULL; /* value we're looking for */
+ const struct ldb_val *candidate = NULL; /* current candidate value */
+ int x = 0;
+
+ target = ldb_dn_get_rdn_val(dn);
+ for(x = 0; x < result->count; x++) {
+ candidate = ldb_dn_get_rdn_val(result->msgs[x]->dn);
+ if (strncasecmp((char *) target->data,
+ (char *) candidate->data,
+ target->length) == 0) {
+ /* Exact match stop searching and return */
+ return result->msgs[x];
+ }
+ if (candidate->length > length) {
+ matched = x;
+ length = candidate->length;
+ }
+ }
+ return result->msgs[matched];
+}
+
+/*
+ * Look up a DNS entry, if an exact match does not exist, return the
+ * closest matching DNS wildcard entry if available
+ *
+ * Returns: LDB_ERR_NO_SUCH_OBJECT If no matching record exists
+ * LDB_ERR_OPERATIONS_ERROR If the query fails
+ * LDB_SUCCESS If a matching record was retrieved
+ *
+ */
+static int dns_wildcard_lookup(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn,
+ struct ldb_message **msg)
+{
+ static const char * const attrs[] = {
+ "dnsRecord",
+ "dNSTombstoned",
+ NULL
+ };
+ struct ldb_dn *parent = NULL; /* The parent dn */
+ struct ldb_result *result = NULL; /* Results of the search */
+ int ret; /* Return code */
+ struct ldb_parse_tree *query = NULL; /* The query to run */
+ struct ldb_request *request = NULL; /* LDB request for the query op */
+ struct ldb_message *match = NULL; /* the best matching DNS record */
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ parent = ldb_dn_get_parent(frame, dn);
+ if (parent == NULL) {
+ DBG_ERR("Unable to extract parent from dn\n");
+ TALLOC_FREE(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ query = build_wildcard_query(frame, dn);
+ if (query == NULL) {
+ TALLOC_FREE(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ result = talloc_zero(mem_ctx, struct ldb_result);
+ if (result == NULL) {
+ TALLOC_FREE(frame);
+ DBG_ERR("Unable to allocate ldb_result\n");
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ ret = ldb_build_search_req_ex(&request,
+ samdb,
+ frame,
+ parent,
+ LDB_SCOPE_SUBTREE,
+ query,
+ attrs,
+ NULL,
+ result,
+ ldb_search_default_callback,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(frame);
+ DBG_ERR("ldb_build_search_req_ex returned %d\n", ret);
+ return ret;
+ }
+
+ ret = ldb_request(samdb, request);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ ret = ldb_wait(request->handle, LDB_WAIT_ALL);
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ if (result->count == 0) {
+ TALLOC_FREE(frame);
+ return LDB_ERR_NO_SUCH_OBJECT;
+ }
+
+ match = get_best_match(dn, result);
+ if (match == NULL) {
+ TALLOC_FREE(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ *msg = talloc_move(mem_ctx, &match);
+ TALLOC_FREE(frame);
+ return LDB_SUCCESS;
+}
+
+/*
+ * Lookup a DNS record, will match DNS wild card records if an exact match
+ * is not found.
+ */
+WERROR dns_common_wildcard_lookup(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn,
+ struct dnsp_DnssrvRpcRecord **records,
+ uint16_t *num_records)
+{
+ const struct timeval start = timeval_current();
+ int ret;
+ WERROR werr = WERR_OK;
+ struct ldb_message *msg = NULL;
+ struct ldb_message_element *el = NULL;
+ const struct ldb_val *name = NULL;
+
+ *records = NULL;
+ *num_records = 0;
+
+ name = ldb_dn_get_rdn_val(dn);
+ if (name == NULL) {
+ werr = DNS_ERR(NAME_ERROR);
+ goto exit;
+ }
+
+ /* Don't look for a wildcard for @ */
+ if (name->length == 1 && name->data[0] == '@') {
+ werr = dns_common_lookup(samdb,
+ mem_ctx,
+ dn,
+ records,
+ num_records,
+ NULL);
+ goto exit;
+ }
+
+ werr = dns_name_check(
+ mem_ctx,
+ strlen((const char*)name->data),
+ (const char*) name->data);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto exit;
+ }
+
+ /*
+ * Do a point search first, then fall back to a wildcard
+ * lookup if it does not exist
+ */
+ werr = dns_common_lookup(samdb,
+ mem_ctx,
+ dn,
+ records,
+ num_records,
+ NULL);
+ if (!W_ERROR_EQUAL(werr, WERR_DNS_ERROR_NAME_DOES_NOT_EXIST)) {
+ goto exit;
+ }
+
+ ret = dns_wildcard_lookup(samdb, mem_ctx, dn, &msg);
+ if (ret == LDB_ERR_OPERATIONS_ERROR) {
+ werr = DNS_ERR(SERVER_FAILURE);
+ goto exit;
+ }
+ if (ret != LDB_SUCCESS) {
+ werr = DNS_ERR(NAME_ERROR);
+ goto exit;
+ }
+
+ el = ldb_msg_find_element(msg, "dnsRecord");
+ if (el == NULL) {
+ werr = WERR_DNS_ERROR_NAME_DOES_NOT_EXIST;
+ goto exit;
+ }
+
+ werr = dns_common_extract(samdb, el, mem_ctx, records, num_records);
+ TALLOC_FREE(msg);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto exit;
+ }
+
+ werr = WERR_OK;
+exit:
+ DNS_COMMON_LOG_OPERATION(
+ win_errstr(werr),
+ &start,
+ NULL,
+ dn == NULL ? NULL : ldb_dn_get_linearized(dn),
+ NULL);
+ return werr;
+}
+
+static int rec_cmp(const struct dnsp_DnssrvRpcRecord *r1,
+ const struct dnsp_DnssrvRpcRecord *r2)
+{
+ if (r1->wType != r2->wType) {
+ /*
+ * The records are sorted with higher types first,
+ * which puts tombstones (type 0) last.
+ */
+ return r2->wType - r1->wType;
+ }
+ /*
+ * Then we need to sort from the oldest to newest timestamp.
+ *
+ * Note that dwTimeStamp == 0 (never expiring) records come first,
+ * then the ones whose expiry is soonest.
+ */
+ return r1->dwTimeStamp - r2->dwTimeStamp;
+}
+
+/*
+ * Check for valid DNS names. These are names which:
+ * - are non-empty
+ * - do not start with a dot
+ * - do not have any empty labels
+ * - have no more than 127 labels
+ * - are no longer than 253 characters
+ * - none of the labels exceed 63 characters
+ */
+WERROR dns_name_check(TALLOC_CTX *mem_ctx, size_t len, const char *name)
+{
+ size_t i;
+ unsigned int labels = 0;
+ unsigned int label_len = 0;
+
+ if (len == 0) {
+ return WERR_DS_INVALID_DN_SYNTAX;
+ }
+
+ if (len > 1 && name[0] == '.') {
+ return WERR_DS_INVALID_DN_SYNTAX;
+ }
+
+ if ((len - 1) > DNS_MAX_DOMAIN_LENGTH) {
+ return WERR_DS_INVALID_DN_SYNTAX;
+ }
+
+ for (i = 0; i < len - 1; i++) {
+ if (name[i] == '.' && name[i+1] == '.') {
+ return WERR_DS_INVALID_DN_SYNTAX;
+ }
+ if (name[i] == '.') {
+ labels++;
+ if (labels > DNS_MAX_LABELS) {
+ return WERR_DS_INVALID_DN_SYNTAX;
+ }
+ label_len = 0;
+ } else {
+ label_len++;
+ if (label_len > DNS_MAX_LABEL_LENGTH) {
+ return WERR_DS_INVALID_DN_SYNTAX;
+ }
+ }
+ }
+
+ return WERR_OK;
+}
+
+static WERROR check_name_list(TALLOC_CTX *mem_ctx, uint16_t rec_count,
+ struct dnsp_DnssrvRpcRecord *records)
+{
+ WERROR werr;
+ uint16_t i;
+ size_t len;
+ struct dnsp_DnssrvRpcRecord record;
+
+ werr = WERR_OK;
+ for (i = 0; i < rec_count; i++) {
+ record = records[i];
+
+ switch (record.wType) {
+
+ case DNS_TYPE_NS:
+ len = strlen(record.data.ns);
+ werr = dns_name_check(mem_ctx, len, record.data.ns);
+ break;
+ case DNS_TYPE_CNAME:
+ len = strlen(record.data.cname);
+ werr = dns_name_check(mem_ctx, len, record.data.cname);
+ break;
+ case DNS_TYPE_SOA:
+ len = strlen(record.data.soa.mname);
+ werr = dns_name_check(mem_ctx, len, record.data.soa.mname);
+ if (!W_ERROR_IS_OK(werr)) {
+ break;
+ }
+ len = strlen(record.data.soa.rname);
+ werr = dns_name_check(mem_ctx, len, record.data.soa.rname);
+ break;
+ case DNS_TYPE_PTR:
+ len = strlen(record.data.ptr);
+ werr = dns_name_check(mem_ctx, len, record.data.ptr);
+ break;
+ case DNS_TYPE_MX:
+ len = strlen(record.data.mx.nameTarget);
+ werr = dns_name_check(mem_ctx, len, record.data.mx.nameTarget);
+ break;
+ case DNS_TYPE_SRV:
+ len = strlen(record.data.srv.nameTarget);
+ werr = dns_name_check(mem_ctx, len,
+ record.data.srv.nameTarget);
+ break;
+ /*
+ * In the default case, the record doesn't have a DN, so it
+ * must be ok.
+ */
+ default:
+ break;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+ }
+
+ return WERR_OK;
+}
+
+bool dns_name_is_static(struct dnsp_DnssrvRpcRecord *records,
+ uint16_t rec_count)
+{
+ int i = 0;
+ for (i = 0; i < rec_count; i++) {
+ if (records[i].wType == DNS_TYPE_TOMBSTONE) {
+ continue;
+ }
+
+ if (records[i].wType == DNS_TYPE_SOA ||
+ records[i].dwTimeStamp == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * Helper function to copy a dnsp_ip4_array struct to an IP4_ARRAY struct.
+ * The new structure and it's data are allocated on the supplied talloc context
+ */
+static struct IP4_ARRAY *copy_ip4_array(TALLOC_CTX *ctx,
+ const char *name,
+ struct dnsp_ip4_array array)
+{
+
+ struct IP4_ARRAY *ip4_array = NULL;
+ unsigned int i;
+
+ ip4_array = talloc_zero(ctx, struct IP4_ARRAY);
+ if (ip4_array == NULL) {
+ DBG_ERR("Out of memory copying property [%s]\n", name);
+ return NULL;
+ }
+
+ ip4_array->AddrCount = array.addrCount;
+ if (ip4_array->AddrCount == 0) {
+ return ip4_array;
+ }
+
+ ip4_array->AddrArray =
+ talloc_array(ip4_array, uint32_t, ip4_array->AddrCount);
+ if (ip4_array->AddrArray == NULL) {
+ TALLOC_FREE(ip4_array);
+ DBG_ERR("Out of memory copying property [%s] values\n", name);
+ return NULL;
+ }
+
+ for (i = 0; i < ip4_array->AddrCount; i++) {
+ ip4_array->AddrArray[i] = array.addrArray[i];
+ }
+
+ return ip4_array;
+}
+
+bool dns_zoneinfo_load_zone_property(struct dnsserver_zoneinfo *zoneinfo,
+ struct dnsp_DnsProperty *prop)
+{
+ switch (prop->id) {
+ case DSPROPERTY_ZONE_TYPE:
+ zoneinfo->dwZoneType = prop->data.zone_type;
+ break;
+ case DSPROPERTY_ZONE_ALLOW_UPDATE:
+ zoneinfo->fAllowUpdate = prop->data.allow_update_flag;
+ break;
+ case DSPROPERTY_ZONE_NOREFRESH_INTERVAL:
+ zoneinfo->dwNoRefreshInterval = prop->data.norefresh_hours;
+ break;
+ case DSPROPERTY_ZONE_REFRESH_INTERVAL:
+ zoneinfo->dwRefreshInterval = prop->data.refresh_hours;
+ break;
+ case DSPROPERTY_ZONE_AGING_STATE:
+ zoneinfo->fAging = prop->data.aging_enabled;
+ break;
+ case DSPROPERTY_ZONE_SCAVENGING_SERVERS:
+ zoneinfo->aipScavengeServers = copy_ip4_array(
+ zoneinfo, "ZONE_SCAVENGING_SERVERS", prop->data.servers);
+ if (zoneinfo->aipScavengeServers == NULL) {
+ return false;
+ }
+ break;
+ case DSPROPERTY_ZONE_AGING_ENABLED_TIME:
+ zoneinfo->dwAvailForScavengeTime =
+ prop->data.next_scavenging_cycle_hours;
+ break;
+ case DSPROPERTY_ZONE_MASTER_SERVERS:
+ zoneinfo->aipLocalMasters = copy_ip4_array(
+ zoneinfo, "ZONE_MASTER_SERVERS", prop->data.master_servers);
+ if (zoneinfo->aipLocalMasters == NULL) {
+ return false;
+ }
+ break;
+ case DSPROPERTY_ZONE_EMPTY:
+ case DSPROPERTY_ZONE_SECURE_TIME:
+ case DSPROPERTY_ZONE_DELETED_FROM_HOSTNAME:
+ case DSPROPERTY_ZONE_AUTO_NS_SERVERS:
+ case DSPROPERTY_ZONE_DCPROMO_CONVERT:
+ case DSPROPERTY_ZONE_SCAVENGING_SERVERS_DA:
+ case DSPROPERTY_ZONE_MASTER_SERVERS_DA:
+ case DSPROPERTY_ZONE_NS_SERVERS_DA:
+ case DSPROPERTY_ZONE_NODE_DBFLAGS:
+ break;
+ }
+ return true;
+}
+WERROR dns_get_zone_properties(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *zone_dn,
+ struct dnsserver_zoneinfo *zoneinfo)
+{
+
+ int ret, i;
+ struct dnsp_DnsProperty *prop = NULL;
+ struct ldb_message_element *element = NULL;
+ const char *const attrs[] = {"dNSProperty", NULL};
+ struct ldb_result *res = NULL;
+ enum ndr_err_code err;
+
+ ret = ldb_search(samdb,
+ mem_ctx,
+ &res,
+ zone_dn,
+ LDB_SCOPE_BASE,
+ attrs,
+ "(objectClass=dnsZone)");
+ if (ret != LDB_SUCCESS) {
+ DBG_ERR("dnsserver: Failed to find DNS zone: %s\n",
+ ldb_dn_get_linearized(zone_dn));
+ return DNS_ERR(SERVER_FAILURE);
+ }
+
+ element = ldb_msg_find_element(res->msgs[0], "dNSProperty");
+ if (element == NULL) {
+ return DNS_ERR(NOTZONE);
+ }
+
+ for (i = 0; i < element->num_values; i++) {
+ bool valid_property;
+ prop = talloc_zero(mem_ctx, struct dnsp_DnsProperty);
+ if (prop == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ err = ndr_pull_struct_blob(
+ &(element->values[i]),
+ mem_ctx,
+ prop,
+ (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnsProperty);
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ /*
+ * If we can't pull it, then there is no valid
+ * data to load into the zone, so ignore this
+ * as Micosoft does. Windows can load an
+ * invalid property with a zero length into
+ * the dnsProperty attribute.
+ */
+ continue;
+ }
+
+ valid_property =
+ dns_zoneinfo_load_zone_property(zoneinfo, prop);
+ if (!valid_property) {
+ return DNS_ERR(SERVER_FAILURE);
+ }
+ }
+
+ return WERR_OK;
+}
+
+WERROR dns_common_replace(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn,
+ bool needs_add,
+ uint32_t serial,
+ struct dnsp_DnssrvRpcRecord *records,
+ uint16_t rec_count)
+{
+ const struct timeval start = timeval_current();
+ struct ldb_message_element *el;
+ uint16_t i;
+ int ret;
+ WERROR werr;
+ struct ldb_message *msg = NULL;
+ bool was_tombstoned = false;
+ bool become_tombstoned = false;
+ struct ldb_dn *zone_dn = NULL;
+ struct dnsserver_zoneinfo *zoneinfo = NULL;
+ uint32_t t;
+
+ msg = ldb_msg_new(mem_ctx);
+ W_ERROR_HAVE_NO_MEMORY(msg);
+
+ msg->dn = dn;
+
+ zone_dn = ldb_dn_copy(mem_ctx, dn);
+ if (zone_dn == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto exit;
+ }
+ if (!ldb_dn_remove_child_components(zone_dn, 1)) {
+ werr = DNS_ERR(SERVER_FAILURE);
+ goto exit;
+ }
+ zoneinfo = talloc(mem_ctx, struct dnsserver_zoneinfo);
+ if (zoneinfo == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto exit;
+ }
+ werr = dns_get_zone_properties(samdb, mem_ctx, zone_dn, zoneinfo);
+ if (W_ERROR_EQUAL(DNS_ERR(NOTZONE), werr)) {
+ /*
+ * We only got zoneinfo for aging so if we didn't find any
+ * properties then just disable aging and keep going.
+ */
+ zoneinfo->fAging = 0;
+ } else if (!W_ERROR_IS_OK(werr)) {
+ goto exit;
+ }
+
+ werr = check_name_list(mem_ctx, rec_count, records);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto exit;
+ }
+
+ ret = ldb_msg_add_empty(msg, "dnsRecord", LDB_FLAG_MOD_REPLACE, &el);
+ if (ret != LDB_SUCCESS) {
+ werr = DNS_ERR(SERVER_FAILURE);
+ goto exit;
+ }
+
+ /*
+ * we have at least one value,
+ * which might be used for the tombstone marker
+ */
+ el->values = talloc_zero_array(el, struct ldb_val, MAX(1, rec_count));
+ if (el->values == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto exit;
+ }
+
+ if (rec_count > 1) {
+ /*
+ * We store a sorted list with the high wType values first
+ * that's what windows does. It also simplifies the
+ * filtering of DNS_TYPE_TOMBSTONE records
+ */
+ TYPESAFE_QSORT(records, rec_count, rec_cmp);
+ }
+
+ for (i = 0; i < rec_count; i++) {
+ struct ldb_val *v = &el->values[el->num_values];
+ enum ndr_err_code ndr_err;
+
+ if (records[i].wType == DNS_TYPE_TOMBSTONE) {
+ /*
+ * There are two things that could be going on here.
+ *
+ * 1. We use a tombstone with EntombedTime == 0 for
+ * passing deletion messages through the stack, and
+ * this is the place we filter them out to perform
+ * that deletion.
+ *
+ * 2. This node is tombstoned, with no records except
+ * for a single tombstone, and it is just waiting to
+ * disappear. In this case, unless the caller has
+ * added a record, rec_count should be 1, and
+ * el->num_values will end up at 0, and we will make
+ * no changes. But if the caller has added a record,
+ * we need to un-tombstone the node.
+ *
+ * It is not possible to add an explicit tombstone
+ * record.
+ */
+ if (records[i].data.EntombedTime != 0) {
+ was_tombstoned = true;
+ }
+ continue;
+ }
+
+ if (zoneinfo->fAging == 1 && records[i].dwTimeStamp != 0) {
+ t = unix_to_dns_timestamp(time(NULL));
+ if (t - records[i].dwTimeStamp >
+ zoneinfo->dwNoRefreshInterval) {
+ records[i].dwTimeStamp = t;
+ }
+ }
+
+ records[i].dwSerial = serial;
+ ndr_err = ndr_push_struct_blob(v, el->values, &records[i],
+ (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0, ("Failed to push dnsp_DnssrvRpcRecord\n"));
+ werr = DNS_ERR(SERVER_FAILURE);
+ goto exit;
+ }
+ el->num_values++;
+ }
+
+ if (needs_add) {
+ /*
+ * This is a new dnsNode, which simplifies everything as we
+ * know there is nothing to delete or change. We add the
+ * records and get out.
+ */
+ if (el->num_values == 0) {
+ werr = WERR_OK;
+ goto exit;
+ }
+
+ ret = ldb_msg_add_string(msg, "objectClass", "dnsNode");
+ if (ret != LDB_SUCCESS) {
+ werr = DNS_ERR(SERVER_FAILURE);
+ goto exit;
+ }
+
+ ret = ldb_add(samdb, msg);
+ if (ret != LDB_SUCCESS) {
+ werr = DNS_ERR(SERVER_FAILURE);
+ goto exit;
+ }
+
+ werr = WERR_OK;
+ goto exit;
+ }
+
+ if (el->num_values == 0) {
+ /*
+ * We get here if there are no records or all the records were
+ * tombstones.
+ */
+ struct dnsp_DnssrvRpcRecord tbs;
+ struct ldb_val *v = &el->values[el->num_values];
+ enum ndr_err_code ndr_err;
+ struct timeval tv;
+
+ if (was_tombstoned) {
+ /*
+ * This is already a tombstoned object.
+ * Just leave it instead of updating the time stamp.
+ */
+ werr = WERR_OK;
+ goto exit;
+ }
+
+ tv = timeval_current();
+ tbs = (struct dnsp_DnssrvRpcRecord) {
+ .wType = DNS_TYPE_TOMBSTONE,
+ .dwSerial = serial,
+ .data.EntombedTime = timeval_to_nttime(&tv),
+ };
+
+ ndr_err = ndr_push_struct_blob(v, el->values, &tbs,
+ (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0, ("Failed to push dnsp_DnssrvRpcRecord\n"));
+ werr = DNS_ERR(SERVER_FAILURE);
+ goto exit;
+ }
+ el->num_values++;
+
+ become_tombstoned = true;
+ }
+
+ if (was_tombstoned || become_tombstoned) {
+ ret = ldb_msg_append_fmt(msg, LDB_FLAG_MOD_REPLACE,
+ "dNSTombstoned", "%s",
+ become_tombstoned ? "TRUE" : "FALSE");
+ if (ret != LDB_SUCCESS) {
+ werr = DNS_ERR(SERVER_FAILURE);
+ goto exit;
+ }
+ }
+
+ ret = ldb_modify(samdb, msg);
+ if (ret != LDB_SUCCESS) {
+ NTSTATUS nt = dsdb_ldb_err_to_ntstatus(ret);
+ werr = ntstatus_to_werror(nt);
+ goto exit;
+ }
+
+ werr = WERR_OK;
+exit:
+ talloc_free(msg);
+ DNS_COMMON_LOG_OPERATION(
+ win_errstr(werr),
+ &start,
+ NULL,
+ dn == NULL ? NULL : ldb_dn_get_linearized(dn),
+ NULL);
+ return werr;
+}
+
+bool dns_name_match(const char *zone, const char *name, size_t *host_part_len)
+{
+ size_t zl = strlen(zone);
+ size_t nl = strlen(name);
+ ssize_t zi, ni;
+ static const size_t fixup = 'a' - 'A';
+
+ if (zl > nl) {
+ return false;
+ }
+
+ for (zi = zl, ni = nl; zi >= 0; zi--, ni--) {
+ char zc = zone[zi];
+ char nc = name[ni];
+
+ /* convert to lower case */
+ if (zc >= 'A' && zc <= 'Z') {
+ zc += fixup;
+ }
+ if (nc >= 'A' && nc <= 'Z') {
+ nc += fixup;
+ }
+
+ if (zc != nc) {
+ return false;
+ }
+ }
+
+ if (ni >= 0) {
+ if (name[ni] != '.') {
+ return false;
+ }
+
+ ni--;
+ }
+
+ *host_part_len = ni+1;
+
+ return true;
+}
+
+WERROR dns_common_name2dn(struct ldb_context *samdb,
+ struct dns_server_zone *zones,
+ TALLOC_CTX *mem_ctx,
+ const char *name,
+ struct ldb_dn **_dn)
+{
+ struct ldb_dn *base;
+ struct ldb_dn *dn;
+ const struct dns_server_zone *z;
+ size_t host_part_len = 0;
+ struct ldb_val host_part;
+ WERROR werr;
+ bool ok;
+ const char *casefold = NULL;
+
+ if (name == NULL) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+
+ if (strcmp(name, "") == 0) {
+ base = ldb_get_default_basedn(samdb);
+ dn = ldb_dn_copy(mem_ctx, base);
+ ok = ldb_dn_add_child_fmt(dn,
+ "DC=@,DC=RootDNSServers,CN=MicrosoftDNS,CN=System");
+ if (ok == false) {
+ TALLOC_FREE(dn);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ *_dn = dn;
+ return WERR_OK;
+ }
+
+ /* Check non-empty names */
+ werr = dns_name_check(mem_ctx, strlen(name), name);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ for (z = zones; z != NULL; z = z->next) {
+ bool match;
+
+ match = dns_name_match(z->name, name, &host_part_len);
+ if (match) {
+ break;
+ }
+ }
+
+ if (z == NULL) {
+ return DNS_ERR(NAME_ERROR);
+ }
+
+ if (host_part_len == 0) {
+ dn = ldb_dn_copy(mem_ctx, z->dn);
+ ok = ldb_dn_add_child_fmt(dn, "DC=@");
+ if (! ok) {
+ TALLOC_FREE(dn);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ *_dn = dn;
+ return WERR_OK;
+ }
+
+ dn = ldb_dn_copy(mem_ctx, z->dn);
+ if (dn == NULL) {
+ TALLOC_FREE(dn);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ host_part = data_blob_const(name, host_part_len);
+
+ ok = ldb_dn_add_child_val(dn, "DC", host_part);
+
+ if (ok == false) {
+ TALLOC_FREE(dn);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /*
+ * Check the new DN here for validity, so as to catch errors
+ * early
+ */
+ ok = ldb_dn_validate(dn);
+ if (ok == false) {
+ TALLOC_FREE(dn);
+ return DNS_ERR(NAME_ERROR);
+ }
+
+ /*
+ * The value from this check is saved in the DN, and doing
+ * this here allows an easy return here.
+ */
+ casefold = ldb_dn_get_casefold(dn);
+ if (casefold == NULL) {
+ TALLOC_FREE(dn);
+ return DNS_ERR(NAME_ERROR);
+ }
+
+ *_dn = dn;
+ return WERR_OK;
+}
+
+
+/*
+ see if two dns records match
+ */
+
+
+bool dns_record_match(struct dnsp_DnssrvRpcRecord *rec1,
+ struct dnsp_DnssrvRpcRecord *rec2)
+{
+ int i;
+ struct in6_addr rec1_in_addr6;
+ struct in6_addr rec2_in_addr6;
+
+ if (rec1->wType != rec2->wType) {
+ return false;
+ }
+
+ /* see if the data matches */
+ switch (rec1->wType) {
+ case DNS_TYPE_A:
+ return strcmp(rec1->data.ipv4, rec2->data.ipv4) == 0;
+ case DNS_TYPE_AAAA: {
+ int ret;
+
+ ret = inet_pton(AF_INET6, rec1->data.ipv6, &rec1_in_addr6);
+ if (ret != 1) {
+ return false;
+ }
+ ret = inet_pton(AF_INET6, rec2->data.ipv6, &rec2_in_addr6);
+ if (ret != 1) {
+ return false;
+ }
+
+ return memcmp(&rec1_in_addr6, &rec2_in_addr6, sizeof(rec1_in_addr6)) == 0;
+ }
+ case DNS_TYPE_CNAME:
+ return samba_dns_name_equal(rec1->data.cname,
+ rec2->data.cname);
+ case DNS_TYPE_TXT:
+ if (rec1->data.txt.count != rec2->data.txt.count) {
+ return false;
+ }
+ for (i = 0; i < rec1->data.txt.count; i++) {
+ if (strcmp(rec1->data.txt.str[i], rec2->data.txt.str[i]) != 0) {
+ return false;
+ }
+ }
+ return true;
+ case DNS_TYPE_PTR:
+ return samba_dns_name_equal(rec1->data.ptr, rec2->data.ptr);
+ case DNS_TYPE_NS:
+ return samba_dns_name_equal(rec1->data.ns, rec2->data.ns);
+
+ case DNS_TYPE_SRV:
+ return rec1->data.srv.wPriority == rec2->data.srv.wPriority &&
+ rec1->data.srv.wWeight == rec2->data.srv.wWeight &&
+ rec1->data.srv.wPort == rec2->data.srv.wPort &&
+ samba_dns_name_equal(rec1->data.srv.nameTarget,
+ rec2->data.srv.nameTarget);
+
+ case DNS_TYPE_MX:
+ return rec1->data.mx.wPriority == rec2->data.mx.wPriority &&
+ samba_dns_name_equal(rec1->data.mx.nameTarget,
+ rec2->data.mx.nameTarget);
+
+ case DNS_TYPE_SOA:
+ return samba_dns_name_equal(rec1->data.soa.mname,
+ rec2->data.soa.mname) &&
+ samba_dns_name_equal(rec1->data.soa.rname,
+ rec2->data.soa.rname) &&
+ rec1->data.soa.serial == rec2->data.soa.serial &&
+ rec1->data.soa.refresh == rec2->data.soa.refresh &&
+ rec1->data.soa.retry == rec2->data.soa.retry &&
+ rec1->data.soa.expire == rec2->data.soa.expire &&
+ rec1->data.soa.minimum == rec2->data.soa.minimum;
+ case DNS_TYPE_TOMBSTONE:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+
+static int dns_common_sort_zones(struct ldb_message **m1, struct ldb_message **m2)
+{
+ const char *n1, *n2;
+ size_t l1, l2;
+
+ n1 = ldb_msg_find_attr_as_string(*m1, "name", NULL);
+ n2 = ldb_msg_find_attr_as_string(*m2, "name", NULL);
+ if (n1 == NULL || n2 == NULL) {
+ if (n1 != NULL) {
+ return -1;
+ } else if (n2 != NULL) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ l1 = strlen(n1);
+ l2 = strlen(n2);
+
+ /* If the string lengths are not equal just sort by length */
+ if (l1 != l2) {
+ /* If m1 is the larger zone name, return it first */
+ return l2 - l1;
+ }
+
+ /*TODO: We need to compare DNs here, we want the DomainDNSZones first */
+ return 0;
+}
+
+NTSTATUS dns_common_zones(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *base_dn,
+ struct dns_server_zone **zones_ret)
+{
+ const struct timeval start = timeval_current();
+ int ret;
+ static const char * const attrs[] = { "name", NULL};
+ struct ldb_result *res;
+ int i;
+ struct dns_server_zone *new_list = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS result = NT_STATUS_OK;
+
+ if (base_dn) {
+ /* This search will work against windows */
+ ret = dsdb_search(samdb, frame, &res,
+ base_dn, LDB_SCOPE_SUBTREE,
+ attrs, 0, "(objectClass=dnsZone)");
+ } else {
+ /* TODO: this search does not work against windows */
+ ret = dsdb_search(samdb, frame, &res, NULL,
+ LDB_SCOPE_SUBTREE,
+ attrs,
+ DSDB_SEARCH_SEARCH_ALL_PARTITIONS,
+ "(objectClass=dnsZone)");
+ }
+ if (ret != LDB_SUCCESS) {
+ TALLOC_FREE(frame);
+ result = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto exit;
+ }
+
+ TYPESAFE_QSORT(res->msgs, res->count, dns_common_sort_zones);
+
+ for (i=0; i < res->count; i++) {
+ struct dns_server_zone *z;
+
+ z = talloc_zero(mem_ctx, struct dns_server_zone);
+ if (z == NULL) {
+ TALLOC_FREE(frame);
+ result = NT_STATUS_NO_MEMORY;
+ goto exit;
+ }
+
+ z->name = ldb_msg_find_attr_as_string(res->msgs[i], "name", NULL);
+ talloc_steal(z, z->name);
+ z->dn = talloc_move(z, &res->msgs[i]->dn);
+ /*
+ * Ignore the RootDNSServers zone and zones that we don't support yet
+ * RootDNSServers should never be returned (Windows DNS server don't)
+ * ..TrustAnchors should never be returned as is, (Windows returns
+ * TrustAnchors) and for the moment we don't support DNSSEC so we'd better
+ * not return this zone.
+ */
+ if ((strcmp(z->name, "RootDNSServers") == 0) ||
+ (strcmp(z->name, "..TrustAnchors") == 0))
+ {
+ DEBUG(10, ("Ignoring zone %s\n", z->name));
+ talloc_free(z);
+ continue;
+ }
+ DLIST_ADD_END(new_list, z);
+ }
+
+ *zones_ret = new_list;
+ TALLOC_FREE(frame);
+ result = NT_STATUS_OK;
+exit:
+ DNS_COMMON_LOG_OPERATION(
+ nt_errstr(result),
+ &start,
+ NULL,
+ base_dn == NULL ? NULL : ldb_dn_get_linearized(base_dn),
+ NULL);
+ return result;
+}
+
+/*
+ see if two DNS names are the same
+ */
+bool samba_dns_name_equal(const char *name1, const char *name2)
+{
+ size_t len1 = strlen(name1);
+ size_t len2 = strlen(name2);
+
+ if (len1 > 0 && name1[len1 - 1] == '.') {
+ len1--;
+ }
+ if (len2 > 0 && name2[len2 - 1] == '.') {
+ len2--;
+ }
+ if (len1 != len2) {
+ return false;
+ }
+ return strncasecmp(name1, name2, len1) == 0;
+}
+
+
+/*
+ * Convert unix time to a DNS timestamp
+ * uint32 hours in the NTTIME epoch
+ *
+ * This uses unix_to_nt_time() which can return special flag NTTIMEs like
+ * UINT64_MAX (0xFFF...) or NTTIME_MAX (0x7FF...), which will convert to
+ * distant future timestamps; or 0 as a flag value, meaning a 1601 timestamp,
+ * which is used to indicate a record does not expire.
+ *
+ * As we don't generally check for these special values in NTTIME conversions,
+ * we also don't check here, but for the benefit of people encountering these
+ * timestamps and searching for their origin, here is a list:
+ *
+ ** TIME_T_MAX
+ *
+ * Even if time_t is 32 bit, this will become NTTIME_MAX (a.k.a INT64_MAX,
+ * 0x7fffffffffffffff) in 100ns units. That translates to 256204778 hours
+ * since 1601, which converts back to 9223372008000000000 or
+ * 0x7ffffff9481f1000. It will present as 30828-09-14 02:00:00, around 48
+ * minutes earlier than NTTIME_MAX.
+ *
+ ** 0, the start of the unix epoch, 1970-01-01 00:00:00
+ *
+ * This is converted into 0 in the Windows epoch, 1601-01-01 00:00:00 which is
+ * clearly the same whether you count in 100ns units or hours. In DNS record
+ * timestamps this is a flag meaning the record will never expire.
+ *
+ ** (time_t)-1, such as what *might* mean 1969-12-31 23:59:59
+ *
+ * This becomes (NTTIME)-1ULL a.k.a. UINT64_MAX, 0xffffffffffffffff thence
+ * 512409557 in hours since 1601. That in turn is 0xfffffffaf2028800 or
+ * 18446744052000000000 in NTTIME (rounded to the hour), which might be
+ * presented as -21709551616 or -0x50dfd7800, because NTITME is not completely
+ * dedicated to being unsigned. If it gets shown as a year, it will be around
+ * 60055.
+ *
+ ** Other negative time_t values (e.g. 1969-05-29).
+ *
+ * The meaning of these is somewhat undefined, but in any case they will
+ * translate perfectly well into the same dates in NTTIME.
+ *
+ ** Notes
+ *
+ * There are dns timestamps that exceed the range of NTTIME (up to 488356 AD),
+ * but it is not possible for this function to produce them.
+ *
+ * It is plausible that it was at midnight on 1601-01-01, in London, that
+ * Shakespeare wrote:
+ *
+ * The time is out of joint. O cursed spite
+ * That ever I was born to set it right!
+ *
+ * and this is why we have this epoch and time zone.
+ */
+uint32_t unix_to_dns_timestamp(time_t t)
+{
+ NTTIME nt;
+ unix_to_nt_time(&nt, t);
+ nt /= NTTIME_TO_HOURS;
+ return (uint32_t) nt;
+}
+
+/*
+ * Convert a DNS timestamp into NTTIME.
+ *
+ * Because DNS timestamps cover a longer time period than NTTIME, and these
+ * would wrap to an arbitrary NTTIME, we saturate at NTTIME_MAX and return an
+ * error in this case.
+ */
+NTSTATUS dns_timestamp_to_nt_time(NTTIME *_nt, uint32_t t)
+{
+ NTTIME nt = t;
+ if (nt > NTTIME_MAX / NTTIME_TO_HOURS) {
+ *_nt = NTTIME_MAX;
+ return NT_STATUS_INTEGER_OVERFLOW;
+ }
+ *_nt = nt * NTTIME_TO_HOURS;
+ return NT_STATUS_OK;
+}