summaryrefslogtreecommitdiff
path: root/source4/dns_server/dnsserver_common.c
diff options
context:
space:
mode:
authorGary Lockyer <gary@catalyst.net.nz>2017-08-03 15:12:02 +1200
committerGarming Sam <garming@samba.org>2017-08-15 08:07:10 +0200
commitedcbc991253f4d6f59ef9a43a691c66cbbdc2b6d (patch)
treea3641f0fa6cc0aa2183b1cff9c0da49a6a0e355d /source4/dns_server/dnsserver_common.c
parent34acf5a99214639e5e7792a9e85d24c9fd7640ac (diff)
downloadsamba-edcbc991253f4d6f59ef9a43a691c66cbbdc2b6d.tar.gz
dnsserver: Add support for dns wildcards
Add support for dns wildcard records. i.e. if the following records exist exact.samba.example.com 3600 A 1.1.1.1 *.samba.example.com 3600 A 1.1.1.2 look up on exact.samba.example.com will return 1.1.1.1 look up on *.samba.example.com will return 1.1.1.2 look up on other.samba.example.com will return 1.1.1.2 Signed-off-by: Gary Lockyer <gary@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abartlet@samba.org> Reviewed-by: Garming Sam <garming@catalyst.net.nz> BUG: https://bugzilla.samba.org/show_bug.cgi?id=12952
Diffstat (limited to 'source4/dns_server/dnsserver_common.c')
-rw-r--r--source4/dns_server/dnsserver_common.c348
1 files changed, 348 insertions, 0 deletions
diff --git a/source4/dns_server/dnsserver_common.c b/source4/dns_server/dnsserver_common.c
index 2a81b836722..217e65b39f4 100644
--- a/source4/dns_server/dnsserver_common.c
+++ b/source4/dns_server/dnsserver_common.c
@@ -133,6 +133,10 @@ WERROR dns_common_extract(struct ldb_context *samdb,
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,
@@ -229,6 +233,350 @@ WERROR dns_common_lookup(struct ldb_context *samdb,
return WERR_OK;
}
+/*
+ * 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 (el == NULL) {
+ DBG_ERR("Unable to allocate value->data\n");
+ TALLOC_FREE(el);
+ return NULL;
+ }
+
+ value->length = length;
+ if (add_asterix) {
+ value->data[0] = '*';
+ memcpy(&value->data[1], name, size);
+ } else {
+ 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))
+ *
+ * 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 = NULL; /* 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 */
+
+ attr = ldb_dn_get_rdn_name(dn);
+ if (attr == NULL) {
+ DBG_ERR("Unable to get rdn_name\n");
+ return NULL;
+ }
+
+ 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 */
+ 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_ONELEVEL,
+ 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)
+{
+ int ret;
+ WERROR werr;
+ 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) {
+ return DNS_ERR(NAME_ERROR);
+ }
+
+ werr = dns_name_check(
+ mem_ctx,
+ strlen((const char*)name->data),
+ (const char*) name->data);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ ret = dns_wildcard_lookup(samdb, mem_ctx, dn, &msg);
+ if (ret == LDB_ERR_OPERATIONS_ERROR) {
+ return DNS_ERR(SERVER_FAILURE);
+ }
+ if (ret != LDB_SUCCESS) {
+ return DNS_ERR(NAME_ERROR);
+ }
+
+ el = ldb_msg_find_element(msg, "dnsRecord");
+ if (el == NULL) {
+ return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST;
+ }
+
+ werr = dns_common_extract(samdb, el, mem_ctx, records, num_records);
+ TALLOC_FREE(msg);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ return WERR_OK;
+}
+
static int rec_cmp(const struct dnsp_DnssrvRpcRecord *r1,
const struct dnsp_DnssrvRpcRecord *r2)
{