diff options
author | Gary Lockyer <gary@catalyst.net.nz> | 2017-08-03 15:12:02 +1200 |
---|---|---|
committer | Garming Sam <garming@samba.org> | 2017-08-15 08:07:10 +0200 |
commit | edcbc991253f4d6f59ef9a43a691c66cbbdc2b6d (patch) | |
tree | a3641f0fa6cc0aa2183b1cff9c0da49a6a0e355d /source4/dns_server/dnsserver_common.c | |
parent | 34acf5a99214639e5e7792a9e85d24c9fd7640ac (diff) | |
download | samba-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.c | 348 |
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) { |