1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-23 17:34:34 +03:00
samba-mirror/source4/dns_server/dnsserver_common.c
Douglas Bagnall ffdd9ddeae s4:dns_server: loudly warn when a tombstone record has other records
This shouldn't happen -- that is, there should never be non-tombstone
records in conjunction with a tombstone record -- and if it does, the
situation should resolve itself here. But the flow is confusing and
strange things sometimes happen often enough that it would be helpful
to know if this ever occurs.

Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
2023-10-26 01:24:32 +00:00

1595 lines
40 KiB
C

/*
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\n"));
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 Microsoft 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) {
if (rec_count != 1) {
DBG_ERR("tombstone record has %u neighbour "
"records.\n",
rec_count - 1);
}
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 NTTIME 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;
}