summaryrefslogtreecommitdiffstats
path: root/source4/dns_server
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source4/dns_server
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source4/dns_server')
-rw-r--r--source4/dns_server/TODO12
-rw-r--r--source4/dns_server/dlz_bind9.c2219
-rw-r--r--source4/dns_server/dlz_minimal.h317
-rw-r--r--source4/dns_server/dns_crypto.c448
-rw-r--r--source4/dns_server/dns_query.c1176
-rw-r--r--source4/dns_server/dns_server.c965
-rw-r--r--source4/dns_server/dns_server.h124
-rw-r--r--source4/dns_server/dns_update.c879
-rw-r--r--source4/dns_server/dns_utils.c130
-rw-r--r--source4/dns_server/dnsserver_common.c1594
-rw-r--r--source4/dns_server/dnsserver_common.h139
-rw-r--r--source4/dns_server/pydns.c445
-rw-r--r--source4/dns_server/wscript_build98
13 files changed, 8546 insertions, 0 deletions
diff --git a/source4/dns_server/TODO b/source4/dns_server/TODO
new file mode 100644
index 0000000..1949650
--- /dev/null
+++ b/source4/dns_server/TODO
@@ -0,0 +1,12 @@
+DNS server todo list
+--------------------
+
+Just so we don't forget the required features for an AD-compatible DNS server:
+
+- Symmetric Bind-style TKEY handling (not strictly needed for AD, but needed for
+ integration to other name servers / tools)
+(- Command line tools that unix admins are used to)
+- Zone transfer support (XFER, IFER) (look at AD for permission settings)
+- Caching
+- dynamic zone reloading
+- Tests, tests, tests
diff --git a/source4/dns_server/dlz_bind9.c b/source4/dns_server/dlz_bind9.c
new file mode 100644
index 0000000..409e2f3
--- /dev/null
+++ b/source4/dns_server/dlz_bind9.c
@@ -0,0 +1,2219 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ bind9 dlz driver for Samba
+
+ Copyright (C) 2010 Andrew Tridgell
+
+ 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 "talloc.h"
+#include "param/param.h"
+#include "lib/events/events.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
+#include "auth/auth.h"
+#include "auth/session.h"
+#include "auth/gensec/gensec.h"
+#include "librpc/gen_ndr/security.h"
+#include "auth/credentials/credentials.h"
+#include "system/kerberos.h"
+#include "auth/kerberos/kerberos.h"
+#include "gen_ndr/ndr_dnsp.h"
+#include "gen_ndr/server_id.h"
+#include "messaging/messaging.h"
+#include <popt.h>
+#include "lib/util/dlinklist.h"
+#include "dlz_minimal.h"
+#include "dnsserver_common.h"
+#include "lib/util/smb_strtox.h"
+#include "lib/util/access.h"
+
+#undef strcasecmp
+
+struct b9_options {
+ const char *url;
+ const char *debug;
+};
+
+struct b9_zone {
+ char *name;
+ struct b9_zone *prev, *next;
+};
+
+struct dlz_bind9_data {
+ struct b9_options options;
+ struct ldb_context *samdb;
+ struct tevent_context *ev_ctx;
+ struct loadparm_context *lp;
+ int *transaction_token;
+ uint32_t soa_serial;
+ struct b9_zone *zonelist;
+
+ /* Used for dynamic update */
+ struct smb_krb5_context *smb_krb5_ctx;
+ struct auth4_context *auth_context;
+ struct auth_session_info *session_info;
+ char *update_name;
+
+ /* helper functions from the dlz_dlopen driver */
+ log_t *log;
+ dns_sdlz_putrr_t *putrr;
+ dns_sdlz_putnamedrr_t *putnamedrr;
+ dns_dlz_writeablezone_t *writeable_zone;
+};
+
+static struct dlz_bind9_data *dlz_bind9_state = NULL;
+static int dlz_bind9_state_ref_count = 0;
+
+static const char *zone_prefixes[] = {
+ "CN=MicrosoftDNS,DC=DomainDnsZones",
+ "CN=MicrosoftDNS,DC=ForestDnsZones",
+ "CN=MicrosoftDNS,CN=System",
+ NULL
+};
+
+/*
+ * Get a printable string representation of an isc_result_t
+ */
+static const char *isc_result_str( const isc_result_t result) {
+ switch (result) {
+ case ISC_R_SUCCESS:
+ return "ISC_R_SUCCESS";
+ case ISC_R_NOMEMORY:
+ return "ISC_R_NOMEMORY";
+ case ISC_R_NOPERM:
+ return "ISC_R_NOPERM";
+ case ISC_R_NOSPACE:
+ return "ISC_R_NOSPACE";
+ case ISC_R_NOTFOUND:
+ return "ISC_R_NOTFOUND";
+ case ISC_R_FAILURE:
+ return "ISC_R_FAILURE";
+ case ISC_R_NOTIMPLEMENTED:
+ return "ISC_R_NOTIMPLEMENTED";
+ case ISC_R_NOMORE:
+ return "ISC_R_NOMORE";
+ case ISC_R_INVALIDFILE:
+ return "ISC_R_INVALIDFILE";
+ case ISC_R_UNEXPECTED:
+ return "ISC_R_UNEXPECTED";
+ case ISC_R_FILENOTFOUND:
+ return "ISC_R_FILENOTFOUND";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+/*
+ return the version of the API
+ */
+_PUBLIC_ int dlz_version(unsigned int *flags)
+{
+ return DLZ_DLOPEN_VERSION;
+}
+
+/*
+ remember a helper function from the bind9 dlz_dlopen driver
+ */
+static void b9_add_helper(struct dlz_bind9_data *state, const char *helper_name, void *ptr)
+{
+ if (strcmp(helper_name, "log") == 0) {
+ state->log = ptr;
+ }
+ if (strcmp(helper_name, "putrr") == 0) {
+ state->putrr = ptr;
+ }
+ if (strcmp(helper_name, "putnamedrr") == 0) {
+ state->putnamedrr = ptr;
+ }
+ if (strcmp(helper_name, "writeable_zone") == 0) {
+ state->writeable_zone = ptr;
+ }
+}
+
+/*
+ * Add a trailing '.' if it's missing
+ */
+static const char *b9_format_fqdn(TALLOC_CTX *mem_ctx, const char *str)
+{
+ size_t len;
+ const char *tmp;
+
+ if (str == NULL || str[0] == '\0') {
+ return str;
+ }
+
+ len = strlen(str);
+ if (str[len-1] != '.') {
+ tmp = talloc_asprintf(mem_ctx, "%s.", str);
+ } else {
+ tmp = str;
+ }
+ return tmp;
+}
+
+/*
+ * Format a record for bind9.
+ *
+ * On failure/error returns false, OR sets *data to NULL.
+ * Callers should check for both!
+ */
+static bool b9_format(struct dlz_bind9_data *state,
+ TALLOC_CTX *mem_ctx,
+ struct dnsp_DnssrvRpcRecord *rec,
+ const char **type, const char **data)
+{
+ uint32_t i;
+ char *tmp;
+ const char *fqdn;
+
+ switch (rec->wType) {
+ case DNS_TYPE_A:
+ *type = "a";
+ *data = rec->data.ipv4;
+ break;
+
+ case DNS_TYPE_AAAA:
+ *type = "aaaa";
+ *data = rec->data.ipv6;
+ break;
+
+ case DNS_TYPE_CNAME:
+ *type = "cname";
+ *data = b9_format_fqdn(mem_ctx, rec->data.cname);
+ break;
+
+ case DNS_TYPE_TXT:
+ *type = "txt";
+ tmp = talloc_asprintf(mem_ctx, "\"%s\"", rec->data.txt.str[0]);
+ for (i=1; i<rec->data.txt.count; i++) {
+ talloc_asprintf_addbuf(&tmp, " \"%s\"", rec->data.txt.str[i]);
+ }
+ *data = tmp;
+ break;
+
+ case DNS_TYPE_PTR:
+ *type = "ptr";
+ *data = b9_format_fqdn(mem_ctx, rec->data.ptr);
+ break;
+
+ case DNS_TYPE_SRV:
+ *type = "srv";
+ fqdn = b9_format_fqdn(mem_ctx, rec->data.srv.nameTarget);
+ if (fqdn == NULL) {
+ return false;
+ }
+ *data = talloc_asprintf(mem_ctx, "%u %u %u %s",
+ rec->data.srv.wPriority,
+ rec->data.srv.wWeight,
+ rec->data.srv.wPort,
+ fqdn);
+ break;
+
+ case DNS_TYPE_MX:
+ *type = "mx";
+ fqdn = b9_format_fqdn(mem_ctx, rec->data.mx.nameTarget);
+ if (fqdn == NULL) {
+ return false;
+ }
+ *data = talloc_asprintf(mem_ctx, "%u %s",
+ rec->data.mx.wPriority, fqdn);
+ break;
+
+ case DNS_TYPE_NS:
+ *type = "ns";
+ *data = b9_format_fqdn(mem_ctx, rec->data.ns);
+ break;
+
+ case DNS_TYPE_SOA: {
+ const char *mname;
+ *type = "soa";
+
+ /* we need to fake the authoritative nameserver to
+ * point at ourselves. This is how AD DNS servers
+ * force clients to send updates to the right local DC
+ */
+ mname = talloc_asprintf(mem_ctx, "%s.%s.",
+ lpcfg_netbios_name(state->lp),
+ lpcfg_dnsdomain(state->lp));
+ if (mname == NULL) {
+ return false;
+ }
+ mname = strlower_talloc(mem_ctx, mname);
+ if (mname == NULL) {
+ return false;
+ }
+
+ fqdn = b9_format_fqdn(mem_ctx, rec->data.soa.rname);
+ if (fqdn == NULL) {
+ return false;
+ }
+
+ state->soa_serial = rec->data.soa.serial;
+
+ *data = talloc_asprintf(mem_ctx, "%s %s %u %u %u %u %u",
+ mname, fqdn,
+ rec->data.soa.serial,
+ rec->data.soa.refresh,
+ rec->data.soa.retry,
+ rec->data.soa.expire,
+ rec->data.soa.minimum);
+ break;
+ }
+
+ default:
+ state->log(ISC_LOG_ERROR, "samba_dlz b9_format: unhandled record type %u",
+ rec->wType);
+ return false;
+ }
+
+ return true;
+}
+
+static const struct {
+ enum dns_record_type dns_type;
+ const char *typestr;
+ bool single_valued;
+} dns_typemap[] = {
+ { DNS_TYPE_A, "A" , false},
+ { DNS_TYPE_AAAA, "AAAA" , false},
+ { DNS_TYPE_CNAME, "CNAME" , true},
+ { DNS_TYPE_TXT, "TXT" , false},
+ { DNS_TYPE_PTR, "PTR" , false},
+ { DNS_TYPE_SRV, "SRV" , false},
+ { DNS_TYPE_MX, "MX" , false},
+ { DNS_TYPE_NS, "NS" , false},
+ { DNS_TYPE_SOA, "SOA" , true},
+};
+
+
+/*
+ see if a DNS type is single valued
+ */
+static bool b9_single_valued(enum dns_record_type dns_type)
+{
+ int i;
+ for (i=0; i<ARRAY_SIZE(dns_typemap); i++) {
+ if (dns_typemap[i].dns_type == dns_type) {
+ return dns_typemap[i].single_valued;
+ }
+ }
+ return false;
+}
+
+/*
+ get a DNS_TYPE_* value from the corresponding string
+ */
+static bool b9_dns_type(const char *type, enum dns_record_type *dtype)
+{
+ int i;
+ for (i=0; i<ARRAY_SIZE(dns_typemap); i++) {
+ if (strcasecmp(dns_typemap[i].typestr, type) == 0) {
+ *dtype = dns_typemap[i].dns_type;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+#define DNS_PARSE_STR(ret, str, sep, saveptr) do { \
+ (ret) = strtok_r(str, sep, &saveptr); \
+ if ((ret) == NULL) return false; \
+ } while (0)
+
+#define DNS_PARSE_UINT(ret, str, sep, saveptr) do { \
+ char *istr = strtok_r(str, sep, &saveptr); \
+ int error = 0;\
+ if ((istr) == NULL) return false; \
+ (ret) = smb_strtoul(istr, NULL, 10, &error, SMB_STR_STANDARD); \
+ if (error != 0) {\
+ return false;\
+ }\
+ } while (0)
+
+/*
+ parse a record from bind9
+ */
+static bool b9_parse(struct dlz_bind9_data *state,
+ const char *rdatastr,
+ struct dnsp_DnssrvRpcRecord *rec)
+{
+ char *full_name, *dclass, *type;
+ char *str, *tmp, *saveptr=NULL;
+ int i;
+
+ str = talloc_strdup(rec, rdatastr);
+ if (str == NULL) {
+ return false;
+ }
+
+ /* parse the SDLZ string form */
+ DNS_PARSE_STR(full_name, str, "\t", saveptr);
+ DNS_PARSE_UINT(rec->dwTtlSeconds, NULL, "\t", saveptr);
+ DNS_PARSE_STR(dclass, NULL, "\t", saveptr);
+ DNS_PARSE_STR(type, NULL, "\t", saveptr);
+
+ /* construct the record */
+ for (i=0; i<ARRAY_SIZE(dns_typemap); i++) {
+ if (strcasecmp(type, dns_typemap[i].typestr) == 0) {
+ rec->wType = dns_typemap[i].dns_type;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(dns_typemap)) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: unsupported record type '%s' for '%s'",
+ type, full_name);
+ return false;
+ }
+
+ switch (rec->wType) {
+ case DNS_TYPE_A:
+ DNS_PARSE_STR(rec->data.ipv4, NULL, " ", saveptr);
+ break;
+
+ case DNS_TYPE_AAAA:
+ DNS_PARSE_STR(rec->data.ipv6, NULL, " ", saveptr);
+ break;
+
+ case DNS_TYPE_CNAME:
+ DNS_PARSE_STR(rec->data.cname, NULL, " ", saveptr);
+ break;
+
+ case DNS_TYPE_TXT:
+ rec->data.txt.count = 0;
+ rec->data.txt.str = talloc_array(rec, const char *, rec->data.txt.count);
+ tmp = strtok_r(NULL, "\t", &saveptr);
+ while (tmp) {
+ rec->data.txt.str = talloc_realloc(rec, rec->data.txt.str, const char *,
+ rec->data.txt.count+1);
+ if (tmp[0] == '"') {
+ /* Strip quotes */
+ rec->data.txt.str[rec->data.txt.count] = talloc_strndup(rec, &tmp[1], strlen(tmp)-2);
+ } else {
+ rec->data.txt.str[rec->data.txt.count] = talloc_strdup(rec, tmp);
+ }
+ rec->data.txt.count++;
+ tmp = strtok_r(NULL, " ", &saveptr);
+ }
+ break;
+
+ case DNS_TYPE_PTR:
+ DNS_PARSE_STR(rec->data.ptr, NULL, " ", saveptr);
+ break;
+
+ case DNS_TYPE_SRV:
+ DNS_PARSE_UINT(rec->data.srv.wPriority, NULL, " ", saveptr);
+ DNS_PARSE_UINT(rec->data.srv.wWeight, NULL, " ", saveptr);
+ DNS_PARSE_UINT(rec->data.srv.wPort, NULL, " ", saveptr);
+ DNS_PARSE_STR(rec->data.srv.nameTarget, NULL, " ", saveptr);
+ break;
+
+ case DNS_TYPE_MX:
+ DNS_PARSE_UINT(rec->data.mx.wPriority, NULL, " ", saveptr);
+ DNS_PARSE_STR(rec->data.mx.nameTarget, NULL, " ", saveptr);
+ break;
+
+ case DNS_TYPE_NS:
+ DNS_PARSE_STR(rec->data.ns, NULL, " ", saveptr);
+ break;
+
+ case DNS_TYPE_SOA:
+ DNS_PARSE_STR(rec->data.soa.mname, NULL, " ", saveptr);
+ DNS_PARSE_STR(rec->data.soa.rname, NULL, " ", saveptr);
+ DNS_PARSE_UINT(rec->data.soa.serial, NULL, " ", saveptr);
+ DNS_PARSE_UINT(rec->data.soa.refresh, NULL, " ", saveptr);
+ DNS_PARSE_UINT(rec->data.soa.retry, NULL, " ", saveptr);
+ DNS_PARSE_UINT(rec->data.soa.expire, NULL, " ", saveptr);
+ DNS_PARSE_UINT(rec->data.soa.minimum, NULL, " ", saveptr);
+ break;
+
+ default:
+ state->log(ISC_LOG_ERROR, "samba_dlz b9_parse: unhandled record type %u",
+ rec->wType);
+ return false;
+ }
+
+ /* we should be at the end of the buffer now */
+ if (strtok_r(NULL, "\t ", &saveptr) != NULL) {
+ state->log(ISC_LOG_ERROR, "samba_dlz b9_parse: unexpected data at end of string for '%s'",
+ rdatastr);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ send a resource record to bind9
+ */
+static isc_result_t b9_putrr(struct dlz_bind9_data *state,
+ void *handle, struct dnsp_DnssrvRpcRecord *rec,
+ const char **types)
+{
+ isc_result_t result;
+ const char *type, *data;
+ TALLOC_CTX *tmp_ctx = talloc_new(state);
+
+ if (!b9_format(state, tmp_ctx, rec, &type, &data)) {
+ return ISC_R_FAILURE;
+ }
+
+ if (data == NULL) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOMEMORY;
+ }
+
+ if (types) {
+ int i;
+ for (i=0; types[i]; i++) {
+ if (strcmp(types[i], type) == 0) break;
+ }
+ if (types[i] == NULL) {
+ /* skip it */
+ return ISC_R_SUCCESS;
+ }
+ }
+
+ result = state->putrr(handle, type, rec->dwTtlSeconds, data);
+ if (result != ISC_R_SUCCESS) {
+ state->log(ISC_LOG_ERROR, "Failed to put rr");
+ }
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+
+/*
+ send a named resource record to bind9
+ */
+static isc_result_t b9_putnamedrr(struct dlz_bind9_data *state,
+ void *handle, const char *name,
+ struct dnsp_DnssrvRpcRecord *rec)
+{
+ isc_result_t result;
+ const char *type, *data;
+ TALLOC_CTX *tmp_ctx = talloc_new(state);
+
+ if (!b9_format(state, tmp_ctx, rec, &type, &data)) {
+ return ISC_R_FAILURE;
+ }
+
+ if (data == NULL) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOMEMORY;
+ }
+
+ result = state->putnamedrr(handle, name, type, rec->dwTtlSeconds, data);
+ if (result != ISC_R_SUCCESS) {
+ state->log(ISC_LOG_ERROR, "Failed to put named rr '%s'", name);
+ }
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+/*
+ parse options
+ */
+static isc_result_t parse_options(struct dlz_bind9_data *state,
+ unsigned int argc, const char **argv,
+ struct b9_options *options)
+{
+ int opt;
+ poptContext pc;
+ struct poptOption long_options[] = {
+ { "url", 'H', POPT_ARG_STRING, &options->url, 0, "database URL", "URL" },
+ { "debug", 'd', POPT_ARG_STRING, &options->debug, 0, "debug level", "DEBUG" },
+ {0}
+ };
+
+ pc = poptGetContext("dlz_bind9", argc, argv, long_options,
+ POPT_CONTEXT_KEEP_FIRST);
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ default:
+ state->log(ISC_LOG_ERROR, "dlz_bind9: Invalid option %s: %s",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptFreeContext(pc);
+ return ISC_R_FAILURE;
+ }
+ }
+
+ poptFreeContext(pc);
+ return ISC_R_SUCCESS;
+}
+
+
+/*
+ * Create session info from PAC
+ * This is called as auth_context->generate_session_info_pac()
+ */
+static NTSTATUS b9_generate_session_info_pac(struct auth4_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ struct smb_krb5_context *smb_krb5_context,
+ DATA_BLOB *pac_blob,
+ const char *principal_name,
+ const struct tsocket_address *remote_addr,
+ uint32_t session_info_flags,
+ struct auth_session_info **session_info)
+{
+ NTSTATUS status;
+ struct auth_user_info_dc *user_info_dc;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ status = kerberos_pac_blob_to_user_info_dc(tmp_ctx,
+ *pac_blob,
+ smb_krb5_context->krb5_context,
+ &user_info_dc,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ if (!(user_info_dc->info->user_flags & NETLOGON_GUEST)) {
+ session_info_flags |= AUTH_SESSION_INFO_AUTHENTICATED;
+ }
+
+ session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES;
+
+ status = auth_generate_session_info(mem_ctx, auth_context->lp_ctx, NULL, user_info_dc,
+ session_info_flags, session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+/* Callback for the DEBUG() system, to catch the remaining messages */
+static void b9_debug(void *private_ptr, int msg_level, const char *msg)
+{
+ static const int isc_log_map[] = {
+ ISC_LOG_CRITICAL, /* 0 */
+ ISC_LOG_ERROR, /* 1 */
+ ISC_LOG_WARNING, /* 2 */
+ ISC_LOG_NOTICE /* 3 */
+ };
+ struct dlz_bind9_data *state = private_ptr;
+ int isc_log_level;
+
+ if (msg_level >= ARRAY_SIZE(isc_log_map) || msg_level < 0) {
+ isc_log_level = ISC_LOG_INFO;
+ } else {
+ isc_log_level = isc_log_map[msg_level];
+ }
+ state->log(isc_log_level, "samba_dlz: %s", msg);
+}
+
+static int dlz_state_debug_unregister(struct dlz_bind9_data *state)
+{
+ /* Stop logging (to the bind9 logs) */
+ debug_set_callback(NULL, NULL);
+ return 0;
+}
+
+/*
+ called to initialise the driver
+ */
+_PUBLIC_ isc_result_t dlz_create(const char *dlzname,
+ unsigned int argc, const char **argv,
+ void **dbdata, ...)
+{
+ struct dlz_bind9_data *state;
+ const char *helper_name;
+ va_list ap;
+ isc_result_t result;
+ struct ldb_dn *dn;
+ NTSTATUS nt_status;
+ int ret;
+ char *errstring = NULL;
+
+ if (dlz_bind9_state != NULL) {
+ dlz_bind9_state->log(ISC_LOG_ERROR,
+ "samba_dlz: dlz_create ignored, #refs=%d",
+ dlz_bind9_state_ref_count);
+ *dbdata = dlz_bind9_state;
+ dlz_bind9_state_ref_count++;
+ return ISC_R_SUCCESS;
+ }
+
+ state = talloc_zero(NULL, struct dlz_bind9_data);
+ if (state == NULL) {
+ return ISC_R_NOMEMORY;
+ }
+
+ talloc_set_destructor(state, dlz_state_debug_unregister);
+
+ /* fill in the helper functions */
+ va_start(ap, dbdata);
+ while ((helper_name = va_arg(ap, const char *)) != NULL) {
+ b9_add_helper(state, helper_name, va_arg(ap, void*));
+ }
+ va_end(ap);
+
+ /* Do not install samba signal handlers */
+ fault_setup_disable();
+
+ /* Start logging (to the bind9 logs) */
+ debug_set_callback(state, b9_debug);
+
+ state->ev_ctx = s4_event_context_init(state);
+ if (state->ev_ctx == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto failed;
+ }
+
+ result = parse_options(state, argc, argv, &state->options);
+ if (result != ISC_R_SUCCESS) {
+ goto failed;
+ }
+
+ state->lp = loadparm_init_global(true);
+ if (state->lp == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto failed;
+ }
+
+ if (state->options.debug) {
+ lpcfg_do_global_parameter(state->lp, "log level", state->options.debug);
+ } else {
+ lpcfg_do_global_parameter(state->lp, "log level", "0");
+ }
+
+ if (smb_krb5_init_context(state, state->lp, &state->smb_krb5_ctx) != 0) {
+ result = ISC_R_NOMEMORY;
+ goto failed;
+ }
+
+ nt_status = gensec_init();
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ result = ISC_R_NOMEMORY;
+ goto failed;
+ }
+
+ state->auth_context = talloc_zero(state, struct auth4_context);
+ if (state->auth_context == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto failed;
+ }
+
+ if (state->options.url == NULL) {
+ state->options.url = talloc_asprintf(state,
+ "%s/dns/sam.ldb",
+ lpcfg_binddns_dir(state->lp));
+ if (state->options.url == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto failed;
+ }
+
+ if (!file_exist(state->options.url)) {
+ state->options.url = talloc_asprintf(state,
+ "%s/dns/sam.ldb",
+ lpcfg_private_dir(state->lp));
+ if (state->options.url == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto failed;
+ }
+ }
+ }
+
+ ret = samdb_connect_url(state,
+ state->ev_ctx,
+ state->lp,
+ system_session(state->lp),
+ 0,
+ state->options.url,
+ NULL,
+ &state->samdb,
+ &errstring);
+ if (ret != LDB_SUCCESS) {
+ state->log(ISC_LOG_ERROR,
+ "samba_dlz: Failed to connect to %s: %s",
+ errstring, ldb_strerror(ret));
+ result = ISC_R_FAILURE;
+ goto failed;
+ }
+
+ dn = ldb_get_default_basedn(state->samdb);
+ if (dn == NULL) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: Unable to get basedn for %s - %s",
+ state->options.url, ldb_errstring(state->samdb));
+ result = ISC_R_FAILURE;
+ goto failed;
+ }
+
+ state->log(ISC_LOG_INFO, "samba_dlz: started for DN %s",
+ ldb_dn_get_linearized(dn));
+
+ state->auth_context->event_ctx = state->ev_ctx;
+ state->auth_context->lp_ctx = state->lp;
+ state->auth_context->sam_ctx = state->samdb;
+ state->auth_context->generate_session_info_pac = b9_generate_session_info_pac;
+
+ *dbdata = state;
+ dlz_bind9_state = state;
+ dlz_bind9_state_ref_count++;
+
+ return ISC_R_SUCCESS;
+
+failed:
+ state->log(ISC_LOG_INFO,
+ "samba_dlz: FAILED dlz_create call result=%d #refs=%d",
+ result,
+ dlz_bind9_state_ref_count);
+ talloc_free(state);
+ return result;
+}
+
+/*
+ shutdown the backend
+ */
+_PUBLIC_ void dlz_destroy(void *dbdata)
+{
+ struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+
+ dlz_bind9_state_ref_count--;
+ if (dlz_bind9_state_ref_count == 0) {
+ state->log(ISC_LOG_INFO, "samba_dlz: shutting down");
+ talloc_unlink(state, state->samdb);
+ talloc_free(state);
+ dlz_bind9_state = NULL;
+ } else {
+ state->log(ISC_LOG_INFO,
+ "samba_dlz: dlz_destroy called. %d refs remaining.",
+ dlz_bind9_state_ref_count);
+ }
+}
+
+
+/*
+ return the base DN for a zone
+ */
+static isc_result_t b9_find_zone_dn(struct dlz_bind9_data *state, const char *zone_name,
+ TALLOC_CTX *mem_ctx, struct ldb_dn **zone_dn)
+{
+ int ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(state);
+ const char *attrs[] = { NULL };
+ int i;
+
+ for (i=0; zone_prefixes[i]; i++) {
+ const char *casefold;
+ struct ldb_dn *dn;
+ struct ldb_result *res;
+ struct ldb_val zone_name_val
+ = data_blob_string_const(zone_name);
+
+ dn = ldb_dn_copy(tmp_ctx, ldb_get_default_basedn(state->samdb));
+ if (dn == NULL) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOMEMORY;
+ }
+
+ /*
+ * This dance ensures that it is not possible to put
+ * (eg) an extra DC=x, into the DNS name being
+ * queried
+ */
+
+ if (!ldb_dn_add_child_fmt(dn,
+ "DC=X,%s",
+ zone_prefixes[i])) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOMEMORY;
+ }
+
+ ret = ldb_dn_set_component(dn,
+ 0,
+ "DC",
+ zone_name_val);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOMEMORY;
+ }
+
+ /*
+ * Check if this is a plausibly valid DN early
+ * (time spent here will be saved during the
+ * search due to an internal cache)
+ */
+ casefold = ldb_dn_get_casefold(dn);
+
+ if (casefold == NULL) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOTFOUND;
+ }
+
+ ret = ldb_search(state->samdb, tmp_ctx, &res, dn, LDB_SCOPE_BASE, attrs, "objectClass=dnsZone");
+ if (ret == LDB_SUCCESS) {
+ if (zone_dn != NULL) {
+ *zone_dn = talloc_steal(mem_ctx, dn);
+ }
+ talloc_free(tmp_ctx);
+ return ISC_R_SUCCESS;
+ }
+ talloc_free(dn);
+ }
+
+ talloc_free(tmp_ctx);
+ return ISC_R_NOTFOUND;
+}
+
+
+/*
+ return the DN for a name. The record does not need to exist, but the
+ zone must exist
+ */
+static isc_result_t b9_find_name_dn(struct dlz_bind9_data *state, const char *name,
+ TALLOC_CTX *mem_ctx, struct ldb_dn **dn)
+{
+ const char *p;
+
+ /* work through the name piece by piece, until we find a zone */
+ for (p=name; p; ) {
+ isc_result_t result;
+ result = b9_find_zone_dn(state, p, mem_ctx, dn);
+ if (result == ISC_R_SUCCESS) {
+ const char *casefold;
+
+ /* we found a zone, now extend the DN to get
+ * the full DN
+ */
+ bool ret;
+ if (p == name) {
+ ret = ldb_dn_add_child_fmt(*dn, "DC=@");
+ if (ret == false) {
+ talloc_free(*dn);
+ return ISC_R_NOMEMORY;
+ }
+ } else {
+ struct ldb_val name_val
+ = data_blob_const(name,
+ (int)(p-name)-1);
+
+ if (!ldb_dn_add_child_val(*dn,
+ "DC",
+ name_val)) {
+ talloc_free(*dn);
+ return ISC_R_NOMEMORY;
+ }
+ }
+
+ /*
+ * Check if this is a plausibly valid DN early
+ * (time spent here will be saved during the
+ * search due to an internal cache)
+ */
+ casefold = ldb_dn_get_casefold(*dn);
+
+ if (casefold == NULL) {
+ return ISC_R_NOTFOUND;
+ }
+
+ return ISC_R_SUCCESS;
+ }
+ p = strchr(p, '.');
+ if (p == NULL) {
+ break;
+ }
+ p++;
+ }
+ return ISC_R_NOTFOUND;
+}
+
+
+/*
+ see if we handle a given zone
+ */
+_PUBLIC_ isc_result_t dlz_findzonedb(void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo)
+{
+ struct timeval start = timeval_current();
+ struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+ isc_result_t result = ISC_R_SUCCESS;
+
+ result = b9_find_zone_dn(state, name, NULL, NULL);
+ DNS_COMMON_LOG_OPERATION(
+ isc_result_str(result),
+ &start,
+ NULL,
+ name,
+ NULL);
+ return result;
+}
+
+
+/*
+ lookup one record
+ */
+static isc_result_t dlz_lookup_types(struct dlz_bind9_data *state,
+ const char *zone, const char *name,
+ dns_sdlzlookup_t *lookup,
+ const char **types)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(state);
+ struct ldb_dn *dn;
+ WERROR werr = WERR_DNS_ERROR_NAME_DOES_NOT_EXIST;
+ struct dnsp_DnssrvRpcRecord *records = NULL;
+ uint16_t num_records = 0, i;
+ struct ldb_val zone_name_val
+ = data_blob_string_const(zone);
+ struct ldb_val name_val
+ = data_blob_string_const(name);
+
+ for (i=0; zone_prefixes[i]; i++) {
+ int ret;
+ const char *casefold;
+ dn = ldb_dn_copy(tmp_ctx, ldb_get_default_basedn(state->samdb));
+ if (dn == NULL) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOMEMORY;
+ }
+
+ /*
+ * This dance ensures that it is not possible to put
+ * (eg) an extra DC=x, into the DNS name being
+ * queried
+ */
+
+ if (!ldb_dn_add_child_fmt(dn,
+ "DC=X,DC=X,%s",
+ zone_prefixes[i])) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOMEMORY;
+ }
+
+ ret = ldb_dn_set_component(dn,
+ 1,
+ "DC",
+ zone_name_val);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOMEMORY;
+ }
+
+ ret = ldb_dn_set_component(dn,
+ 0,
+ "DC",
+ name_val);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOMEMORY;
+ }
+
+ /*
+ * Check if this is a plausibly valid DN early
+ * (time spent here will be saved during the
+ * search due to an internal cache)
+ */
+ casefold = ldb_dn_get_casefold(dn);
+
+ if (casefold == NULL) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOTFOUND;
+ }
+
+ werr = dns_common_wildcard_lookup(state->samdb, tmp_ctx, dn,
+ &records, &num_records);
+ if (W_ERROR_IS_OK(werr)) {
+ break;
+ }
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOTFOUND;
+ }
+
+ for (i=0; i < num_records; i++) {
+ isc_result_t result;
+
+ result = b9_putrr(state, lookup, &records[i], types);
+ if (result != ISC_R_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return ISC_R_SUCCESS;
+}
+
+/*
+ lookup one record
+ */
+_PUBLIC_ isc_result_t dlz_lookup(const char *zone, const char *name,
+ void *dbdata, dns_sdlzlookup_t *lookup,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo)
+{
+ struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+ isc_result_t result = ISC_R_SUCCESS;
+ struct timeval start = timeval_current();
+
+ result = dlz_lookup_types(state, zone, name, lookup, NULL);
+ DNS_COMMON_LOG_OPERATION(
+ isc_result_str(result),
+ &start,
+ zone,
+ name,
+ NULL);
+
+ return result;
+}
+
+
+/*
+ see if a zone transfer is allowed
+ */
+_PUBLIC_ isc_result_t dlz_allowzonexfr(void *dbdata, const char *name, const char *client)
+{
+ struct dlz_bind9_data *state = talloc_get_type(
+ dbdata, struct dlz_bind9_data);
+ isc_result_t ret;
+ const char **authorized_clients, **denied_clients;
+ const char *cname="";
+
+ /* check that the zone is known */
+ ret = b9_find_zone_dn(state, name, NULL, NULL);
+ if (ret != ISC_R_SUCCESS) {
+ return ret;
+ }
+
+ /* default is to deny all transfers */
+
+ authorized_clients = lpcfg_dns_zone_transfer_clients_allow(state->lp);
+ denied_clients = lpcfg_dns_zone_transfer_clients_deny(state->lp);
+
+ /* The logic of allow_access() when both allow and deny lists are given
+ * does not match our expectation here: it would allow clients that are
+ * neither allowed nor denied.
+ * Here, we want to deny clients by default.
+ * Using the allow_access() function is still useful as it takes care of
+ * parsing IP addresses and subnets in a consistent way with other options
+ * from smb.conf.
+ *
+ * We will then check the deny list first, then the allow list, so that
+ * we accept only clients that are explicitly allowed AND not explicitly
+ * denied.
+ */
+ if ((authorized_clients == NULL) && (denied_clients == NULL)) {
+ /* No "allow" or "deny" lists given. Deny by default. */
+ return ISC_R_NOPERM;
+ }
+
+ if (denied_clients != NULL) {
+ bool ok = allow_access(denied_clients, NULL, cname, client);
+ if (!ok) {
+ /* client on deny list. Deny. */
+ return ISC_R_NOPERM;
+ }
+ }
+
+ if (authorized_clients != NULL) {
+ bool ok = allow_access(NULL, authorized_clients, cname, client);
+ if (ok) {
+ /*
+ * client is not on deny list and is on allow list.
+ * This is the only place we should return "allow".
+ */
+ return ISC_R_SUCCESS;
+ }
+ }
+ /* We shouldn't get here, but deny by default. */
+ return ISC_R_NOPERM;
+}
+
+/*
+ perform a zone transfer
+ */
+_PUBLIC_ isc_result_t dlz_allnodes(const char *zone, void *dbdata,
+ dns_sdlzallnodes_t *allnodes)
+{
+ struct timeval start = timeval_current();
+ struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+ const char *attrs[] = { "dnsRecord", NULL };
+ int ret = LDB_ERR_NO_SUCH_OBJECT;
+ size_t i, j;
+ struct ldb_dn *dn = NULL;
+ struct ldb_result *res;
+ TALLOC_CTX *tmp_ctx = talloc_new(state);
+ struct ldb_val zone_name_val = data_blob_string_const(zone);
+ isc_result_t result = ISC_R_SUCCESS;
+
+ for (i=0; zone_prefixes[i]; i++) {
+ const char *casefold;
+
+ dn = ldb_dn_copy(tmp_ctx, ldb_get_default_basedn(state->samdb));
+ if (dn == NULL) {
+ talloc_free(tmp_ctx);
+ result = ISC_R_NOMEMORY;
+ goto exit;
+ }
+
+ /*
+ * This dance ensures that it is not possible to put
+ * (eg) an extra DC=x, into the DNS name being
+ * queried
+ */
+
+ if (!ldb_dn_add_child_fmt(dn,
+ "DC=X,%s",
+ zone_prefixes[i])) {
+ talloc_free(tmp_ctx);
+ result = ISC_R_NOMEMORY;
+ goto exit;
+ }
+
+ ret = ldb_dn_set_component(dn,
+ 0,
+ "DC",
+ zone_name_val);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ result = ISC_R_NOMEMORY;
+ goto exit;
+ }
+
+ /*
+ * Check if this is a plausibly valid DN early
+ * (time spent here will be saved during the
+ * search due to an internal cache)
+ */
+ casefold = ldb_dn_get_casefold(dn);
+
+ if (casefold == NULL) {
+ result = ISC_R_NOTFOUND;
+ goto exit;
+ }
+
+ ret = ldb_search(state->samdb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE,
+ attrs, "objectClass=dnsNode");
+ if (ret == LDB_SUCCESS) {
+ break;
+ }
+ }
+ if (ret != LDB_SUCCESS || dn == NULL) {
+ talloc_free(tmp_ctx);
+ result = ISC_R_NOTFOUND;
+ goto exit;
+ }
+
+ for (i=0; i<res->count; i++) {
+ struct ldb_message_element *el;
+ TALLOC_CTX *el_ctx = talloc_new(tmp_ctx);
+ const char *rdn, *name;
+ const struct ldb_val *v;
+ WERROR werr;
+ struct dnsp_DnssrvRpcRecord *recs = NULL;
+ uint16_t num_recs = 0;
+
+ el = ldb_msg_find_element(res->msgs[i], "dnsRecord");
+ if (el == NULL || el->num_values == 0) {
+ state->log(ISC_LOG_INFO, "failed to find dnsRecord for %s",
+ ldb_dn_get_linearized(dn));
+ talloc_free(el_ctx);
+ continue;
+ }
+
+ v = ldb_dn_get_rdn_val(res->msgs[i]->dn);
+ if (v == NULL) {
+ state->log(ISC_LOG_INFO, "failed to find RDN for %s",
+ ldb_dn_get_linearized(dn));
+ talloc_free(el_ctx);
+ continue;
+ }
+
+ rdn = talloc_strndup(el_ctx, (char *)v->data, v->length);
+ if (rdn == NULL) {
+ talloc_free(tmp_ctx);
+ result = ISC_R_NOMEMORY;
+ goto exit;
+ }
+
+ if (strcmp(rdn, "@") == 0) {
+ name = zone;
+ } else {
+ name = talloc_asprintf(el_ctx, "%s.%s", rdn, zone);
+ }
+ name = b9_format_fqdn(el_ctx, name);
+ if (name == NULL) {
+ talloc_free(tmp_ctx);
+ result = ISC_R_NOMEMORY;
+ goto exit;
+ }
+
+ werr = dns_common_extract(state->samdb, el, el_ctx, &recs, &num_recs);
+ if (!W_ERROR_IS_OK(werr)) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: failed to parse dnsRecord for %s, %s",
+ ldb_dn_get_linearized(dn), win_errstr(werr));
+ talloc_free(el_ctx);
+ continue;
+ }
+
+ for (j=0; j < num_recs; j++) {
+ isc_result_t rc;
+
+ rc = b9_putnamedrr(state, allnodes, name, &recs[j]);
+ if (rc != ISC_R_SUCCESS) {
+ continue;
+ }
+ }
+
+ talloc_free(el_ctx);
+ }
+
+ talloc_free(tmp_ctx);
+exit:
+ DNS_COMMON_LOG_OPERATION(
+ isc_result_str(result),
+ &start,
+ zone,
+ NULL,
+ NULL);
+ return result;
+}
+
+
+/*
+ start a transaction
+ */
+_PUBLIC_ isc_result_t dlz_newversion(const char *zone, void *dbdata, void **versionp)
+{
+ struct timeval start = timeval_current();
+ struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+ isc_result_t result = ISC_R_SUCCESS;
+
+ state->log(ISC_LOG_INFO, "samba_dlz: starting transaction on zone %s", zone);
+
+ if (state->transaction_token != NULL) {
+ state->log(ISC_LOG_INFO, "samba_dlz: transaction already started for zone %s", zone);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ state->transaction_token = talloc_zero(state, int);
+ if (state->transaction_token == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto exit;
+ }
+
+ if (ldb_transaction_start(state->samdb) != LDB_SUCCESS) {
+ state->log(ISC_LOG_INFO, "samba_dlz: failed to start a transaction for zone %s", zone);
+ talloc_free(state->transaction_token);
+ state->transaction_token = NULL;
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ *versionp = (void *)state->transaction_token;
+exit:
+ DNS_COMMON_LOG_OPERATION(
+ isc_result_str(result),
+ &start,
+ zone,
+ NULL,
+ NULL);
+ return result;
+}
+
+/*
+ end a transaction
+ */
+_PUBLIC_ void dlz_closeversion(const char *zone, isc_boolean_t commit,
+ void *dbdata, void **versionp)
+{
+ struct timeval start = timeval_current();
+ struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+ const char *data = NULL;
+
+ data = commit ? "commit" : "cancel";
+
+ if (state->transaction_token != (int *)*versionp) {
+ state->log(ISC_LOG_INFO, "samba_dlz: transaction not started for zone %s", zone);
+ goto exit;
+ }
+
+ if (commit) {
+ if (ldb_transaction_commit(state->samdb) != LDB_SUCCESS) {
+ state->log(ISC_LOG_INFO, "samba_dlz: failed to commit a transaction for zone %s", zone);
+ goto exit;
+ }
+ state->log(ISC_LOG_INFO, "samba_dlz: committed transaction on zone %s", zone);
+ } else {
+ if (ldb_transaction_cancel(state->samdb) != LDB_SUCCESS) {
+ state->log(ISC_LOG_INFO, "samba_dlz: failed to cancel a transaction for zone %s", zone);
+ goto exit;
+ }
+ state->log(ISC_LOG_INFO, "samba_dlz: cancelling transaction on zone %s", zone);
+ }
+
+ talloc_free(state->transaction_token);
+ state->transaction_token = NULL;
+ *versionp = NULL;
+
+exit:
+ DNS_COMMON_LOG_OPERATION(
+ isc_result_str(ISC_R_SUCCESS),
+ &start,
+ zone,
+ NULL,
+ data);
+}
+
+
+/*
+ see if there is a SOA record for a zone
+ */
+static bool b9_has_soa(struct dlz_bind9_data *state, struct ldb_dn *dn, const char *zone)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(state);
+ WERROR werr;
+ struct dnsp_DnssrvRpcRecord *records = NULL;
+ uint16_t num_records = 0, i;
+ struct ldb_val zone_name_val
+ = data_blob_string_const(zone);
+
+ /*
+ * This dance ensures that it is not possible to put
+ * (eg) an extra DC=x, into the DNS name being
+ * queried
+ */
+
+ if (!ldb_dn_add_child_val(dn,
+ "DC",
+ zone_name_val)) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ /*
+ * The SOA record is always stored under DC=@,DC=zonename
+ * This can probably be removed when dns_common_lookup makes a fallback
+ * lookup on @ pseudo record
+ */
+
+ if (!ldb_dn_add_child_fmt(dn,"DC=@")) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ werr = dns_common_lookup(state->samdb, tmp_ctx, dn,
+ &records, &num_records, NULL);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ for (i=0; i < num_records; i++) {
+ if (records[i].wType == DNS_TYPE_SOA) {
+ talloc_free(tmp_ctx);
+ return true;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return false;
+}
+
+static bool b9_zone_add(struct dlz_bind9_data *state, const char *name)
+{
+ struct b9_zone *zone;
+
+ zone = talloc_zero(state, struct b9_zone);
+ if (zone == NULL) {
+ return false;
+ }
+
+ zone->name = talloc_strdup(zone, name);
+ if (zone->name == NULL) {
+ talloc_free(zone);
+ return false;
+ }
+
+ DLIST_ADD(state->zonelist, zone);
+ return true;
+}
+
+static bool b9_zone_exists(struct dlz_bind9_data *state, const char *name)
+{
+ struct b9_zone *zone = state->zonelist;
+ bool found = false;
+
+ while (zone != NULL) {
+ if (strcasecmp(name, zone->name) == 0) {
+ found = true;
+ break;
+ }
+ zone = zone->next;
+ }
+
+ return found;
+}
+
+
+/*
+ configure a writeable zone
+ */
+_PUBLIC_ isc_result_t dlz_configure(dns_view_t *view, dns_dlzdb_t *dlzdb,
+ void *dbdata)
+{
+ struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_dn *dn;
+ int i;
+
+ state->log(ISC_LOG_INFO, "samba_dlz: starting configure");
+ if (state->writeable_zone == NULL) {
+ state->log(ISC_LOG_INFO, "samba_dlz: no writeable_zone method available");
+ return ISC_R_FAILURE;
+ }
+
+ tmp_ctx = talloc_new(state);
+
+ for (i=0; zone_prefixes[i]; i++) {
+ const char *attrs[] = { "name", NULL };
+ int j, ret;
+ struct ldb_result *res;
+
+ dn = ldb_dn_copy(tmp_ctx, ldb_get_default_basedn(state->samdb));
+ if (dn == NULL) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOMEMORY;
+ }
+
+ if (!ldb_dn_add_child_fmt(dn, "%s", zone_prefixes[i])) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOMEMORY;
+ }
+
+ ret = ldb_search(state->samdb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE,
+ attrs, "objectClass=dnsZone");
+ if (ret != LDB_SUCCESS) {
+ continue;
+ }
+
+ for (j=0; j<res->count; j++) {
+ isc_result_t result;
+ const char *zone = ldb_msg_find_attr_as_string(res->msgs[j], "name", NULL);
+ struct ldb_dn *zone_dn;
+
+ if (zone == NULL) {
+ continue;
+ }
+ /* Ignore zones that are not handled in BIND */
+ if ((strcmp(zone, "RootDNSServers") == 0) ||
+ (strcmp(zone, "..TrustAnchors") == 0)) {
+ continue;
+ }
+ zone_dn = ldb_dn_copy(tmp_ctx, dn);
+ if (zone_dn == NULL) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOMEMORY;
+ }
+
+ if (!b9_has_soa(state, zone_dn, zone)) {
+ continue;
+ }
+
+ if (b9_zone_exists(state, zone)) {
+ state->log(ISC_LOG_WARNING, "samba_dlz: Ignoring duplicate zone '%s' from '%s'",
+ zone, ldb_dn_get_linearized(zone_dn));
+ continue;
+ }
+
+ if (!b9_zone_add(state, zone)) {
+ talloc_free(tmp_ctx);
+ return ISC_R_NOMEMORY;
+ }
+
+ result = state->writeable_zone(view, dlzdb, zone);
+ if (result != ISC_R_SUCCESS) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: Failed to configure zone '%s'",
+ zone);
+ talloc_free(tmp_ctx);
+ return result;
+ }
+ state->log(ISC_LOG_INFO, "samba_dlz: configured writeable zone '%s'", zone);
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return ISC_R_SUCCESS;
+}
+
+/*
+ authorize a zone update
+ */
+_PUBLIC_ isc_boolean_t dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr,
+ const char *type, const char *key, uint32_t keydatalen, uint8_t *keydata,
+ void *dbdata)
+{
+ struct timeval start = timeval_current();
+ struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+ TALLOC_CTX *tmp_ctx;
+ DATA_BLOB ap_req;
+ struct cli_credentials *server_credentials;
+ char *keytab_name;
+ char *keytab_file = NULL;
+ int ret;
+ int ldb_ret;
+ NTSTATUS nt_status;
+ struct gensec_security *gensec_ctx;
+ struct auth_session_info *session_info;
+ struct ldb_dn *dn;
+ isc_result_t rc;
+ struct ldb_result *res;
+ const char * attrs[] = { NULL };
+ uint32_t access_mask;
+ struct gensec_settings *settings = NULL;
+ const struct gensec_security_ops **backends = NULL;
+ size_t idx = 0;
+ isc_boolean_t result = ISC_FALSE;
+ NTSTATUS status;
+ bool ok;
+
+ /* Remove cached credentials, if any */
+ if (state->session_info) {
+ talloc_free(state->session_info);
+ state->session_info = NULL;
+ }
+ if (state->update_name) {
+ talloc_free(state->update_name);
+ state->update_name = NULL;
+ }
+
+ tmp_ctx = talloc_new(state);
+ if (tmp_ctx == NULL) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: no memory");
+ result = ISC_FALSE;
+ goto exit;
+ }
+
+ ap_req = data_blob_const(keydata, keydatalen);
+ server_credentials = cli_credentials_init(tmp_ctx);
+ if (!server_credentials) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: failed to init server credentials");
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+
+ status = cli_credentials_set_krb5_context(server_credentials,
+ state->smb_krb5_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ state->log(ISC_LOG_ERROR,
+ "samba_dlz: failed to set krb5 context");
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+
+ ok = cli_credentials_set_conf(server_credentials, state->lp);
+ if (!ok) {
+ state->log(ISC_LOG_ERROR,
+ "samba_dlz: failed to load smb.conf");
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+
+ keytab_file = talloc_asprintf(tmp_ctx,
+ "%s/dns.keytab",
+ lpcfg_binddns_dir(state->lp));
+ if (keytab_file == NULL) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: Out of memory!");
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+
+ if (!file_exist(keytab_file)) {
+ keytab_file = talloc_asprintf(tmp_ctx,
+ "%s/dns.keytab",
+ lpcfg_private_dir(state->lp));
+ if (keytab_file == NULL) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: Out of memory!");
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+ }
+
+ keytab_name = talloc_asprintf(tmp_ctx, "FILE:%s", keytab_file);
+ if (keytab_name == NULL) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: Out of memory!");
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+
+ ret = cli_credentials_set_keytab_name(server_credentials, state->lp, keytab_name,
+ CRED_SPECIFIED);
+ if (ret != 0) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: failed to obtain server credentials from %s",
+ keytab_name);
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+ talloc_free(keytab_name);
+
+ settings = lpcfg_gensec_settings(tmp_ctx, state->lp);
+ if (settings == NULL) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: lpcfg_gensec_settings failed");
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+ backends = talloc_zero_array(settings,
+ const struct gensec_security_ops *, 3);
+ if (backends == NULL) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: talloc_zero_array gensec_security_ops failed");
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+ settings->backends = backends;
+
+ gensec_init();
+
+ backends[idx++] = gensec_security_by_oid(NULL, GENSEC_OID_KERBEROS5);
+ backends[idx++] = gensec_security_by_oid(NULL, GENSEC_OID_SPNEGO);
+
+ nt_status = gensec_server_start(tmp_ctx, settings,
+ state->auth_context, &gensec_ctx);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: failed to start gensec server");
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+
+ gensec_set_credentials(gensec_ctx, server_credentials);
+
+ nt_status = gensec_start_mech_by_oid(gensec_ctx, GENSEC_OID_SPNEGO);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: failed to start spnego");
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+
+ /*
+ * We only allow SPNEGO/KRB5 and make sure the backend
+ * to is RPC/IPC free.
+ *
+ * See gensec_gssapi_update_internal() as
+ * GENSEC_SERVER.
+ *
+ * It allows gensec_update() not to block.
+ *
+ * If that changes in future we need to use
+ * gensec_update_send/recv here!
+ */
+ nt_status = gensec_update(gensec_ctx, tmp_ctx, ap_req, &ap_req);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: spnego update failed");
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+
+ nt_status = gensec_session_info(gensec_ctx, tmp_ctx, &session_info);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: failed to create session info");
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+
+ /* Get the DN from name */
+ rc = b9_find_name_dn(state, name, tmp_ctx, &dn);
+ if (rc != ISC_R_SUCCESS) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: failed to find name %s", name);
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+
+ /* make sure the dn exists, or find parent dn in case new object is being added */
+ ldb_ret = ldb_search(state->samdb, tmp_ctx, &res, dn, LDB_SCOPE_BASE,
+ attrs, "objectClass=dnsNode");
+ if (ldb_ret == LDB_ERR_NO_SUCH_OBJECT) {
+ ldb_dn_remove_child_components(dn, 1);
+ access_mask = SEC_ADS_CREATE_CHILD;
+ talloc_free(res);
+ } else if (ldb_ret == LDB_SUCCESS) {
+ access_mask = SEC_STD_REQUIRED | SEC_ADS_SELF_WRITE;
+ talloc_free(res);
+ } else {
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+
+ /* Do ACL check */
+ ldb_ret = dsdb_check_access_on_dn(state->samdb, tmp_ctx, dn,
+ session_info->security_token,
+ access_mask, NULL);
+ if (ldb_ret != LDB_SUCCESS) {
+ state->log(ISC_LOG_INFO,
+ "samba_dlz: disallowing update of signer=%s name=%s type=%s error=%s",
+ signer, name, type, ldb_strerror(ldb_ret));
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+
+ /* Cache session_info, so it can be used in the actual add/delete operation */
+ state->update_name = talloc_strdup(state, name);
+ if (state->update_name == NULL) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: memory allocation error");
+ talloc_free(tmp_ctx);
+ result = ISC_FALSE;
+ goto exit;
+ }
+ state->session_info = talloc_steal(state, session_info);
+
+ state->log(ISC_LOG_INFO, "samba_dlz: allowing update of signer=%s name=%s tcpaddr=%s type=%s key=%s",
+ signer, name, tcpaddr, type, key);
+
+ talloc_free(tmp_ctx);
+ result = ISC_TRUE;
+exit:
+ DNS_COMMON_LOG_OPERATION(
+ isc_result_str(result),
+ &start,
+ NULL,
+ name,
+ NULL);
+ return result;
+}
+
+
+/*
+ see if two dns records match
+ */
+static bool b9_record_match(struct dnsp_DnssrvRpcRecord *rec1,
+ struct dnsp_DnssrvRpcRecord *rec2)
+{
+ if (rec1->wType != rec2->wType) {
+ return false;
+ }
+ /* see if this type is single valued */
+ if (b9_single_valued(rec1->wType)) {
+ return true;
+ }
+
+ return dns_record_match(rec1, rec2);
+}
+
+/*
+ * Update session_info on samdb using the cached credentials
+ */
+static bool b9_set_session_info(struct dlz_bind9_data *state, const char *name)
+{
+ int ret;
+
+ if (state->update_name == NULL || state->session_info == NULL) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: invalid credentials");
+ return false;
+ }
+
+ /* Do not use client credentials, if we're not updating the client specified name */
+ if (strcmp(state->update_name, name) != 0) {
+ return true;
+ }
+
+ ret = ldb_set_opaque(
+ state->samdb,
+ DSDB_SESSION_INFO,
+ state->session_info);
+ if (ret != LDB_SUCCESS) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: unable to set session info");
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Reset session_info on samdb as system session
+ */
+static void b9_reset_session_info(struct dlz_bind9_data *state)
+{
+ ldb_set_opaque(
+ state->samdb,
+ DSDB_SESSION_INFO,
+ system_session(state->lp));
+}
+
+/*
+ add or modify a rdataset
+ */
+_PUBLIC_ isc_result_t dlz_addrdataset(const char *name, const char *rdatastr, void *dbdata, void *version)
+{
+ struct timeval start = timeval_current();
+ struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+ struct dnsp_DnssrvRpcRecord *rec;
+ struct ldb_dn *dn;
+ isc_result_t result = ISC_R_SUCCESS;
+ bool tombstoned = false;
+ bool needs_add = false;
+ struct dnsp_DnssrvRpcRecord *recs = NULL;
+ uint16_t num_recs = 0;
+ uint16_t first = 0;
+ uint16_t i;
+ WERROR werr;
+
+ if (state->transaction_token != (void*)version) {
+ state->log(ISC_LOG_INFO, "samba_dlz: bad transaction version");
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ rec = talloc_zero(state, struct dnsp_DnssrvRpcRecord);
+ if (rec == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto exit;
+ }
+
+ rec->rank = DNS_RANK_ZONE;
+
+ if (!b9_parse(state, rdatastr, rec)) {
+ state->log(ISC_LOG_INFO, "samba_dlz: failed to parse rdataset '%s'", rdatastr);
+ talloc_free(rec);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ /* find the DN of the record */
+ result = b9_find_name_dn(state, name, rec, &dn);
+ if (result != ISC_R_SUCCESS) {
+ talloc_free(rec);
+ goto exit;
+ }
+
+ /* get any existing records */
+ werr = dns_common_lookup(state->samdb, rec, dn,
+ &recs, &num_recs, &tombstoned);
+ if (W_ERROR_EQUAL(werr, WERR_DNS_ERROR_NAME_DOES_NOT_EXIST)) {
+ needs_add = true;
+ werr = WERR_OK;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: failed to parse dnsRecord for %s, %s",
+ ldb_dn_get_linearized(dn), win_errstr(werr));
+ talloc_free(rec);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ if (tombstoned) {
+ /*
+ * we need to keep the existing tombstone record
+ * and ignore it
+ */
+ first = num_recs;
+ }
+
+ /* there may be existing records. We need to see if this will
+ * replace a record or add to it
+ */
+ for (i=first; i < num_recs; i++) {
+ if (b9_record_match(rec, &recs[i])) {
+ break;
+ }
+ }
+ if (i == UINT16_MAX) {
+ state->log(ISC_LOG_ERROR,
+ "samba_dlz: failed to find record to modify, and "
+ "there are already %u dnsRecord values for %s",
+ i, ldb_dn_get_linearized(dn));
+ talloc_free(rec);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ if (i == num_recs) {
+ /* set dwTimeStamp before increasing num_recs */
+ if (dns_name_is_static(recs, num_recs)) {
+ rec->dwTimeStamp = 0;
+ } else {
+ rec->dwTimeStamp = unix_to_dns_timestamp(time(NULL));
+ }
+ /* adding space for a new value */
+ recs = talloc_realloc(rec, recs,
+ struct dnsp_DnssrvRpcRecord,
+ num_recs + 1);
+ if (recs == NULL) {
+ talloc_free(rec);
+ result = ISC_R_NOMEMORY;
+ goto exit;
+ }
+ num_recs++;
+ } else {
+ /*
+ * We are updating a record. Depending on whether aging is
+ * enabled, and how old the old timestamp is,
+ * dns_common_replace() will work out whether to bump the
+ * timestamp or not. But to do that, we need to tell it the
+ * old timestamp.
+ */
+ if (! dns_name_is_static(recs, num_recs)) {
+ rec->dwTimeStamp = recs[i].dwTimeStamp;
+ }
+ }
+
+ recs[i] = *rec;
+
+ if (!b9_set_session_info(state, name)) {
+ talloc_free(rec);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ /* modify the record */
+ werr = dns_common_replace(state->samdb, rec, dn,
+ needs_add,
+ state->soa_serial,
+ recs, num_recs);
+ b9_reset_session_info(state);
+ if (!W_ERROR_IS_OK(werr)) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: failed to %s %s - %s",
+ needs_add ? "add" : "modify",
+ ldb_dn_get_linearized(dn), win_errstr(werr));
+ talloc_free(rec);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ state->log(ISC_LOG_INFO, "samba_dlz: added rdataset %s '%s'", name, rdatastr);
+
+ talloc_free(rec);
+exit:
+ DNS_COMMON_LOG_OPERATION(
+ isc_result_str(result),
+ &start,
+ NULL,
+ name,
+ rdatastr);
+ return result;
+}
+
+/*
+ remove a rdataset
+ */
+_PUBLIC_ isc_result_t dlz_subrdataset(const char *name, const char *rdatastr, void *dbdata, void *version)
+{
+ struct timeval start = timeval_current();
+ struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+ struct dnsp_DnssrvRpcRecord *rec;
+ struct ldb_dn *dn;
+ isc_result_t result = ISC_R_SUCCESS;
+ struct dnsp_DnssrvRpcRecord *recs = NULL;
+ uint16_t num_recs = 0;
+ uint16_t i;
+ WERROR werr;
+
+ if (state->transaction_token != (void*)version) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: bad transaction version");
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ rec = talloc_zero(state, struct dnsp_DnssrvRpcRecord);
+ if (rec == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto exit;
+ }
+
+ if (!b9_parse(state, rdatastr, rec)) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: failed to parse rdataset '%s'", rdatastr);
+ talloc_free(rec);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ /* find the DN of the record */
+ result = b9_find_name_dn(state, name, rec, &dn);
+ if (result != ISC_R_SUCCESS) {
+ talloc_free(rec);
+ goto exit;
+ }
+
+ /* get the existing records */
+ werr = dns_common_lookup(state->samdb, rec, dn,
+ &recs, &num_recs, NULL);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(rec);
+ result = ISC_R_NOTFOUND;
+ goto exit;
+ }
+
+ for (i=0; i < num_recs; i++) {
+ if (b9_record_match(rec, &recs[i])) {
+ recs[i] = (struct dnsp_DnssrvRpcRecord) {
+ .wType = DNS_TYPE_TOMBSTONE,
+ };
+ break;
+ }
+ }
+ if (i == num_recs) {
+ talloc_free(rec);
+ result = ISC_R_NOTFOUND;
+ goto exit;
+ }
+
+ if (!b9_set_session_info(state, name)) {
+ talloc_free(rec);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ /* modify the record */
+ werr = dns_common_replace(state->samdb, rec, dn,
+ false,/* needs_add */
+ state->soa_serial,
+ recs, num_recs);
+ b9_reset_session_info(state);
+ if (!W_ERROR_IS_OK(werr)) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: failed to modify %s - %s",
+ ldb_dn_get_linearized(dn), win_errstr(werr));
+ talloc_free(rec);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ state->log(ISC_LOG_INFO, "samba_dlz: subtracted rdataset %s '%s'", name, rdatastr);
+
+ talloc_free(rec);
+exit:
+ DNS_COMMON_LOG_OPERATION(
+ isc_result_str(result),
+ &start,
+ NULL,
+ name,
+ rdatastr);
+ return result;
+}
+
+
+/*
+ delete all records of the given type
+ */
+_PUBLIC_ isc_result_t dlz_delrdataset(const char *name, const char *type, void *dbdata, void *version)
+{
+ struct timeval start = timeval_current();
+ struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+ TALLOC_CTX *tmp_ctx;
+ struct ldb_dn *dn;
+ isc_result_t result = ISC_R_SUCCESS;
+ enum dns_record_type dns_type;
+ bool found = false;
+ struct dnsp_DnssrvRpcRecord *recs = NULL;
+ uint16_t num_recs = 0;
+ uint16_t ri = 0;
+ WERROR werr;
+
+ if (state->transaction_token != (void*)version) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: bad transaction version");
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ if (!b9_dns_type(type, &dns_type)) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: bad dns type %s in delete", type);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ tmp_ctx = talloc_new(state);
+
+ /* find the DN of the record */
+ result = b9_find_name_dn(state, name, tmp_ctx, &dn);
+ if (result != ISC_R_SUCCESS) {
+ talloc_free(tmp_ctx);
+ goto exit;
+ }
+
+ /* get the existing records */
+ werr = dns_common_lookup(state->samdb, tmp_ctx, dn,
+ &recs, &num_recs, NULL);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(tmp_ctx);
+ result = ISC_R_NOTFOUND;
+ goto exit;
+ }
+
+ for (ri=0; ri < num_recs; ri++) {
+ if (dns_type != recs[ri].wType) {
+ continue;
+ }
+
+ found = true;
+ recs[ri] = (struct dnsp_DnssrvRpcRecord) {
+ .wType = DNS_TYPE_TOMBSTONE,
+ };
+ }
+
+ if (!found) {
+ talloc_free(tmp_ctx);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ if (!b9_set_session_info(state, name)) {
+ talloc_free(tmp_ctx);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ /* modify the record */
+ werr = dns_common_replace(state->samdb, tmp_ctx, dn,
+ false,/* needs_add */
+ state->soa_serial,
+ recs, num_recs);
+ b9_reset_session_info(state);
+ if (!W_ERROR_IS_OK(werr)) {
+ state->log(ISC_LOG_ERROR, "samba_dlz: failed to modify %s - %s",
+ ldb_dn_get_linearized(dn), win_errstr(werr));
+ talloc_free(tmp_ctx);
+ result = ISC_R_FAILURE;
+ goto exit;
+ }
+
+ state->log(ISC_LOG_INFO, "samba_dlz: deleted rdataset %s of type %s", name, type);
+
+ talloc_free(tmp_ctx);
+exit:
+ DNS_COMMON_LOG_OPERATION(
+ isc_result_str(result),
+ &start,
+ NULL,
+ name,
+ type);
+ return result;
+}
diff --git a/source4/dns_server/dlz_minimal.h b/source4/dns_server/dlz_minimal.h
new file mode 100644
index 0000000..b7e36e7
--- /dev/null
+++ b/source4/dns_server/dlz_minimal.h
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2010 Andrew Tridgell
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all
+ * copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR
+ * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ * USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* This header is updated based on BIND 9.10.1 source.
+ * contrib/dlz/modules/include/dlz_minimal.h
+ */
+
+#ifndef DLZ_MINIMAL_H
+#define DLZ_MINIMAL_H 1
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#if defined (BIND_VERSION_9_8)
+# error Bind 9.8 is not supported!
+#elif defined (BIND_VERSION_9_9)
+# error Bind 9.9 is not supported!
+#elif defined (BIND_VERSION_9_10)
+# define DLZ_DLOPEN_VERSION 3
+# define DNS_CLIENTINFO_VERSION 1
+# define ISC_BOOLEAN_AS_BOOL 0
+#elif defined (BIND_VERSION_9_11)
+# define DLZ_DLOPEN_VERSION 3
+# define DNS_CLIENTINFO_VERSION 2
+# define ISC_BOOLEAN_AS_BOOL 0
+#elif defined (BIND_VERSION_9_12)
+# define DLZ_DLOPEN_VERSION 3
+# define DNS_CLIENTINFO_VERSION 2
+# define ISC_BOOLEAN_AS_BOOL 0
+#elif defined (BIND_VERSION_9_14)
+# define DLZ_DLOPEN_VERSION 3
+# define DNS_CLIENTINFO_VERSION 2
+#elif defined (BIND_VERSION_9_16)
+# define DLZ_DLOPEN_VERSION 3
+# define DNS_CLIENTINFO_VERSION 2
+#elif defined (BIND_VERSION_9_18)
+# define DLZ_DLOPEN_VERSION 3
+# define DNS_CLIENTINFO_VERSION 2
+#else
+# error Unsupported BIND version
+#endif
+
+#ifndef ISC_BOOLEAN_AS_BOOL
+#define ISC_BOOLEAN_AS_BOOL 1
+#endif
+
+# define DLZ_DLOPEN_AGE 0
+
+typedef unsigned int isc_result_t;
+#if ISC_BOOLEAN_AS_BOOL == 1
+typedef bool isc_boolean_t;
+#else
+typedef int isc_boolean_t;
+#endif
+typedef uint32_t dns_ttl_t;
+
+/* return these in flags from dlz_version() */
+#define DNS_SDLZFLAG_THREADSAFE 0x00000001U
+#define DNS_SDLZFLAG_RELATIVEOWNER 0x00000002U
+#define DNS_SDLZFLAG_RELATIVERDATA 0x00000004U
+
+/* result codes */
+#define ISC_R_SUCCESS 0
+#define ISC_R_NOMEMORY 1
+#define ISC_R_NOPERM 6
+#define ISC_R_NOSPACE 19
+#define ISC_R_NOTFOUND 23
+#define ISC_R_FAILURE 25
+#define ISC_R_NOTIMPLEMENTED 27
+#define ISC_R_NOMORE 29
+#define ISC_R_INVALIDFILE 30
+#define ISC_R_UNEXPECTED 34
+#define ISC_R_FILENOTFOUND 38
+
+/* boolean values */
+#if ISC_BOOLEAN_AS_BOOL == 1
+#define ISC_TRUE true
+#define ISC_FALSE false
+#else
+#define ISC_TRUE 1
+#define ISC_FALSE 0
+#endif
+
+/* log levels */
+#define ISC_LOG_INFO (-1)
+#define ISC_LOG_NOTICE (-2)
+#define ISC_LOG_WARNING (-3)
+#define ISC_LOG_ERROR (-4)
+#define ISC_LOG_CRITICAL (-5)
+#define ISC_LOG_DEBUG(level) (level)
+
+/* opaque structures */
+typedef void *dns_sdlzlookup_t;
+typedef void *dns_sdlzallnodes_t;
+typedef void *dns_view_t;
+typedef void *dns_dlzdb_t;
+
+/*
+ * Method and type definitions needed for retrieval of client info
+ * from the caller.
+ */
+typedef struct isc_sockaddr {
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ struct sockaddr_un sunix;
+ } type;
+ unsigned int length;
+ void * link;
+} isc_sockaddr_t;
+
+#if DNS_CLIENTINFO_VERSION == 1
+
+typedef struct dns_clientinfo {
+ uint16_t version;
+ void *data;
+} dns_clientinfo_t;
+
+typedef isc_result_t (*dns_clientinfo_sourceip_t)(dns_clientinfo_t *client,
+ isc_sockaddr_t **addrp);
+
+#define DNS_CLIENTINFOMETHODS_VERSION 1
+#define DNS_CLIENTINFOMETHODS_AGE 0
+
+typedef struct dns_clientinfomethods {
+ uint16_t version;
+ uint16_t age;
+ dns_clientinfo_sourceip_t sourceip;
+} dns_clientinfomethods_t;
+
+#elif DNS_CLIENTINFO_VERSION == 2
+
+typedef struct dns_clientinfo {
+ uint16_t version;
+ void *data;
+ void *dbversion;
+} dns_clientinfo_t;
+
+typedef isc_result_t (*dns_clientinfo_sourceip_t)(dns_clientinfo_t *client,
+ isc_sockaddr_t **addrp);
+
+typedef isc_result_t (*dns_clientinfo_version_t)(dns_clientinfo_t *client,
+ void **addrp);
+
+#define DNS_CLIENTINFOMETHODS_VERSION 2
+#define DNS_CLIENTINFOMETHODS_AGE 1
+
+typedef struct dns_clientinfomethods {
+ uint16_t version;
+ uint16_t age;
+ dns_clientinfo_sourceip_t sourceip;
+ dns_clientinfo_version_t dbversion;
+} dns_clientinfomethods_t;
+
+#endif /* DNS_CLIENTINFO_VERSION */
+
+
+/*
+ * Method definitions for callbacks provided by the dlopen driver
+ */
+
+typedef void log_t(int level, const char *fmt, ...);
+
+typedef isc_result_t dns_sdlz_putrr_t(dns_sdlzlookup_t *lookup,
+ const char *type,
+ dns_ttl_t ttl,
+ const char *data);
+
+typedef isc_result_t dns_sdlz_putnamedrr_t(dns_sdlzallnodes_t *allnodes,
+ const char *name,
+ const char *type,
+ dns_ttl_t ttl,
+ const char *data);
+
+typedef isc_result_t dns_dlz_writeablezone_t(dns_view_t *view,
+ dns_dlzdb_t *dlzdb,
+ const char *zone_name);
+
+
+/*
+ * prototypes for the functions you can include in your module
+ */
+
+/*
+ * dlz_version() is required for all DLZ external drivers. It should
+ * return DLZ_DLOPEN_VERSION. 'flags' is updated to indicate capabilities
+ * of the module. In particular, if the module is thread-safe then it
+ * sets 'flags' to include DNS_SDLZFLAG_THREADSAFE. Other capability
+ * flags may be added in the future.
+ */
+int
+dlz_version(unsigned int *flags);
+
+/*
+ * dlz_create() is required for all DLZ external drivers.
+ */
+isc_result_t
+dlz_create(const char *dlzname, unsigned int argc, const char *argv[],
+ void **dbdata, ...);
+
+/*
+ * dlz_destroy() is optional, and will be called when the driver is
+ * unloaded if supplied
+ */
+void
+dlz_destroy(void *dbdata);
+
+/*
+ * dlz_findzonedb is required for all DLZ external drivers
+ */
+isc_result_t
+dlz_findzonedb(void *dbdata, const char *name,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo);
+
+/*
+ * dlz_lookup is required for all DLZ external drivers
+ */
+isc_result_t
+dlz_lookup(const char *zone, const char *name, void *dbdata,
+ dns_sdlzlookup_t *lookup,
+ dns_clientinfomethods_t *methods,
+ dns_clientinfo_t *clientinfo);
+
+/*
+ * dlz_authority() is optional if dlz_lookup() supplies
+ * authority information (i.e., SOA, NS) for the dns record
+ */
+isc_result_t
+dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup);
+
+/*
+ * dlz_allowzonexfr() is optional, and should be supplied if you want to
+ * support zone transfers
+ */
+isc_result_t
+dlz_allowzonexfr(void *dbdata, const char *name, const char *client);
+
+/*
+ * dlz_allnodes() is optional, but must be supplied if supply a
+ * dlz_allowzonexfr() function
+ */
+isc_result_t
+dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes);
+
+/*
+ * dlz_newversion() is optional. It should be supplied if you want to
+ * support dynamic updates.
+ */
+isc_result_t
+dlz_newversion(const char *zone, void *dbdata, void **versionp);
+
+/*
+ * dlz_closeversion() is optional, but must be supplied if you supply a
+ * dlz_newversion() function
+ */
+void
+dlz_closeversion(const char *zone, isc_boolean_t commit, void *dbdata,
+ void **versionp);
+
+/*
+ * dlz_configure() is optional, but must be supplied if you want to support
+ * dynamic updates
+ */
+isc_result_t
+dlz_configure(dns_view_t *view, dns_dlzdb_t *dlzdb, void *dbdata);
+
+/*
+ * dlz_ssumatch() is optional, but must be supplied if you want to support
+ * dynamic updates
+ */
+isc_boolean_t
+dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr,
+ const char *type, const char *key, uint32_t keydatalen,
+ uint8_t *keydata, void *dbdata);
+
+/*
+ * dlz_addrdataset() is optional, but must be supplied if you want to
+ * support dynamic updates
+ */
+isc_result_t
+dlz_addrdataset(const char *name, const char *rdatastr, void *dbdata,
+ void *version);
+
+/*
+ * dlz_subrdataset() is optional, but must be supplied if you want to
+ * support dynamic updates
+ */
+isc_result_t
+dlz_subrdataset(const char *name, const char *rdatastr, void *dbdata,
+ void *version);
+
+/*
+ * dlz_delrdataset() is optional, but must be supplied if you want to
+ * support dynamic updates
+ */
+isc_result_t
+dlz_delrdataset(const char *name, const char *type, void *dbdata,
+ void *version);
+
+#endif /* DLZ_MINIMAL_H */
diff --git a/source4/dns_server/dns_crypto.c b/source4/dns_server/dns_crypto.c
new file mode 100644
index 0000000..be79a4e
--- /dev/null
+++ b/source4/dns_server/dns_crypto.c
@@ -0,0 +1,448 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ DNS server handler for signed packets
+
+ Copyright (C) 2012 Kai Blin <kai@samba.org>
+
+ 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 "system/network.h"
+#include "librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ndr_dns.h"
+#include "dns_server/dns_server.h"
+#include "libcli/util/ntstatus.h"
+#include "auth/auth.h"
+#include "auth/gensec/gensec.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_DNS
+
+static WERROR dns_copy_tsig(TALLOC_CTX *mem_ctx,
+ struct dns_res_rec *old,
+ struct dns_res_rec *new_rec)
+{
+ new_rec->name = talloc_strdup(mem_ctx, old->name);
+ W_ERROR_HAVE_NO_MEMORY(new_rec->name);
+
+ new_rec->rr_type = old->rr_type;
+ new_rec->rr_class = old->rr_class;
+ new_rec->ttl = old->ttl;
+ new_rec->length = old->length;
+ new_rec->rdata.tsig_record.algorithm_name = talloc_strdup(mem_ctx,
+ old->rdata.tsig_record.algorithm_name);
+ W_ERROR_HAVE_NO_MEMORY(new_rec->rdata.tsig_record.algorithm_name);
+
+ new_rec->rdata.tsig_record.time_prefix = old->rdata.tsig_record.time_prefix;
+ new_rec->rdata.tsig_record.time = old->rdata.tsig_record.time;
+ new_rec->rdata.tsig_record.fudge = old->rdata.tsig_record.fudge;
+ new_rec->rdata.tsig_record.mac_size = old->rdata.tsig_record.mac_size;
+ new_rec->rdata.tsig_record.mac = talloc_memdup(mem_ctx,
+ old->rdata.tsig_record.mac,
+ old->rdata.tsig_record.mac_size);
+ W_ERROR_HAVE_NO_MEMORY(new_rec->rdata.tsig_record.mac);
+
+ new_rec->rdata.tsig_record.original_id = old->rdata.tsig_record.original_id;
+ new_rec->rdata.tsig_record.error = old->rdata.tsig_record.error;
+ new_rec->rdata.tsig_record.other_size = old->rdata.tsig_record.other_size;
+ new_rec->rdata.tsig_record.other_data = talloc_memdup(mem_ctx,
+ old->rdata.tsig_record.other_data,
+ old->rdata.tsig_record.other_size);
+ W_ERROR_HAVE_NO_MEMORY(new_rec->rdata.tsig_record.other_data);
+
+ return WERR_OK;
+}
+
+struct dns_server_tkey *dns_find_tkey(struct dns_server_tkey_store *store,
+ const char *name)
+{
+ struct dns_server_tkey *tkey = NULL;
+ uint16_t i = 0;
+
+ do {
+ struct dns_server_tkey *tmp_key = store->tkeys[i];
+
+ i++;
+ i %= TKEY_BUFFER_SIZE;
+
+ if (tmp_key == NULL) {
+ continue;
+ }
+ if (samba_dns_name_equal(name, tmp_key->name)) {
+ tkey = tmp_key;
+ break;
+ }
+ } while (i != 0);
+
+ return tkey;
+}
+
+WERROR dns_verify_tsig(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ struct dns_request_state *state,
+ struct dns_name_packet *packet,
+ DATA_BLOB *in)
+{
+ WERROR werror;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ uint16_t i, arcount = 0;
+ DATA_BLOB tsig_blob, fake_tsig_blob, sig;
+ uint8_t *buffer = NULL;
+ size_t buffer_len = 0, packet_len = 0;
+ struct dns_server_tkey *tkey = NULL;
+ struct dns_fake_tsig_rec *check_rec = talloc_zero(mem_ctx,
+ struct dns_fake_tsig_rec);
+
+
+ /* Find the first TSIG record in the additional records */
+ for (i=0; i < packet->arcount; i++) {
+ if (packet->additional[i].rr_type == DNS_QTYPE_TSIG) {
+ break;
+ }
+ }
+
+ if (i == packet->arcount) {
+ /* no TSIG around */
+ return WERR_OK;
+ }
+
+ /* The TSIG record needs to be the last additional record */
+ if (i + 1 != packet->arcount) {
+ DEBUG(1, ("TSIG record not the last additional record!\n"));
+ return DNS_ERR(FORMAT_ERROR);
+ }
+
+ /* We got a TSIG, so we need to sign our reply */
+ state->sign = true;
+ DBG_DEBUG("Got TSIG\n");
+
+ state->tsig = talloc_zero(state->mem_ctx, struct dns_res_rec);
+ if (state->tsig == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ werror = dns_copy_tsig(state->tsig, &packet->additional[i],
+ state->tsig);
+ if (!W_ERROR_IS_OK(werror)) {
+ return werror;
+ }
+
+ packet->arcount--;
+
+ tkey = dns_find_tkey(dns->tkeys, state->tsig->name);
+ if (tkey == NULL) {
+ DBG_DEBUG("dns_find_tkey() => NOTAUTH / DNS_RCODE_BADKEY\n");
+ /*
+ * We must save the name for use in the TSIG error
+ * response and have no choice here but to save the
+ * keyname from the TSIG request.
+ */
+ state->key_name = talloc_strdup(state->mem_ctx,
+ state->tsig->name);
+ if (state->key_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ state->tsig_error = DNS_RCODE_BADKEY;
+ return DNS_ERR(NOTAUTH);
+ }
+ DBG_DEBUG("dns_find_tkey() => found\n");
+
+ /*
+ * Remember the keyname that found an existing tkey, used
+ * later to fetch the key with dns_find_tkey() when signing
+ * and adding a TSIG record with MAC.
+ */
+ state->key_name = talloc_strdup(state->mem_ctx, tkey->name);
+ if (state->key_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* FIXME: check TSIG here */
+ if (check_rec == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* first build and verify check packet */
+ check_rec->name = talloc_strdup(check_rec, tkey->name);
+ if (check_rec->name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ check_rec->rr_class = DNS_QCLASS_ANY;
+ check_rec->ttl = 0;
+ check_rec->algorithm_name = talloc_strdup(check_rec, tkey->algorithm);
+ if (check_rec->algorithm_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ check_rec->time_prefix = 0;
+ check_rec->time = state->tsig->rdata.tsig_record.time;
+ check_rec->fudge = state->tsig->rdata.tsig_record.fudge;
+ check_rec->error = 0;
+ check_rec->other_size = 0;
+ check_rec->other_data = NULL;
+
+ ndr_err = ndr_push_struct_blob(&tsig_blob, mem_ctx, state->tsig,
+ (ndr_push_flags_fn_t)ndr_push_dns_res_rec);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(1, ("Failed to push packet: %s!\n",
+ ndr_errstr(ndr_err)));
+ return DNS_ERR(SERVER_FAILURE);
+ }
+
+ ndr_err = ndr_push_struct_blob(&fake_tsig_blob, mem_ctx, check_rec,
+ (ndr_push_flags_fn_t)ndr_push_dns_fake_tsig_rec);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(1, ("Failed to push packet: %s!\n",
+ ndr_errstr(ndr_err)));
+ return DNS_ERR(SERVER_FAILURE);
+ }
+
+ /* we need to work some magic here. we need to keep the input packet
+ * exactly like we got it, but we need to cut off the tsig record */
+ packet_len = in->length - tsig_blob.length;
+ buffer_len = packet_len + fake_tsig_blob.length;
+ buffer = talloc_zero_array(mem_ctx, uint8_t, buffer_len);
+ if (buffer == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ memcpy(buffer, in->data, packet_len);
+ memcpy(buffer + packet_len, fake_tsig_blob.data, fake_tsig_blob.length);
+
+ sig.length = state->tsig->rdata.tsig_record.mac_size;
+ sig.data = talloc_memdup(mem_ctx, state->tsig->rdata.tsig_record.mac, sig.length);
+ if (sig.data == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* Now we also need to count down the additional record counter */
+ arcount = RSVAL(buffer, 10);
+ RSSVAL(buffer, 10, arcount-1);
+
+ status = gensec_check_packet(tkey->gensec, buffer, buffer_len,
+ buffer, buffer_len, &sig);
+ if (NT_STATUS_EQUAL(NT_STATUS_ACCESS_DENIED, status)) {
+ dump_data_dbgc(DBGC_DNS, 8, sig.data, sig.length);
+ dump_data_dbgc(DBGC_DNS, 8, buffer, buffer_len);
+ DBG_NOTICE("Verifying tsig failed: %s\n", nt_errstr(status));
+ state->tsig_error = DNS_RCODE_BADSIG;
+ return DNS_ERR(NOTAUTH);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ dump_data_dbgc(DBGC_DNS, 8, sig.data, sig.length);
+ dump_data_dbgc(DBGC_DNS, 8, buffer, buffer_len);
+ DEBUG(1, ("Verifying tsig failed: %s\n", nt_errstr(status)));
+ return ntstatus_to_werror(status);
+ }
+
+ state->authenticated = true;
+
+ DBG_DEBUG("AUTHENTICATED\n");
+ return WERR_OK;
+}
+
+static WERROR dns_tsig_compute_mac(TALLOC_CTX *mem_ctx,
+ struct dns_request_state *state,
+ struct dns_name_packet *packet,
+ struct dns_server_tkey *tkey,
+ time_t current_time,
+ DATA_BLOB *_psig)
+{
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB packet_blob, tsig_blob, sig;
+ uint8_t *buffer = NULL;
+ uint8_t *p = NULL;
+ size_t buffer_len = 0;
+ struct dns_fake_tsig_rec *check_rec = talloc_zero(mem_ctx,
+ struct dns_fake_tsig_rec);
+ size_t mac_size = 0;
+
+ if (check_rec == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* first build and verify check packet */
+ check_rec->name = talloc_strdup(check_rec, tkey->name);
+ if (check_rec->name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ check_rec->rr_class = DNS_QCLASS_ANY;
+ check_rec->ttl = 0;
+ check_rec->algorithm_name = talloc_strdup(check_rec, tkey->algorithm);
+ if (check_rec->algorithm_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ check_rec->time_prefix = 0;
+ check_rec->time = current_time;
+ check_rec->fudge = 300;
+ check_rec->error = state->tsig_error;
+ check_rec->other_size = 0;
+ check_rec->other_data = NULL;
+
+ ndr_err = ndr_push_struct_blob(&packet_blob, mem_ctx, packet,
+ (ndr_push_flags_fn_t)ndr_push_dns_name_packet);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(1, ("Failed to push packet: %s!\n",
+ ndr_errstr(ndr_err)));
+ return DNS_ERR(SERVER_FAILURE);
+ }
+
+ ndr_err = ndr_push_struct_blob(&tsig_blob, mem_ctx, check_rec,
+ (ndr_push_flags_fn_t)ndr_push_dns_fake_tsig_rec);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(1, ("Failed to push packet: %s!\n",
+ ndr_errstr(ndr_err)));
+ return DNS_ERR(SERVER_FAILURE);
+ }
+
+ if (state->tsig != NULL) {
+ mac_size = state->tsig->rdata.tsig_record.mac_size;
+ }
+
+ buffer_len = mac_size;
+
+ buffer_len += packet_blob.length;
+ if (buffer_len < packet_blob.length) {
+ return WERR_INVALID_PARAMETER;
+ }
+ buffer_len += tsig_blob.length;
+ if (buffer_len < tsig_blob.length) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ buffer = talloc_zero_array(mem_ctx, uint8_t, buffer_len);
+ if (buffer == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ p = buffer;
+
+ /*
+ * RFC 2845 "4.2 TSIG on Answers", how to lay out the buffer
+ * that we're going to sign:
+ * 1. MAC of request (if present)
+ * 2. Outgoing packet
+ * 3. TSIG record
+ */
+ if (mac_size > 0) {
+ memcpy(p, state->tsig->rdata.tsig_record.mac, mac_size);
+ p += mac_size;
+ }
+
+ memcpy(p, packet_blob.data, packet_blob.length);
+ p += packet_blob.length;
+
+ memcpy(p, tsig_blob.data, tsig_blob.length);
+
+ status = gensec_sign_packet(tkey->gensec, mem_ctx, buffer, buffer_len,
+ buffer, buffer_len, &sig);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ *_psig = sig;
+ return WERR_OK;
+}
+
+WERROR dns_sign_tsig(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ struct dns_request_state *state,
+ struct dns_name_packet *packet,
+ uint16_t error)
+{
+ WERROR werror;
+ time_t current_time = time(NULL);
+ struct dns_res_rec *tsig = NULL;
+ DATA_BLOB sig = (DATA_BLOB) {
+ .data = NULL,
+ .length = 0
+ };
+
+ tsig = talloc_zero(mem_ctx, struct dns_res_rec);
+ if (tsig == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (state->tsig_error == DNS_RCODE_OK) {
+ struct dns_server_tkey *tkey = dns_find_tkey(
+ dns->tkeys, state->key_name);
+ if (tkey == NULL) {
+ DBG_WARNING("dns_find_tkey() => NULL)\n");
+ return DNS_ERR(SERVER_FAILURE);
+ }
+
+ werror = dns_tsig_compute_mac(mem_ctx, state, packet,
+ tkey, current_time, &sig);
+ DBG_DEBUG("dns_tsig_compute_mac() => %s\n", win_errstr(werror));
+ if (!W_ERROR_IS_OK(werror)) {
+ return werror;
+ }
+ }
+
+ tsig->name = talloc_strdup(tsig, state->key_name);
+ if (tsig->name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ tsig->rr_class = DNS_QCLASS_ANY;
+ tsig->rr_type = DNS_QTYPE_TSIG;
+ tsig->ttl = 0;
+ tsig->length = UINT16_MAX;
+ tsig->rdata.tsig_record.algorithm_name = talloc_strdup(tsig, "gss-tsig");
+ if (tsig->rdata.tsig_record.algorithm_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ tsig->rdata.tsig_record.time_prefix = 0;
+ tsig->rdata.tsig_record.time = current_time;
+ tsig->rdata.tsig_record.fudge = 300;
+ tsig->rdata.tsig_record.error = state->tsig_error;
+ tsig->rdata.tsig_record.original_id = packet->id;
+ tsig->rdata.tsig_record.other_size = 0;
+ tsig->rdata.tsig_record.other_data = NULL;
+ if (sig.length > 0) {
+ tsig->rdata.tsig_record.mac_size = sig.length;
+ tsig->rdata.tsig_record.mac = talloc_memdup(tsig, sig.data, sig.length);
+ if (tsig->rdata.tsig_record.mac == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ DBG_DEBUG("sig.length=%zu\n", sig.length);
+
+ if (packet->arcount == 0) {
+ packet->additional = talloc_zero(mem_ctx, struct dns_res_rec);
+ if (packet->additional == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+ packet->additional = talloc_realloc(mem_ctx, packet->additional,
+ struct dns_res_rec,
+ packet->arcount + 1);
+ if (packet->additional == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ werror = dns_copy_tsig(mem_ctx, tsig,
+ &packet->additional[packet->arcount]);
+ DBG_DEBUG("dns_copy_tsig() => %s\n", win_errstr(werror));
+ if (!W_ERROR_IS_OK(werror)) {
+ return werror;
+ }
+ packet->arcount++;
+
+ return WERR_OK;
+}
diff --git a/source4/dns_server/dns_query.c b/source4/dns_server/dns_query.c
new file mode 100644
index 0000000..181beda
--- /dev/null
+++ b/source4/dns_server/dns_query.c
@@ -0,0 +1,1176 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ DNS server handler for queries
+
+ Copyright (C) 2010 Kai Blin <kai@samba.org>
+
+ 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 "samba/service_task.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 "param/param.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
+#include "dns_server/dns_server.h"
+#include "libcli/dns/libdns.h"
+#include "lib/util/dlinklist.h"
+#include "lib/util/util_net.h"
+#include "lib/util/tevent_werror.h"
+#include "auth/auth.h"
+#include "auth/credentials/credentials.h"
+#include "auth/gensec/gensec.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_DNS
+#define MAX_Q_RECURSION_DEPTH 20
+
+struct forwarder_string {
+ const char *forwarder;
+ struct forwarder_string *prev, *next;
+};
+
+static WERROR add_response_rr(const char *name,
+ const struct dnsp_DnssrvRpcRecord *rec,
+ struct dns_res_rec **answers)
+{
+ struct dns_res_rec *ans = *answers;
+ uint16_t ai = talloc_array_length(ans);
+ enum ndr_err_code ndr_err;
+
+ if (ai == UINT16_MAX) {
+ return WERR_BUFFER_OVERFLOW;
+ }
+
+ /*
+ * "ans" is always non-NULL and thus its own talloc context
+ */
+ ans = talloc_realloc(ans, ans, struct dns_res_rec, ai+1);
+ if (ans == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(ans[ai]);
+
+ switch (rec->wType) {
+ case DNS_QTYPE_CNAME:
+ ans[ai].rdata.cname_record = talloc_strdup(ans, rec->data.cname);
+ W_ERROR_HAVE_NO_MEMORY(ans[ai].rdata.cname_record);
+ break;
+ case DNS_QTYPE_A:
+ ans[ai].rdata.ipv4_record = talloc_strdup(ans, rec->data.ipv4);
+ W_ERROR_HAVE_NO_MEMORY(ans[ai].rdata.ipv4_record);
+ break;
+ case DNS_QTYPE_AAAA:
+ ans[ai].rdata.ipv6_record = talloc_strdup(ans, rec->data.ipv6);
+ W_ERROR_HAVE_NO_MEMORY(ans[ai].rdata.ipv6_record);
+ break;
+ case DNS_TYPE_NS:
+ ans[ai].rdata.ns_record = talloc_strdup(ans, rec->data.ns);
+ W_ERROR_HAVE_NO_MEMORY(ans[ai].rdata.ns_record);
+ break;
+ case DNS_QTYPE_SRV:
+ ans[ai].rdata.srv_record.priority = rec->data.srv.wPriority;
+ ans[ai].rdata.srv_record.weight = rec->data.srv.wWeight;
+ ans[ai].rdata.srv_record.port = rec->data.srv.wPort;
+ ans[ai].rdata.srv_record.target = talloc_strdup(
+ ans, rec->data.srv.nameTarget);
+ W_ERROR_HAVE_NO_MEMORY(ans[ai].rdata.srv_record.target);
+ break;
+ case DNS_QTYPE_SOA:
+ ans[ai].rdata.soa_record.mname = talloc_strdup(
+ ans, rec->data.soa.mname);
+ W_ERROR_HAVE_NO_MEMORY(ans[ai].rdata.soa_record.mname);
+ ans[ai].rdata.soa_record.rname = talloc_strdup(
+ ans, rec->data.soa.rname);
+ W_ERROR_HAVE_NO_MEMORY(ans[ai].rdata.soa_record.rname);
+ ans[ai].rdata.soa_record.serial = rec->data.soa.serial;
+ ans[ai].rdata.soa_record.refresh = rec->data.soa.refresh;
+ ans[ai].rdata.soa_record.retry = rec->data.soa.retry;
+ ans[ai].rdata.soa_record.expire = rec->data.soa.expire;
+ ans[ai].rdata.soa_record.minimum = rec->data.soa.minimum;
+ break;
+ case DNS_QTYPE_PTR:
+ ans[ai].rdata.ptr_record = talloc_strdup(ans, rec->data.ptr);
+ W_ERROR_HAVE_NO_MEMORY(ans[ai].rdata.ptr_record);
+ break;
+ case DNS_QTYPE_MX:
+ ans[ai].rdata.mx_record.preference = rec->data.mx.wPriority;
+ ans[ai].rdata.mx_record.exchange = talloc_strdup(
+ ans, rec->data.mx.nameTarget);
+ if (ans[ai].rdata.mx_record.exchange == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ case DNS_QTYPE_TXT:
+ ndr_err = ndr_dnsp_string_list_copy(ans,
+ &rec->data.txt,
+ &ans[ai].rdata.txt_record.txt);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ default:
+ DEBUG(0, ("Got unhandled type %u query.\n", rec->wType));
+ return DNS_ERR(NOT_IMPLEMENTED);
+ }
+
+ ans[ai].name = talloc_strdup(ans, name);
+ W_ERROR_HAVE_NO_MEMORY(ans[ai].name);
+ ans[ai].rr_type = (enum dns_qtype)rec->wType;
+ ans[ai].rr_class = DNS_QCLASS_IN;
+ ans[ai].ttl = rec->dwTtlSeconds;
+ ans[ai].length = UINT16_MAX;
+
+ *answers = ans;
+
+ return WERR_OK;
+}
+
+static WERROR add_dns_res_rec(struct dns_res_rec **pdst,
+ const struct dns_res_rec *src)
+{
+ struct dns_res_rec *dst = *pdst;
+ uint16_t di = talloc_array_length(dst);
+ enum ndr_err_code ndr_err;
+
+ if (di == UINT16_MAX) {
+ return WERR_BUFFER_OVERFLOW;
+ }
+
+ dst = talloc_realloc(dst, dst, struct dns_res_rec, di+1);
+ if (dst == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(dst[di]);
+
+ dst[di] = (struct dns_res_rec) {
+ .name = talloc_strdup(dst, src->name),
+ .rr_type = src->rr_type,
+ .rr_class = src->rr_class,
+ .ttl = src->ttl,
+ .length = src->length
+ };
+
+ if (dst[di].name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ switch (src->rr_type) {
+ case DNS_QTYPE_CNAME:
+ dst[di].rdata.cname_record = talloc_strdup(
+ dst, src->rdata.cname_record);
+ if (dst[di].rdata.cname_record == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ case DNS_QTYPE_A:
+ dst[di].rdata.ipv4_record = talloc_strdup(
+ dst, src->rdata.ipv4_record);
+ if (dst[di].rdata.ipv4_record == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ case DNS_QTYPE_AAAA:
+ dst[di].rdata.ipv6_record = talloc_strdup(
+ dst, src->rdata.ipv6_record);
+ if (dst[di].rdata.ipv6_record == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ case DNS_TYPE_NS:
+ dst[di].rdata.ns_record = talloc_strdup(
+ dst, src->rdata.ns_record);
+ if (dst[di].rdata.ns_record == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ case DNS_QTYPE_SRV:
+ dst[di].rdata.srv_record = (struct dns_srv_record) {
+ .priority = src->rdata.srv_record.priority,
+ .weight = src->rdata.srv_record.weight,
+ .port = src->rdata.srv_record.port,
+ .target = talloc_strdup(
+ dst, src->rdata.srv_record.target)
+ };
+ if (dst[di].rdata.srv_record.target == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ case DNS_QTYPE_SOA:
+ dst[di].rdata.soa_record = (struct dns_soa_record) {
+ .mname = talloc_strdup(
+ dst, src->rdata.soa_record.mname),
+ .rname = talloc_strdup(
+ dst, src->rdata.soa_record.rname),
+ .serial = src->rdata.soa_record.serial,
+ .refresh = src->rdata.soa_record.refresh,
+ .retry = src->rdata.soa_record.retry,
+ .expire = src->rdata.soa_record.expire,
+ .minimum = src->rdata.soa_record.minimum
+ };
+
+ if ((dst[di].rdata.soa_record.mname == NULL) ||
+ (dst[di].rdata.soa_record.rname == NULL)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ break;
+ case DNS_QTYPE_PTR:
+ dst[di].rdata.ptr_record = talloc_strdup(
+ dst, src->rdata.ptr_record);
+ if (dst[di].rdata.ptr_record == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ case DNS_QTYPE_MX:
+ dst[di].rdata.mx_record = (struct dns_mx_record) {
+ .preference = src->rdata.mx_record.preference,
+ .exchange = talloc_strdup(
+ src, src->rdata.mx_record.exchange)
+ };
+
+ if (dst[di].rdata.mx_record.exchange == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ case DNS_QTYPE_TXT:
+ ndr_err = ndr_dnsp_string_list_copy(dst,
+ &src->rdata.txt_record.txt,
+ &dst[di].rdata.txt_record.txt);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ default:
+ DBG_WARNING("Got unhandled type %u query.\n", src->rr_type);
+ return DNS_ERR(NOT_IMPLEMENTED);
+ }
+
+ *pdst = dst;
+
+ return WERR_OK;
+}
+
+struct ask_forwarder_state {
+ struct dns_name_packet *reply;
+};
+
+static void ask_forwarder_done(struct tevent_req *subreq);
+
+static struct tevent_req *ask_forwarder_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *forwarder, struct dns_name_question *question)
+{
+ struct tevent_req *req, *subreq;
+ struct ask_forwarder_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct ask_forwarder_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = dns_cli_request_send(state, ev, forwarder,
+ question->name, question->question_class,
+ question->question_type);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, ask_forwarder_done, req);
+ return req;
+}
+
+static void ask_forwarder_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct ask_forwarder_state *state = tevent_req_data(
+ req, struct ask_forwarder_state);
+ int ret;
+
+ ret = dns_cli_request_recv(subreq, state, &state->reply);
+ TALLOC_FREE(subreq);
+
+ if (ret != 0) {
+ tevent_req_werror(req, unix_to_werror(ret));
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static WERROR ask_forwarder_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct dns_res_rec **answers, uint16_t *ancount,
+ struct dns_res_rec **nsrecs, uint16_t *nscount,
+ struct dns_res_rec **additional, uint16_t *arcount)
+{
+ struct ask_forwarder_state *state = tevent_req_data(
+ req, struct ask_forwarder_state);
+ struct dns_name_packet *in_packet = state->reply;
+ WERROR err;
+
+ if (tevent_req_is_werror(req, &err)) {
+ return err;
+ }
+
+ *ancount = in_packet->ancount;
+ *answers = talloc_move(mem_ctx, &in_packet->answers);
+
+ *nscount = in_packet->nscount;
+ *nsrecs = talloc_move(mem_ctx, &in_packet->nsrecs);
+
+ *arcount = in_packet->arcount;
+ *additional = talloc_move(mem_ctx, &in_packet->additional);
+
+ return WERR_OK;
+}
+
+static WERROR add_zone_authority_record(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ const struct dns_name_question *question,
+ struct dns_res_rec **nsrecs)
+{
+ const char *zone = NULL;
+ struct dnsp_DnssrvRpcRecord *recs;
+ struct dns_res_rec *ns = *nsrecs;
+ uint16_t rec_count;
+ struct ldb_dn *dn = NULL;
+ unsigned int ri;
+ WERROR werror;
+
+ zone = dns_get_authoritative_zone(dns, question->name);
+ DEBUG(10, ("Creating zone authority record for '%s'\n", zone));
+
+ werror = dns_name2dn(dns, mem_ctx, zone, &dn);
+ if (!W_ERROR_IS_OK(werror)) {
+ return werror;
+ }
+
+ werror = dns_lookup_records(dns, mem_ctx, dn, &recs, &rec_count);
+ if (!W_ERROR_IS_OK(werror)) {
+ return werror;
+ }
+
+ for (ri = 0; ri < rec_count; ri++) {
+ if (recs[ri].wType == DNS_TYPE_SOA) {
+ werror = add_response_rr(zone, &recs[ri], &ns);
+ if (!W_ERROR_IS_OK(werror)) {
+ return werror;
+ }
+ }
+ }
+
+ *nsrecs = ns;
+
+ return WERR_OK;
+}
+
+static struct tevent_req *handle_authoritative_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct dns_server *dns, const char *forwarder,
+ struct dns_name_question *question,
+ struct dns_res_rec **answers, struct dns_res_rec **nsrecs,
+ size_t cname_depth);
+static WERROR handle_authoritative_recv(struct tevent_req *req);
+
+struct handle_dnsrpcrec_state {
+ struct dns_res_rec **answers;
+ struct dns_res_rec **nsrecs;
+};
+
+static void handle_dnsrpcrec_gotauth(struct tevent_req *subreq);
+static void handle_dnsrpcrec_gotforwarded(struct tevent_req *subreq);
+
+static struct tevent_req *handle_dnsrpcrec_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct dns_server *dns, const char *forwarder,
+ const struct dns_name_question *question,
+ struct dnsp_DnssrvRpcRecord *rec,
+ struct dns_res_rec **answers, struct dns_res_rec **nsrecs,
+ size_t cname_depth)
+{
+ struct tevent_req *req, *subreq;
+ struct handle_dnsrpcrec_state *state;
+ struct dns_name_question *new_q;
+ bool resolve_cname;
+ WERROR werr;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct handle_dnsrpcrec_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->answers = answers;
+ state->nsrecs = nsrecs;
+
+ if (cname_depth >= MAX_Q_RECURSION_DEPTH) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ resolve_cname = ((rec->wType == DNS_TYPE_CNAME) &&
+ ((question->question_type == DNS_QTYPE_A) ||
+ (question->question_type == DNS_QTYPE_AAAA)));
+
+ if (!resolve_cname) {
+ if ((question->question_type != DNS_QTYPE_ALL) &&
+ (rec->wType !=
+ (enum dns_record_type) question->question_type)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ werr = add_response_rr(question->name, rec, state->answers);
+ if (tevent_req_werror(req, werr)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ werr = add_response_rr(question->name, rec, state->answers);
+ if (tevent_req_werror(req, werr)) {
+ return tevent_req_post(req, ev);
+ }
+
+ new_q = talloc(state, struct dns_name_question);
+ if (tevent_req_nomem(new_q, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ *new_q = (struct dns_name_question) {
+ .question_type = question->question_type,
+ .question_class = question->question_class,
+ .name = rec->data.cname
+ };
+
+ if (dns_authoritative_for_zone(dns, new_q->name)) {
+ subreq = handle_authoritative_send(
+ state, ev, dns, forwarder, new_q,
+ state->answers, state->nsrecs,
+ cname_depth + 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, handle_dnsrpcrec_gotauth, req);
+ return req;
+ }
+
+ subreq = ask_forwarder_send(state, ev, forwarder, new_q);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, handle_dnsrpcrec_gotforwarded, req);
+
+ return req;
+}
+
+static void handle_dnsrpcrec_gotauth(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ WERROR werr;
+
+ werr = handle_authoritative_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_werror(req, werr)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void handle_dnsrpcrec_gotforwarded(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct handle_dnsrpcrec_state *state = tevent_req_data(
+ req, struct handle_dnsrpcrec_state);
+ struct dns_res_rec *answers, *nsrecs, *additional;
+ uint16_t ancount = 0;
+ uint16_t nscount = 0;
+ uint16_t arcount = 0;
+ uint16_t i;
+ WERROR werr;
+
+ werr = ask_forwarder_recv(subreq, state, &answers, &ancount,
+ &nsrecs, &nscount, &additional, &arcount);
+ if (tevent_req_werror(req, werr)) {
+ return;
+ }
+
+ for (i=0; i<ancount; i++) {
+ werr = add_dns_res_rec(state->answers, &answers[i]);
+ if (tevent_req_werror(req, werr)) {
+ return;
+ }
+ }
+
+ for (i=0; i<nscount; i++) {
+ werr = add_dns_res_rec(state->nsrecs, &nsrecs[i]);
+ if (tevent_req_werror(req, werr)) {
+ return;
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+static WERROR handle_dnsrpcrec_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_werror(req);
+}
+
+struct handle_authoritative_state {
+ struct tevent_context *ev;
+ struct dns_server *dns;
+ struct dns_name_question *question;
+ const char *forwarder;
+
+ struct dnsp_DnssrvRpcRecord *recs;
+ uint16_t rec_count;
+ uint16_t recs_done;
+
+ struct dns_res_rec **answers;
+ struct dns_res_rec **nsrecs;
+
+ size_t cname_depth;
+};
+
+static void handle_authoritative_done(struct tevent_req *subreq);
+
+static struct tevent_req *handle_authoritative_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct dns_server *dns, const char *forwarder,
+ struct dns_name_question *question,
+ struct dns_res_rec **answers, struct dns_res_rec **nsrecs,
+ size_t cname_depth)
+{
+ struct tevent_req *req, *subreq;
+ struct handle_authoritative_state *state;
+ struct ldb_dn *dn = NULL;
+ WERROR werr;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct handle_authoritative_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->dns = dns;
+ state->question = question;
+ state->forwarder = forwarder;
+ state->answers = answers;
+ state->nsrecs = nsrecs;
+ state->cname_depth = cname_depth;
+
+ werr = dns_name2dn(dns, state, question->name, &dn);
+ if (tevent_req_werror(req, werr)) {
+ return tevent_req_post(req, ev);
+ }
+ werr = dns_lookup_records_wildcard(dns, state, dn, &state->recs,
+ &state->rec_count);
+ TALLOC_FREE(dn);
+ if (tevent_req_werror(req, werr)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->rec_count == 0) {
+ tevent_req_werror(req, DNS_ERR(NAME_ERROR));
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = handle_dnsrpcrec_send(
+ state, state->ev, state->dns, state->forwarder,
+ state->question, &state->recs[state->recs_done],
+ state->answers, state->nsrecs,
+ state->cname_depth);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, handle_authoritative_done, req);
+ return req;
+}
+
+static void handle_authoritative_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct handle_authoritative_state *state = tevent_req_data(
+ req, struct handle_authoritative_state);
+ WERROR werr;
+
+ werr = handle_dnsrpcrec_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_werror(req, werr)) {
+ return;
+ }
+
+ state->recs_done += 1;
+
+ if (state->recs_done == state->rec_count) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = handle_dnsrpcrec_send(
+ state, state->ev, state->dns, state->forwarder,
+ state->question, &state->recs[state->recs_done],
+ state->answers, state->nsrecs,
+ state->cname_depth);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, handle_authoritative_done, req);
+}
+
+static WERROR handle_authoritative_recv(struct tevent_req *req)
+{
+ WERROR werr;
+
+ if (tevent_req_is_werror(req, &werr)) {
+ return werr;
+ }
+
+ return WERR_OK;
+}
+
+static NTSTATUS create_tkey(struct dns_server *dns,
+ const char* name,
+ const char* algorithm,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ struct dns_server_tkey **tkey)
+{
+ NTSTATUS status;
+ struct dns_server_tkey_store *store = dns->tkeys;
+ struct dns_server_tkey *k = talloc_zero(store, struct dns_server_tkey);
+
+ if (k == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ k->name = talloc_strdup(k, name);
+
+ if (k->name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ k->algorithm = talloc_strdup(k, algorithm);
+ if (k->algorithm == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * We only allow SPNEGO/KRB5 currently
+ * and rely on the backend to be RPC/IPC free.
+ *
+ * It allows gensec_update() not to block.
+ */
+ status = samba_server_gensec_krb5_start(k,
+ dns->task->event_ctx,
+ dns->task->msg_ctx,
+ dns->task->lp_ctx,
+ dns->server_credentials,
+ "dns",
+ &k->gensec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to start GENSEC server code: %s\n", nt_errstr(status)));
+ *tkey = NULL;
+ return status;
+ }
+
+ gensec_want_feature(k->gensec, GENSEC_FEATURE_SIGN);
+
+ status = gensec_set_remote_address(k->gensec,
+ remote_address);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to set remote address into GENSEC: %s\n",
+ nt_errstr(status)));
+ *tkey = NULL;
+ return status;
+ }
+
+ status = gensec_set_local_address(k->gensec,
+ local_address);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to set local address into GENSEC: %s\n",
+ nt_errstr(status)));
+ *tkey = NULL;
+ return status;
+ }
+
+ status = gensec_start_mech_by_oid(k->gensec, GENSEC_OID_SPNEGO);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to start GENSEC server code: %s\n",
+ nt_errstr(status)));
+ *tkey = NULL;
+ return status;
+ }
+
+ TALLOC_FREE(store->tkeys[store->next_idx]);
+
+ store->tkeys[store->next_idx] = k;
+ (store->next_idx)++;
+ store->next_idx %= store->size;
+
+ *tkey = k;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS accept_gss_ticket(TALLOC_CTX *mem_ctx,
+ struct dns_server *dns,
+ struct dns_server_tkey *tkey,
+ const DATA_BLOB *key,
+ DATA_BLOB *reply,
+ uint16_t *dns_auth_error)
+{
+ NTSTATUS status;
+
+ /*
+ * We use samba_server_gensec_krb5_start(),
+ * which only allows SPNEGO/KRB5 currently
+ * and makes sure the backend to be RPC/IPC free.
+ *
+ * See gensec_gssapi_update_internal() as
+ * GENSEC_SERVER.
+ *
+ * It allows gensec_update() not to block.
+ *
+ * If that changes in future we need to use
+ * gensec_update_send/recv here!
+ */
+ status = gensec_update(tkey->gensec, mem_ctx,
+ *key, reply);
+
+ if (NT_STATUS_EQUAL(NT_STATUS_MORE_PROCESSING_REQUIRED, status)) {
+ *dns_auth_error = DNS_RCODE_OK;
+ return status;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+
+ status = gensec_session_info(tkey->gensec, tkey, &tkey->session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ *dns_auth_error = DNS_RCODE_BADKEY;
+ return status;
+ }
+ *dns_auth_error = DNS_RCODE_OK;
+ }
+
+ return status;
+}
+
+static WERROR handle_tkey(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ const struct dns_name_packet *in,
+ struct dns_request_state *state,
+ struct dns_res_rec **answers,
+ uint16_t *ancount)
+{
+ struct dns_res_rec *in_tkey = NULL;
+ struct dns_res_rec *ret_tkey;
+ uint16_t i;
+
+ for (i = 0; i < in->arcount; i++) {
+ if (in->additional[i].rr_type == DNS_QTYPE_TKEY) {
+ in_tkey = &in->additional[i];
+ break;
+ }
+ }
+
+ /* If this is a TKEY query, it should have a TKEY RR.
+ * Behaviour is not really specified in RFC 2930 or RFC 3645, but
+ * FORMAT_ERROR seems to be what BIND uses .*/
+ if (in_tkey == NULL) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+
+ ret_tkey = talloc_zero(mem_ctx, struct dns_res_rec);
+ if (ret_tkey == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ret_tkey->name = talloc_strdup(ret_tkey, in_tkey->name);
+ if (ret_tkey->name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ret_tkey->rr_type = DNS_QTYPE_TKEY;
+ ret_tkey->rr_class = DNS_QCLASS_ANY;
+ ret_tkey->length = UINT16_MAX;
+
+ ret_tkey->rdata.tkey_record.algorithm = talloc_strdup(ret_tkey,
+ in_tkey->rdata.tkey_record.algorithm);
+ if (ret_tkey->rdata.tkey_record.algorithm == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ret_tkey->rdata.tkey_record.inception = in_tkey->rdata.tkey_record.inception;
+ ret_tkey->rdata.tkey_record.expiration = in_tkey->rdata.tkey_record.expiration;
+ ret_tkey->rdata.tkey_record.mode = in_tkey->rdata.tkey_record.mode;
+
+ switch (in_tkey->rdata.tkey_record.mode) {
+ case DNS_TKEY_MODE_DH:
+ /* FIXME: According to RFC 2930, we MUST support this, but we don't.
+ * Still, claim it's a bad key instead of a bad mode */
+ ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADKEY;
+ break;
+ case DNS_TKEY_MODE_GSSAPI: {
+ NTSTATUS status;
+ struct dns_server_tkey *tkey;
+ DATA_BLOB key;
+ DATA_BLOB reply;
+
+ tkey = dns_find_tkey(dns->tkeys, in->questions[0].name);
+ if (tkey != NULL && tkey->complete) {
+ /* TODO: check if the key is still valid */
+ DEBUG(1, ("Rejecting tkey negotiation for already established key\n"));
+ ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADNAME;
+ break;
+ }
+
+ if (tkey == NULL) {
+ status = create_tkey(dns, in->questions[0].name,
+ in_tkey->rdata.tkey_record.algorithm,
+ state->remote_address,
+ state->local_address,
+ &tkey);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADKEY;
+ return ntstatus_to_werror(status);
+ }
+ }
+
+ key.data = in_tkey->rdata.tkey_record.key_data;
+ key.length = in_tkey->rdata.tkey_record.key_size;
+
+ status = accept_gss_ticket(ret_tkey, dns, tkey, &key, &reply,
+ &ret_tkey->rdata.tkey_record.error);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ DEBUG(1, ("More processing required\n"));
+ ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADKEY;
+ } else if (NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Tkey handshake completed\n");
+ ret_tkey->rdata.tkey_record.key_size = reply.length;
+ ret_tkey->rdata.tkey_record.key_data = talloc_memdup(ret_tkey,
+ reply.data,
+ reply.length);
+ if (ret_tkey->rdata.tkey_record.key_data == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ state->sign = true;
+ state->key_name = talloc_strdup(state->mem_ctx, tkey->name);
+ if (state->key_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ } else {
+ DEBUG(1, ("GSS key negotiation returned %s\n", nt_errstr(status)));
+ ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADKEY;
+ }
+
+ break;
+ }
+ case DNS_TKEY_MODE_DELETE:
+ /* TODO: implement me */
+ DEBUG(1, ("Should delete tkey here\n"));
+ ret_tkey->rdata.tkey_record.error = DNS_RCODE_OK;
+ break;
+ case DNS_TKEY_MODE_NULL:
+ case DNS_TKEY_MODE_SERVER:
+ case DNS_TKEY_MODE_CLIENT:
+ case DNS_TKEY_MODE_LAST:
+ /* We don't have to implement these, return a mode error */
+ ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADMODE;
+ break;
+ default:
+ DEBUG(1, ("Unsupported TKEY mode %d\n",
+ in_tkey->rdata.tkey_record.mode));
+ }
+
+ *answers = ret_tkey;
+ *ancount = 1;
+
+ return WERR_OK;
+}
+
+struct dns_server_process_query_state {
+ struct tevent_context *ev;
+ struct dns_server *dns;
+ struct dns_name_question *question;
+
+ struct dns_res_rec *answers;
+ uint16_t ancount;
+ struct dns_res_rec *nsrecs;
+ uint16_t nscount;
+ struct dns_res_rec *additional;
+ uint16_t arcount;
+ struct forwarder_string *forwarders;
+};
+
+static void dns_server_process_query_got_auth(struct tevent_req *subreq);
+static void dns_server_process_query_got_response(struct tevent_req *subreq);
+
+struct tevent_req *dns_server_process_query_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct dns_server *dns, struct dns_request_state *req_state,
+ const struct dns_name_packet *in)
+{
+ struct tevent_req *req, *subreq;
+ struct dns_server_process_query_state *state;
+ const char **forwarders = NULL;
+ unsigned int i;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct dns_server_process_query_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ if (in->qdcount != 1) {
+ tevent_req_werror(req, DNS_ERR(FORMAT_ERROR));
+ return tevent_req_post(req, ev);
+ }
+
+ /* Windows returns NOT_IMPLEMENTED on this as well */
+ if (in->questions[0].question_class == DNS_QCLASS_NONE) {
+ tevent_req_werror(req, DNS_ERR(NOT_IMPLEMENTED));
+ return tevent_req_post(req, ev);
+ }
+
+ if (in->questions[0].question_type == DNS_QTYPE_TKEY) {
+ WERROR err;
+
+ err = handle_tkey(dns, state, in, req_state,
+ &state->answers, &state->ancount);
+ if (tevent_req_werror(req, err)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ state->dns = dns;
+ state->ev = ev;
+ state->question = &in->questions[0];
+
+ forwarders = lpcfg_dns_forwarder(dns->task->lp_ctx);
+ for (i = 0; forwarders != NULL && forwarders[i] != NULL; i++) {
+ struct forwarder_string *f = talloc_zero(state,
+ struct forwarder_string);
+ f->forwarder = forwarders[i];
+ DLIST_ADD_END(state->forwarders, f);
+ }
+
+ if (dns_authoritative_for_zone(dns, in->questions[0].name)) {
+
+ req_state->flags |= DNS_FLAG_AUTHORITATIVE;
+
+ /*
+ * Initialize the response arrays, so that we can use
+ * them as their own talloc contexts when doing the
+ * realloc
+ */
+ state->answers = talloc_array(state, struct dns_res_rec, 0);
+ if (tevent_req_nomem(state->answers, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->nsrecs = talloc_array(state, struct dns_res_rec, 0);
+ if (tevent_req_nomem(state->nsrecs, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = handle_authoritative_send(
+ state, ev, dns, (forwarders == NULL ? NULL : forwarders[0]),
+ &in->questions[0], &state->answers, &state->nsrecs,
+ 0); /* cname_depth */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, dns_server_process_query_got_auth, req);
+ return req;
+ }
+
+ if ((req_state->flags & DNS_FLAG_RECURSION_DESIRED) &&
+ (req_state->flags & DNS_FLAG_RECURSION_AVAIL)) {
+ DEBUG(5, ("Not authoritative for '%s', forwarding\n",
+ in->questions[0].name));
+
+ subreq = ask_forwarder_send(state, ev,
+ (forwarders == NULL ? NULL : forwarders[0]),
+ &in->questions[0]);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, dns_server_process_query_got_response, req);
+ return req;
+ }
+
+ tevent_req_werror(req, DNS_ERR(NAME_ERROR));
+ return tevent_req_post(req, ev);
+}
+
+static void dns_server_process_query_got_response(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct dns_server_process_query_state *state = tevent_req_data(
+ req, struct dns_server_process_query_state);
+ WERROR werr;
+
+ werr = ask_forwarder_recv(subreq, state,
+ &state->answers, &state->ancount,
+ &state->nsrecs, &state->nscount,
+ &state->additional, &state->arcount);
+ TALLOC_FREE(subreq);
+
+ /* If you get an error, attempt a different forwarder */
+ if (!W_ERROR_IS_OK(werr)) {
+ if (state->forwarders != NULL) {
+ DLIST_REMOVE(state->forwarders, state->forwarders);
+ }
+
+ /* If you have run out of forwarders, simply finish */
+ if (state->forwarders == NULL) {
+ tevent_req_werror(req, werr);
+ return;
+ }
+
+ DEBUG(5, ("DNS query returned %s, trying another forwarder.\n",
+ win_errstr(werr)));
+ subreq = ask_forwarder_send(state, state->ev,
+ state->forwarders->forwarder,
+ state->question);
+
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+
+ tevent_req_set_callback(subreq,
+ dns_server_process_query_got_response,
+ req);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static void dns_server_process_query_got_auth(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct dns_server_process_query_state *state = tevent_req_data(
+ req, struct dns_server_process_query_state);
+ WERROR werr;
+ WERROR werr2;
+
+ werr = handle_authoritative_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ /* If you get an error, attempt a different forwarder */
+ if (!W_ERROR_IS_OK(werr)) {
+ if (state->forwarders != NULL) {
+ DLIST_REMOVE(state->forwarders, state->forwarders);
+ }
+
+ /* If you have run out of forwarders, simply finish */
+ if (state->forwarders == NULL) {
+ werr2 = add_zone_authority_record(state->dns,
+ state,
+ state->question,
+ &state->nsrecs);
+ if (tevent_req_werror(req, werr2)) {
+ DBG_WARNING("Failed to add SOA record: %s\n",
+ win_errstr(werr2));
+ return;
+ }
+
+ state->ancount = talloc_array_length(state->answers);
+ state->nscount = talloc_array_length(state->nsrecs);
+ state->arcount = talloc_array_length(state->additional);
+
+ tevent_req_werror(req, werr);
+ return;
+ }
+
+ DEBUG(5, ("Error: %s, trying a different forwarder.\n",
+ win_errstr(werr)));
+ subreq = handle_authoritative_send(state, state->ev, state->dns,
+ state->forwarders->forwarder,
+ state->question, &state->answers,
+ &state->nsrecs,
+ 0); /* cname_depth */
+
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+
+ tevent_req_set_callback(subreq,
+ dns_server_process_query_got_auth,
+ req);
+ return;
+ }
+
+ werr2 = add_zone_authority_record(state->dns,
+ state,
+ state->question,
+ &state->nsrecs);
+ if (tevent_req_werror(req, werr2)) {
+ DBG_WARNING("Failed to add SOA record: %s\n",
+ win_errstr(werr2));
+ return;
+ }
+
+ state->ancount = talloc_array_length(state->answers);
+ state->nscount = talloc_array_length(state->nsrecs);
+ state->arcount = talloc_array_length(state->additional);
+
+ tevent_req_done(req);
+}
+
+WERROR dns_server_process_query_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct dns_res_rec **answers, uint16_t *ancount,
+ struct dns_res_rec **nsrecs, uint16_t *nscount,
+ struct dns_res_rec **additional, uint16_t *arcount)
+{
+ struct dns_server_process_query_state *state = tevent_req_data(
+ req, struct dns_server_process_query_state);
+ WERROR err = WERR_OK;
+
+ if (tevent_req_is_werror(req, &err)) {
+
+ if ((!W_ERROR_EQUAL(err, DNS_ERR(NAME_ERROR))) &&
+ (!W_ERROR_EQUAL(err, WERR_DNS_ERROR_NAME_DOES_NOT_EXIST))) {
+ return err;
+ }
+ }
+ *answers = talloc_move(mem_ctx, &state->answers);
+ *ancount = state->ancount;
+ *nsrecs = talloc_move(mem_ctx, &state->nsrecs);
+ *nscount = state->nscount;
+ *additional = talloc_move(mem_ctx, &state->additional);
+ *arcount = state->arcount;
+ return err;
+}
diff --git a/source4/dns_server/dns_server.c b/source4/dns_server/dns_server.c
new file mode 100644
index 0000000..0a36c1c
--- /dev/null
+++ b/source4/dns_server/dns_server.c
@@ -0,0 +1,965 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ DNS server startup
+
+ Copyright (C) 2010 Kai Blin <kai@samba.org>
+
+ 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 "samba/service_task.h"
+#include "samba/service.h"
+#include "samba/service_stream.h"
+#include "samba/process_model.h"
+#include "lib/events/events.h"
+#include "lib/socket/socket.h"
+#include "lib/tsocket/tsocket.h"
+#include "libcli/util/tstream.h"
+#include "libcli/util/ntstatus.h"
+#include "system/network.h"
+#include "lib/stream/packet.h"
+#include "lib/socket/netif.h"
+#include "dns_server/dns_server.h"
+#include "param/param.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 "auth/session.h"
+#include "lib/util/dlinklist.h"
+#include "lib/util/tevent_werror.h"
+#include "auth/auth.h"
+#include "auth/credentials/credentials.h"
+#include "librpc/gen_ndr/ndr_irpc.h"
+#include "lib/messaging/irpc.h"
+#include "libds/common/roles.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_DNS
+
+NTSTATUS server_service_dns_init(TALLOC_CTX *);
+
+/* hold information about one dns socket */
+struct dns_socket {
+ struct dns_server *dns;
+ struct tsocket_address *local_address;
+};
+
+struct dns_udp_socket {
+ struct dns_socket *dns_socket;
+ struct tdgram_context *dgram;
+ struct tevent_queue *send_queue;
+};
+
+/*
+ state of an open tcp connection
+*/
+struct dns_tcp_connection {
+ /* stream connection we belong to */
+ struct stream_connection *conn;
+
+ /* the dns_server the connection belongs to */
+ struct dns_socket *dns_socket;
+
+ struct tstream_context *tstream;
+
+ struct tevent_queue *send_queue;
+};
+
+static void dns_tcp_terminate_connection(struct dns_tcp_connection *dnsconn, const char *reason)
+{
+ stream_terminate_connection(dnsconn->conn, reason);
+}
+
+static void dns_tcp_recv(struct stream_connection *conn, uint16_t flags)
+{
+ struct dns_tcp_connection *dnsconn = talloc_get_type(conn->private_data,
+ struct dns_tcp_connection);
+ /* this should never be triggered! */
+ dns_tcp_terminate_connection(dnsconn, "dns_tcp_recv: called");
+}
+
+static void dns_tcp_send(struct stream_connection *conn, uint16_t flags)
+{
+ struct dns_tcp_connection *dnsconn = talloc_get_type(conn->private_data,
+ struct dns_tcp_connection);
+ /* this should never be triggered! */
+ dns_tcp_terminate_connection(dnsconn, "dns_tcp_send: called");
+}
+
+struct dns_process_state {
+ DATA_BLOB *in;
+ struct dns_server *dns;
+ struct dns_name_packet in_packet;
+ struct dns_request_state state;
+ WERROR dns_err;
+ struct dns_name_packet out_packet;
+ DATA_BLOB out;
+};
+
+static void dns_process_done(struct tevent_req *subreq);
+
+static struct tevent_req *dns_process_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct dns_server *dns,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ DATA_BLOB *in)
+{
+ struct tevent_req *req, *subreq;
+ struct dns_process_state *state;
+ enum ndr_err_code ndr_err;
+ WERROR ret;
+ const char **forwarder = lpcfg_dns_forwarder(dns->task->lp_ctx);
+ req = tevent_req_create(mem_ctx, &state, struct dns_process_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->state.mem_ctx = state;
+ state->in = in;
+
+ state->dns = dns;
+
+ if (in->length < 12) {
+ tevent_req_werror(req, WERR_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ dump_data_dbgc(DBGC_DNS, 8, in->data, in->length);
+
+ ndr_err = ndr_pull_struct_blob(
+ in, state, &state->in_packet,
+ (ndr_pull_flags_fn_t)ndr_pull_dns_name_packet);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_NOTICE("ndr_pull_dns_name_packet() failed with %s\n",
+ ndr_map_error2string(ndr_err));
+ state->dns_err = DNS_ERR(FORMAT_ERROR);
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ if (DEBUGLVLC(DBGC_DNS, 8)) {
+ NDR_PRINT_DEBUGC(DBGC_DNS, dns_name_packet, &state->in_packet);
+ }
+
+ if (state->in_packet.operation & DNS_FLAG_REPLY) {
+ DBG_INFO("Won't reply to replies.\n");
+ tevent_req_werror(req, WERR_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->state.flags = state->in_packet.operation;
+ state->state.flags |= DNS_FLAG_REPLY;
+
+ state->state.local_address = local_address;
+ state->state.remote_address = remote_address;
+
+ if (forwarder && *forwarder && **forwarder) {
+ state->state.flags |= DNS_FLAG_RECURSION_AVAIL;
+ }
+
+ state->out_packet = state->in_packet;
+
+ ret = dns_verify_tsig(dns, state, &state->state,
+ &state->out_packet, in);
+ if (!W_ERROR_IS_OK(ret)) {
+ DBG_INFO("dns_verify_tsig() failed with %s\n",
+ win_errstr(ret));
+ state->dns_err = ret;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ switch (state->in_packet.operation & DNS_OPCODE) {
+ case DNS_OPCODE_QUERY:
+ subreq = dns_server_process_query_send(
+ state, ev, dns,
+ &state->state, &state->in_packet);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, dns_process_done, req);
+ return req;
+ case DNS_OPCODE_UPDATE:
+ ret = dns_server_process_update(
+ dns, &state->state, state, &state->in_packet,
+ &state->out_packet.answers, &state->out_packet.ancount,
+ &state->out_packet.nsrecs, &state->out_packet.nscount,
+ &state->out_packet.additional,
+ &state->out_packet.arcount);
+ DBG_DEBUG("dns_server_process_update(): %s\n",
+ win_errstr(ret));
+ break;
+ default:
+ ret = WERR_DNS_ERROR_RCODE_NOT_IMPLEMENTED;
+ DBG_NOTICE("OPCODE[0x%x]: %s\n",
+ (state->in_packet.operation & DNS_OPCODE),
+ win_errstr(ret));
+ }
+ state->dns_err = ret;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static void dns_process_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct dns_process_state *state = tevent_req_data(
+ req, struct dns_process_state);
+ WERROR ret;
+
+ ret = dns_server_process_query_recv(
+ subreq, state,
+ &state->out_packet.answers, &state->out_packet.ancount,
+ &state->out_packet.nsrecs, &state->out_packet.nscount,
+ &state->out_packet.additional, &state->out_packet.arcount);
+ TALLOC_FREE(subreq);
+
+ DBG_DEBUG("dns_server_process_query_recv(): %s\n",
+ win_errstr(ret));
+ state->dns_err = ret;
+ tevent_req_done(req);
+}
+
+static WERROR dns_process_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out)
+{
+ struct dns_process_state *state = tevent_req_data(
+ req, struct dns_process_state);
+ enum ndr_err_code ndr_err;
+ uint16_t dns_err;
+ WERROR ret;
+
+ if (tevent_req_is_werror(req, &ret)) {
+ DBG_NOTICE("ERROR: %s from %s\n", win_errstr(ret),
+ tevent_req_print(state, req));
+ return ret;
+ }
+ dns_err = werr_to_dns_err(state->dns_err);
+ if ((dns_err != DNS_RCODE_OK) &&
+ (dns_err != DNS_RCODE_NXDOMAIN) &&
+ (dns_err != DNS_RCODE_NOTAUTH))
+ {
+ DBG_INFO("FAILURE: %s from %s\n",
+ win_errstr(state->dns_err),
+ tevent_req_print(state, req));
+ goto drop;
+ }
+ if (dns_err != DNS_RCODE_OK) {
+ DBG_DEBUG("INFO: %s from %s\n",
+ win_errstr(state->dns_err),
+ tevent_req_print(state, req));
+ state->out_packet.operation |= dns_err;
+ } else {
+ DBG_DEBUG("OK: %s\n",
+ tevent_req_print(state, req));
+ }
+ state->out_packet.operation |= state->state.flags;
+
+ if (state->state.sign) {
+ ret = dns_sign_tsig(state->dns, mem_ctx, &state->state,
+ &state->out_packet, 0);
+ if (!W_ERROR_IS_OK(ret)) {
+ DBG_WARNING("dns_sign_tsig() failed %s\n",
+ win_errstr(ret));
+ dns_err = DNS_RCODE_SERVFAIL;
+ goto drop;
+ }
+ }
+
+ if (DEBUGLVLC(DBGC_DNS, 8)) {
+ NDR_PRINT_DEBUGC(DBGC_DNS, dns_name_packet, &state->out_packet);
+ }
+
+ ndr_err = ndr_push_struct_blob(
+ out, mem_ctx, &state->out_packet,
+ (ndr_push_flags_fn_t)ndr_push_dns_name_packet);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("Failed to push packet: %s!\n",
+ ndr_errstr(ndr_err));
+ dns_err = DNS_RCODE_SERVFAIL;
+ goto drop;
+ }
+ return WERR_OK;
+
+drop:
+ *out = data_blob_talloc(mem_ctx, state->in->data, state->in->length);
+ if (out->data == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ out->data[2] |= 0x80; /* Toggle DNS_FLAG_REPLY */
+ out->data[3] |= dns_err;
+ return WERR_OK;
+}
+
+struct dns_tcp_call {
+ struct dns_tcp_connection *dns_conn;
+ DATA_BLOB in;
+ DATA_BLOB out;
+ uint8_t out_hdr[4];
+ struct iovec out_iov[2];
+};
+
+static void dns_tcp_call_process_done(struct tevent_req *subreq);
+static void dns_tcp_call_writev_done(struct tevent_req *subreq);
+
+static void dns_tcp_call_loop(struct tevent_req *subreq)
+{
+ struct dns_tcp_connection *dns_conn = tevent_req_callback_data(subreq,
+ struct dns_tcp_connection);
+ struct dns_server *dns = dns_conn->dns_socket->dns;
+ struct dns_tcp_call *call;
+ NTSTATUS status;
+
+ call = talloc(dns_conn, struct dns_tcp_call);
+ if (call == NULL) {
+ dns_tcp_terminate_connection(dns_conn, "dns_tcp_call_loop: "
+ "no memory for dns_tcp_call");
+ return;
+ }
+ call->dns_conn = dns_conn;
+
+ status = tstream_read_pdu_blob_recv(subreq,
+ call,
+ &call->in);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ const char *reason;
+
+ reason = talloc_asprintf(call, "dns_tcp_call_loop: "
+ "tstream_read_pdu_blob_recv() - %s",
+ nt_errstr(status));
+ if (!reason) {
+ reason = nt_errstr(status);
+ }
+
+ dns_tcp_terminate_connection(dns_conn, reason);
+ return;
+ }
+
+ DEBUG(10,("Received DNS TCP packet of length %lu from %s\n",
+ (long) call->in.length,
+ tsocket_address_string(dns_conn->conn->remote_address, call)));
+
+ /* skip length header */
+ call->in.data += 2;
+ call->in.length -= 2;
+
+ subreq = dns_process_send(call, dns->task->event_ctx, dns,
+ dns_conn->conn->remote_address,
+ dns_conn->conn->local_address,
+ &call->in);
+ if (subreq == NULL) {
+ dns_tcp_terminate_connection(
+ dns_conn, "dns_tcp_call_loop: dns_process_send "
+ "failed\n");
+ return;
+ }
+ tevent_req_set_callback(subreq, dns_tcp_call_process_done, call);
+
+ /*
+ * The dns tcp pdu's has the length as 2 byte (initial_read_size),
+ * tstream_full_request_u16 provides the pdu length then.
+ */
+ subreq = tstream_read_pdu_blob_send(dns_conn,
+ dns_conn->conn->event.ctx,
+ dns_conn->tstream,
+ 2, /* initial_read_size */
+ tstream_full_request_u16,
+ dns_conn);
+ if (subreq == NULL) {
+ dns_tcp_terminate_connection(dns_conn, "dns_tcp_call_loop: "
+ "no memory for tstream_read_pdu_blob_send");
+ return;
+ }
+ tevent_req_set_callback(subreq, dns_tcp_call_loop, dns_conn);
+}
+
+static void dns_tcp_call_process_done(struct tevent_req *subreq)
+{
+ struct dns_tcp_call *call = tevent_req_callback_data(subreq,
+ struct dns_tcp_call);
+ struct dns_tcp_connection *dns_conn = call->dns_conn;
+ WERROR err;
+
+ err = dns_process_recv(subreq, call, &call->out);
+ TALLOC_FREE(subreq);
+ if (!W_ERROR_IS_OK(err)) {
+ DEBUG(1, ("dns_process returned %s\n", win_errstr(err)));
+ dns_tcp_terminate_connection(dns_conn,
+ "dns_tcp_call_loop: process function failed");
+ return;
+ }
+
+ /* First add the length of the out buffer */
+ RSSVAL(call->out_hdr, 0, call->out.length);
+ call->out_iov[0].iov_base = (char *) call->out_hdr;
+ call->out_iov[0].iov_len = 2;
+
+ call->out_iov[1].iov_base = (char *) call->out.data;
+ call->out_iov[1].iov_len = call->out.length;
+
+ subreq = tstream_writev_queue_send(call,
+ dns_conn->conn->event.ctx,
+ dns_conn->tstream,
+ dns_conn->send_queue,
+ call->out_iov, 2);
+ if (subreq == NULL) {
+ dns_tcp_terminate_connection(dns_conn, "dns_tcp_call_loop: "
+ "no memory for tstream_writev_queue_send");
+ return;
+ }
+ tevent_req_set_callback(subreq, dns_tcp_call_writev_done, call);
+}
+
+static void dns_tcp_call_writev_done(struct tevent_req *subreq)
+{
+ struct dns_tcp_call *call = tevent_req_callback_data(subreq,
+ struct dns_tcp_call);
+ int sys_errno;
+ int rc;
+
+ rc = tstream_writev_queue_recv(subreq, &sys_errno);
+ TALLOC_FREE(subreq);
+ if (rc == -1) {
+ const char *reason;
+
+ reason = talloc_asprintf(call, "dns_tcp_call_writev_done: "
+ "tstream_writev_queue_recv() - %d:%s",
+ sys_errno, strerror(sys_errno));
+ if (!reason) {
+ reason = "dns_tcp_call_writev_done: tstream_writev_queue_recv() failed";
+ }
+
+ dns_tcp_terminate_connection(call->dns_conn, reason);
+ return;
+ }
+
+ /* We don't care about errors */
+
+ talloc_free(call);
+}
+
+/*
+ called when we get a new connection
+*/
+static void dns_tcp_accept(struct stream_connection *conn)
+{
+ struct dns_socket *dns_socket;
+ struct dns_tcp_connection *dns_conn;
+ struct tevent_req *subreq;
+ int rc;
+
+ dns_conn = talloc_zero(conn, struct dns_tcp_connection);
+ if (dns_conn == NULL) {
+ stream_terminate_connection(conn,
+ "dns_tcp_accept: out of memory");
+ return;
+ }
+
+ dns_conn->send_queue = tevent_queue_create(conn, "dns_tcp_accept");
+ if (dns_conn->send_queue == NULL) {
+ stream_terminate_connection(conn,
+ "dns_tcp_accept: out of memory");
+ return;
+ }
+
+ dns_socket = talloc_get_type(conn->private_data, struct dns_socket);
+
+ TALLOC_FREE(conn->event.fde);
+
+ rc = tstream_bsd_existing_socket(dns_conn,
+ socket_get_fd(conn->socket),
+ &dns_conn->tstream);
+ if (rc < 0) {
+ stream_terminate_connection(conn,
+ "dns_tcp_accept: out of memory");
+ return;
+ }
+ /* as server we want to fail early */
+ tstream_bsd_fail_readv_first_error(dns_conn->tstream, true);
+
+ dns_conn->conn = conn;
+ dns_conn->dns_socket = dns_socket;
+ conn->private_data = dns_conn;
+
+ /*
+ * The dns tcp pdu's has the length as 2 byte (initial_read_size),
+ * tstream_full_request_u16 provides the pdu length then.
+ */
+ subreq = tstream_read_pdu_blob_send(dns_conn,
+ dns_conn->conn->event.ctx,
+ dns_conn->tstream,
+ 2, /* initial_read_size */
+ tstream_full_request_u16,
+ dns_conn);
+ if (subreq == NULL) {
+ dns_tcp_terminate_connection(dns_conn, "dns_tcp_accept: "
+ "no memory for tstream_read_pdu_blob_send");
+ return;
+ }
+ tevent_req_set_callback(subreq, dns_tcp_call_loop, dns_conn);
+}
+
+static const struct stream_server_ops dns_tcp_stream_ops = {
+ .name = "dns_tcp",
+ .accept_connection = dns_tcp_accept,
+ .recv_handler = dns_tcp_recv,
+ .send_handler = dns_tcp_send
+};
+
+struct dns_udp_call {
+ struct dns_udp_socket *sock;
+ struct tsocket_address *src;
+ DATA_BLOB in;
+ DATA_BLOB out;
+};
+
+static void dns_udp_call_process_done(struct tevent_req *subreq);
+static void dns_udp_call_sendto_done(struct tevent_req *subreq);
+
+static void dns_udp_call_loop(struct tevent_req *subreq)
+{
+ struct dns_udp_socket *sock = tevent_req_callback_data(subreq,
+ struct dns_udp_socket);
+ struct dns_server *dns = sock->dns_socket->dns;
+ struct dns_udp_call *call;
+ uint8_t *buf;
+ ssize_t len;
+ int sys_errno;
+
+ call = talloc(sock, struct dns_udp_call);
+ if (call == NULL) {
+ talloc_free(call);
+ goto done;
+ }
+ call->sock = sock;
+
+ len = tdgram_recvfrom_recv(subreq, &sys_errno,
+ call, &buf, &call->src);
+ TALLOC_FREE(subreq);
+ if (len == -1) {
+ talloc_free(call);
+ goto done;
+ }
+
+ call->in.data = buf;
+ call->in.length = len;
+
+ DEBUG(10,("Received DNS UDP packet of length %lu from %s\n",
+ (long)call->in.length,
+ tsocket_address_string(call->src, call)));
+
+ subreq = dns_process_send(call, dns->task->event_ctx, dns,
+ call->src,
+ sock->dns_socket->local_address,
+ &call->in);
+ if (subreq == NULL) {
+ TALLOC_FREE(call);
+ goto done;
+ }
+ tevent_req_set_callback(subreq, dns_udp_call_process_done, call);
+
+done:
+ subreq = tdgram_recvfrom_send(sock,
+ sock->dns_socket->dns->task->event_ctx,
+ sock->dgram);
+ if (subreq == NULL) {
+ task_server_terminate(sock->dns_socket->dns->task,
+ "no memory for tdgram_recvfrom_send",
+ true);
+ return;
+ }
+ tevent_req_set_callback(subreq, dns_udp_call_loop, sock);
+}
+
+static void dns_udp_call_process_done(struct tevent_req *subreq)
+{
+ struct dns_udp_call *call = tevent_req_callback_data(
+ subreq, struct dns_udp_call);
+ struct dns_udp_socket *sock = call->sock;
+ struct dns_server *dns = sock->dns_socket->dns;
+ WERROR err;
+
+ err = dns_process_recv(subreq, call, &call->out);
+ TALLOC_FREE(subreq);
+ if (!W_ERROR_IS_OK(err)) {
+ DEBUG(1, ("dns_process returned %s\n", win_errstr(err)));
+ TALLOC_FREE(call);
+ return;
+ }
+
+ subreq = tdgram_sendto_queue_send(call,
+ dns->task->event_ctx,
+ sock->dgram,
+ sock->send_queue,
+ call->out.data,
+ call->out.length,
+ call->src);
+ if (subreq == NULL) {
+ talloc_free(call);
+ return;
+ }
+ tevent_req_set_callback(subreq, dns_udp_call_sendto_done, call);
+
+}
+static void dns_udp_call_sendto_done(struct tevent_req *subreq)
+{
+ struct dns_udp_call *call = tevent_req_callback_data(subreq,
+ struct dns_udp_call);
+ int sys_errno;
+
+ tdgram_sendto_queue_recv(subreq, &sys_errno);
+
+ /* We don't care about errors */
+
+ talloc_free(call);
+}
+
+/*
+ start listening on the given address
+*/
+static NTSTATUS dns_add_socket(struct dns_server *dns,
+ const struct model_ops *model_ops,
+ const char *name,
+ const char *address,
+ uint16_t port)
+{
+ struct dns_socket *dns_socket;
+ struct dns_udp_socket *dns_udp_socket;
+ struct tevent_req *udpsubreq;
+ NTSTATUS status;
+ int ret;
+
+ dns_socket = talloc(dns, struct dns_socket);
+ NT_STATUS_HAVE_NO_MEMORY(dns_socket);
+
+ dns_socket->dns = dns;
+
+ ret = tsocket_address_inet_from_strings(dns_socket, "ip",
+ address, port,
+ &dns_socket->local_address);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ return status;
+ }
+
+ status = stream_setup_socket(dns->task,
+ dns->task->event_ctx,
+ dns->task->lp_ctx,
+ model_ops,
+ &dns_tcp_stream_ops,
+ "ip", address, &port,
+ lpcfg_socket_options(dns->task->lp_ctx),
+ dns_socket,
+ dns->task->process_context);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Failed to bind to %s:%u TCP - %s\n",
+ address, port, nt_errstr(status)));
+ talloc_free(dns_socket);
+ return status;
+ }
+
+ dns_udp_socket = talloc(dns_socket, struct dns_udp_socket);
+ NT_STATUS_HAVE_NO_MEMORY(dns_udp_socket);
+
+ dns_udp_socket->dns_socket = dns_socket;
+
+ ret = tdgram_inet_udp_socket(dns_socket->local_address,
+ NULL,
+ dns_udp_socket,
+ &dns_udp_socket->dgram);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("Failed to bind to %s:%u UDP - %s\n",
+ address, port, nt_errstr(status)));
+ return status;
+ }
+
+ dns_udp_socket->send_queue = tevent_queue_create(dns_udp_socket,
+ "dns_udp_send_queue");
+ NT_STATUS_HAVE_NO_MEMORY(dns_udp_socket->send_queue);
+
+ udpsubreq = tdgram_recvfrom_send(dns_udp_socket,
+ dns->task->event_ctx,
+ dns_udp_socket->dgram);
+ NT_STATUS_HAVE_NO_MEMORY(udpsubreq);
+ tevent_req_set_callback(udpsubreq, dns_udp_call_loop, dns_udp_socket);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ setup our listening sockets on the configured network interfaces
+*/
+static NTSTATUS dns_startup_interfaces(struct dns_server *dns,
+ struct interface *ifaces,
+ const struct model_ops *model_ops)
+{
+ size_t num_interfaces;
+ TALLOC_CTX *tmp_ctx = talloc_new(dns);
+ NTSTATUS status;
+ int i;
+
+ if (ifaces != NULL) {
+ num_interfaces = iface_list_count(ifaces);
+
+ for (i=0; i<num_interfaces; i++) {
+ const char *address = talloc_strdup(tmp_ctx,
+ iface_list_n_ip(ifaces, i));
+
+ status = dns_add_socket(dns, model_ops, "dns", address,
+ lpcfg_dns_port(dns->task->lp_ctx));
+ NT_STATUS_NOT_OK_RETURN(status);
+ }
+ } else {
+ size_t num_binds = 0;
+ char **wcard;
+ wcard = iface_list_wildcard(tmp_ctx);
+ if (wcard == NULL) {
+ DEBUG(0, ("No wildcard address available\n"));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ for (i = 0; wcard[i] != NULL; i++) {
+ status = dns_add_socket(dns, model_ops, "dns", wcard[i],
+ lpcfg_dns_port(dns->task->lp_ctx));
+ if (NT_STATUS_IS_OK(status)) {
+ num_binds++;
+ }
+ }
+ if (num_binds == 0) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+
+ return NT_STATUS_OK;
+}
+
+static struct dns_server_tkey_store *tkey_store_init(TALLOC_CTX *mem_ctx,
+ uint16_t size)
+{
+ struct dns_server_tkey_store *buffer = talloc_zero(mem_ctx,
+ struct dns_server_tkey_store);
+
+ if (buffer == NULL) {
+ return NULL;
+ }
+
+ buffer->size = size;
+ buffer->next_idx = 0;
+
+ buffer->tkeys = talloc_zero_array(buffer, struct dns_server_tkey *, size);
+ if (buffer->tkeys == NULL) {
+ TALLOC_FREE(buffer);
+ }
+
+ return buffer;
+}
+
+static NTSTATUS dns_server_reload_zones(struct dns_server *dns)
+{
+ NTSTATUS status;
+ struct dns_server_zone *new_list = NULL;
+ struct dns_server_zone *old_list = dns->zones;
+ struct dns_server_zone *old_zone;
+ status = dns_common_zones(dns->samdb, dns, NULL, &new_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ dns->zones = new_list;
+ while ((old_zone = DLIST_TAIL(old_list)) != NULL) {
+ DLIST_REMOVE(old_list, old_zone);
+ talloc_free(old_zone);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * Called when the internal DNS server should reload the zones from DB, for
+ * example, when zones are added or deleted through RPC or replicated by
+ * inbound DRS.
+ */
+static NTSTATUS dns_reload_zones(struct irpc_message *msg,
+ struct dnssrv_reload_dns_zones *r)
+{
+ struct dns_server *dns;
+
+ dns = talloc_get_type(msg->private_data, struct dns_server);
+ if (dns == NULL) {
+ r->out.result = NT_STATUS_INTERNAL_ERROR;
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ r->out.result = dns_server_reload_zones(dns);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS dns_task_init(struct task_server *task)
+{
+ struct dns_server *dns;
+ NTSTATUS status;
+ struct interface *ifaces = NULL;
+ int ret;
+ static const char * const attrs_none[] = { NULL};
+ struct ldb_message *dns_acc;
+ char *hostname_lower;
+ char *dns_spn;
+ bool ok;
+
+ switch (lpcfg_server_role(task->lp_ctx)) {
+ case ROLE_STANDALONE:
+ task_server_terminate(task, "dns: no DNS required in standalone configuration", false);
+ return NT_STATUS_INVALID_DOMAIN_ROLE;
+ case ROLE_DOMAIN_MEMBER:
+ task_server_terminate(task, "dns: no DNS required in member server configuration", false);
+ return NT_STATUS_INVALID_DOMAIN_ROLE;
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ /* Yes, we want a DNS */
+ break;
+ }
+
+ if (lpcfg_interfaces(task->lp_ctx) && lpcfg_bind_interfaces_only(task->lp_ctx)) {
+ load_interface_list(task, task->lp_ctx, &ifaces);
+
+ if (iface_list_count(ifaces) == 0) {
+ task_server_terminate(task, "dns: no network interfaces configured", false);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ task_server_set_title(task, "task[dns]");
+
+ dns = talloc_zero(task, struct dns_server);
+ if (dns == NULL) {
+ task_server_terminate(task, "dns: out of memory", true);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dns->task = task;
+
+ dns->server_credentials = cli_credentials_init(dns);
+ if (!dns->server_credentials) {
+ task_server_terminate(task, "Failed to init server credentials\n", true);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ dns->samdb = samdb_connect(dns,
+ dns->task->event_ctx,
+ dns->task->lp_ctx,
+ system_session(dns->task->lp_ctx),
+ NULL,
+ 0);
+ if (!dns->samdb) {
+ task_server_terminate(task, "dns: samdb_connect failed", true);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ok = cli_credentials_set_conf(dns->server_credentials, task->lp_ctx);
+ if (!ok) {
+ task_server_terminate(task,
+ "dns: failed to load smb.conf",
+ true);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ hostname_lower = strlower_talloc(dns, lpcfg_netbios_name(task->lp_ctx));
+ dns_spn = talloc_asprintf(dns, "DNS/%s.%s",
+ hostname_lower,
+ lpcfg_dnsdomain(task->lp_ctx));
+ TALLOC_FREE(hostname_lower);
+
+ ret = dsdb_search_one(dns->samdb, dns, &dns_acc,
+ ldb_get_default_basedn(dns->samdb), LDB_SCOPE_SUBTREE,
+ attrs_none, 0, "(servicePrincipalName=%s)",
+ dns_spn);
+ if (ret == LDB_SUCCESS) {
+ TALLOC_FREE(dns_acc);
+ if (!dns_spn) {
+ task_server_terminate(task, "dns: talloc_asprintf failed", true);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ status = cli_credentials_set_stored_principal(dns->server_credentials, task->lp_ctx, dns_spn);
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task,
+ talloc_asprintf(task, "Failed to obtain server credentials for DNS, "
+ "despite finding it in the samdb! %s\n",
+ nt_errstr(status)),
+ true);
+ return status;
+ }
+ } else {
+ TALLOC_FREE(dns_spn);
+ status = cli_credentials_set_machine_account(dns->server_credentials, task->lp_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task,
+ talloc_asprintf(task, "Failed to obtain server credentials, perhaps a standalone server?: %s\n",
+ nt_errstr(status)),
+ true);
+ return status;
+ }
+ }
+
+ dns->tkeys = tkey_store_init(dns, TKEY_BUFFER_SIZE);
+ if (!dns->tkeys) {
+ task_server_terminate(task, "Failed to allocate tkey storage\n", true);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dns_server_reload_zones(dns);
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task, "dns: failed to load DNS zones", true);
+ return status;
+ }
+
+ status = dns_startup_interfaces(dns, ifaces, task->model_ops);
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task, "dns failed to setup interfaces", true);
+ return status;
+ }
+
+ /* Setup the IRPC interface and register handlers */
+ status = irpc_add_name(task->msg_ctx, "dnssrv");
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task, "dns: failed to register IRPC name", true);
+ return status;
+ }
+
+ status = IRPC_REGISTER(task->msg_ctx, irpc, DNSSRV_RELOAD_DNS_ZONES,
+ dns_reload_zones, dns);
+ if (!NT_STATUS_IS_OK(status)) {
+ task_server_terminate(task, "dns: failed to setup reload handler", true);
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS server_service_dns_init(TALLOC_CTX *ctx)
+{
+ static const struct service_details details = {
+ .inhibit_fork_on_accept = true,
+ .inhibit_pre_fork = true,
+ .task_init = dns_task_init,
+ .post_fork = NULL
+ };
+ return register_server_service(ctx, "dns", &details);
+}
diff --git a/source4/dns_server/dns_server.h b/source4/dns_server/dns_server.h
new file mode 100644
index 0000000..f4e5a61
--- /dev/null
+++ b/source4/dns_server/dns_server.h
@@ -0,0 +1,124 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ DNS structures
+
+ Copyright (C) 2010 Kai Blin <kai@samba.org>
+
+ 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/>.
+*/
+
+#ifndef __DNS_SERVER_H__
+#define __DNS_SERVER_H__
+
+#include "librpc/gen_ndr/dns.h"
+#include "librpc/gen_ndr/ndr_dnsp.h"
+#include "dnsserver_common.h"
+
+struct tsocket_address;
+struct dns_server_tkey {
+ const char *name;
+ enum dns_tkey_mode mode;
+ const char *algorithm;
+ struct auth_session_info *session_info;
+ struct gensec_security *gensec;
+ bool complete;
+};
+
+#define TKEY_BUFFER_SIZE 128
+
+struct dns_server_tkey_store {
+ struct dns_server_tkey **tkeys;
+ uint16_t next_idx;
+ uint16_t size;
+};
+
+struct dns_server {
+ struct task_server *task;
+ struct ldb_context *samdb;
+ struct dns_server_zone *zones;
+ struct dns_server_tkey_store *tkeys;
+ struct cli_credentials *server_credentials;
+};
+
+struct dns_request_state {
+ TALLOC_CTX *mem_ctx;
+ uint16_t flags;
+ bool authenticated;
+ bool sign;
+ char *key_name;
+ struct dns_res_rec *tsig;
+ uint16_t tsig_error;
+ const struct tsocket_address *local_address;
+ const struct tsocket_address *remote_address;
+};
+
+struct tevent_req *dns_server_process_query_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct dns_server *dns, struct dns_request_state *req_state,
+ const struct dns_name_packet *in);
+WERROR dns_server_process_query_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct dns_res_rec **answers, uint16_t *ancount,
+ struct dns_res_rec **nsrecs, uint16_t *nscount,
+ struct dns_res_rec **additional, uint16_t *arcount);
+
+WERROR dns_server_process_update(struct dns_server *dns,
+ const struct dns_request_state *state,
+ TALLOC_CTX *mem_ctx,
+ const struct dns_name_packet *in,
+ struct dns_res_rec **prereqs, uint16_t *prereq_count,
+ struct dns_res_rec **updates, uint16_t *update_count,
+ struct dns_res_rec **additional, uint16_t *arcount);
+
+bool dns_authoritative_for_zone(struct dns_server *dns,
+ const char *name);
+const char *dns_get_authoritative_zone(struct dns_server *dns,
+ const char *name);
+WERROR dns_lookup_records(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn,
+ struct dnsp_DnssrvRpcRecord **records,
+ uint16_t *rec_count);
+WERROR dns_lookup_records_wildcard(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn,
+ struct dnsp_DnssrvRpcRecord **records,
+ uint16_t *rec_count);
+WERROR dns_replace_records(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn,
+ bool needs_add,
+ struct dnsp_DnssrvRpcRecord *records,
+ uint16_t rec_count);
+WERROR dns_name2dn(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ const char *name,
+ struct ldb_dn **_dn);
+struct dns_server_tkey *dns_find_tkey(struct dns_server_tkey_store *store,
+ const char *name);
+WERROR dns_verify_tsig(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ struct dns_request_state *state,
+ struct dns_name_packet *packet,
+ DATA_BLOB *in);
+WERROR dns_sign_tsig(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ struct dns_request_state *state,
+ struct dns_name_packet *packet,
+ uint16_t error);
+
+#include "source4/dns_server/dnsserver_common.h"
+
+#endif /* __DNS_SERVER_H__ */
diff --git a/source4/dns_server/dns_update.c b/source4/dns_server/dns_update.c
new file mode 100644
index 0000000..4d2ee0b
--- /dev/null
+++ b/source4/dns_server/dns_update.c
@@ -0,0 +1,879 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ DNS server handler for update requests
+
+ Copyright (C) 2010 Kai Blin <kai@samba.org>
+
+ 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 "librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ndr_dns.h"
+#include "librpc/gen_ndr/ndr_dnsp.h"
+#include <ldb.h>
+#include "param/param.h"
+#include "param/loadparm.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
+#include "samba/service_task.h"
+#include "dns_server/dns_server.h"
+#include "auth/auth.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_DNS
+
+static WERROR dns_rr_to_dnsp(TALLOC_CTX *mem_ctx,
+ const struct dns_res_rec *rrec,
+ struct dnsp_DnssrvRpcRecord *r,
+ bool name_is_static);
+
+static WERROR check_one_prerequisite(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ const struct dns_name_question *zone,
+ const struct dns_res_rec *pr,
+ bool *final_result)
+{
+ bool match;
+ WERROR werror;
+ struct ldb_dn *dn;
+ uint16_t i;
+ bool found = false;
+ struct dnsp_DnssrvRpcRecord *rec = NULL;
+ struct dnsp_DnssrvRpcRecord *ans;
+ uint16_t a_count;
+
+ size_t host_part_len = 0;
+
+ *final_result = true;
+
+ if (pr->ttl != 0) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+
+ match = dns_name_match(zone->name, pr->name, &host_part_len);
+ if (!match) {
+ return DNS_ERR(NOTZONE);
+ }
+
+ werror = dns_name2dn(dns, mem_ctx, pr->name, &dn);
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ if (pr->rr_class == DNS_QCLASS_ANY) {
+
+ if (pr->length != 0) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+
+
+ if (pr->rr_type == DNS_QTYPE_ALL) {
+ /*
+ */
+ werror = dns_lookup_records(dns, mem_ctx, dn, &ans, &a_count);
+ if (W_ERROR_EQUAL(werror, WERR_DNS_ERROR_NAME_DOES_NOT_EXIST)) {
+ return DNS_ERR(NAME_ERROR);
+ }
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ if (a_count == 0) {
+ return DNS_ERR(NAME_ERROR);
+ }
+ } else {
+ /*
+ */
+ werror = dns_lookup_records(dns, mem_ctx, dn, &ans, &a_count);
+ if (W_ERROR_EQUAL(werror, WERR_DNS_ERROR_NAME_DOES_NOT_EXIST)) {
+ return DNS_ERR(NXRRSET);
+ }
+ if (W_ERROR_EQUAL(werror, DNS_ERR(NAME_ERROR))) {
+ return DNS_ERR(NXRRSET);
+ }
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ for (i = 0; i < a_count; i++) {
+ if (ans[i].wType == (enum dns_record_type) pr->rr_type) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return DNS_ERR(NXRRSET);
+ }
+ }
+
+ /*
+ * RFC2136 3.2.5 doesn't actually mention the need to return
+ * OK here, but otherwise we'd always return a FORMAT_ERROR
+ * later on. This also matches Microsoft DNS behavior.
+ */
+ return WERR_OK;
+ }
+
+ if (pr->rr_class == DNS_QCLASS_NONE) {
+ if (pr->length != 0) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+
+ if (pr->rr_type == DNS_QTYPE_ALL) {
+ /*
+ */
+ werror = dns_lookup_records(dns, mem_ctx, dn, &ans, &a_count);
+ if (W_ERROR_EQUAL(werror, WERR_OK)) {
+ return DNS_ERR(YXDOMAIN);
+ }
+ } else {
+ /*
+ */
+ werror = dns_lookup_records(dns, mem_ctx, dn, &ans, &a_count);
+ if (W_ERROR_EQUAL(werror, WERR_DNS_ERROR_NAME_DOES_NOT_EXIST)) {
+ werror = WERR_OK;
+ }
+ if (W_ERROR_EQUAL(werror, DNS_ERR(NAME_ERROR))) {
+ werror = WERR_OK;
+ }
+
+ for (i = 0; i < a_count; i++) {
+ if (ans[i].wType == (enum dns_record_type) pr->rr_type) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ return DNS_ERR(YXRRSET);
+ }
+ }
+
+ /*
+ * RFC2136 3.2.5 doesn't actually mention the need to return
+ * OK here, but otherwise we'd always return a FORMAT_ERROR
+ * later on. This also matches Microsoft DNS behavior.
+ */
+ return WERR_OK;
+ }
+
+ if (pr->rr_class != zone->question_class) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+
+ *final_result = false;
+
+ werror = dns_lookup_records(dns, mem_ctx, dn, &ans, &a_count);
+ if (W_ERROR_EQUAL(werror, WERR_DNS_ERROR_NAME_DOES_NOT_EXIST)) {
+ return DNS_ERR(NXRRSET);
+ }
+ if (W_ERROR_EQUAL(werror, DNS_ERR(NAME_ERROR))) {
+ return DNS_ERR(NXRRSET);
+ }
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ rec = talloc_zero(mem_ctx, struct dnsp_DnssrvRpcRecord);
+ W_ERROR_HAVE_NO_MEMORY(rec);
+
+ werror = dns_rr_to_dnsp(rec, pr, rec, dns_name_is_static(ans, a_count));
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ for (i = 0; i < a_count; i++) {
+ if (dns_record_match(rec, &ans[i])) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return DNS_ERR(NXRRSET);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR check_prerequisites(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ const struct dns_name_question *zone,
+ const struct dns_res_rec *prereqs, uint16_t count)
+{
+ uint16_t i;
+ WERROR final_error = WERR_OK;
+
+ for (i = 0; i < count; i++) {
+ bool final;
+ WERROR werror;
+
+ werror = check_one_prerequisite(dns, mem_ctx, zone,
+ &prereqs[i], &final);
+ if (!W_ERROR_IS_OK(werror)) {
+ if (final) {
+ return werror;
+ }
+ if (W_ERROR_IS_OK(final_error)) {
+ final_error = werror;
+ }
+ }
+ }
+
+ if (!W_ERROR_IS_OK(final_error)) {
+ return final_error;
+ }
+
+ return WERR_OK;
+}
+
+static WERROR update_prescan(const struct dns_name_question *zone,
+ const struct dns_res_rec *updates, uint16_t count)
+{
+ const struct dns_res_rec *r;
+ uint16_t i;
+ size_t host_part_len;
+ bool match;
+
+ for (i = 0; i < count; i++) {
+ r = &updates[i];
+ match = dns_name_match(zone->name, r->name, &host_part_len);
+ if (!match) {
+ return DNS_ERR(NOTZONE);
+ }
+ if (zone->question_class == r->rr_class) {
+ if (r->rr_type == DNS_QTYPE_ALL) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ if (r->rr_type == DNS_QTYPE_AXFR) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ if (r->rr_type == DNS_QTYPE_MAILB) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ if (r->rr_type == DNS_QTYPE_MAILA) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ } else if (r->rr_class == DNS_QCLASS_ANY) {
+ if (r->ttl != 0) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ if (r->length != 0) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ if (r->rr_type == DNS_QTYPE_AXFR) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ if (r->rr_type == DNS_QTYPE_MAILB) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ if (r->rr_type == DNS_QTYPE_MAILA) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ } else if (r->rr_class == DNS_QCLASS_NONE) {
+ if (r->ttl != 0) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ if (r->rr_type == DNS_QTYPE_ALL) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ if (r->rr_type == DNS_QTYPE_AXFR) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ if (r->rr_type == DNS_QTYPE_MAILB) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ if (r->rr_type == DNS_QTYPE_MAILA) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ } else {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+ }
+ return WERR_OK;
+}
+
+static WERROR dns_rr_to_dnsp(TALLOC_CTX *mem_ctx,
+ const struct dns_res_rec *rrec,
+ struct dnsp_DnssrvRpcRecord *r,
+ bool name_is_static)
+{
+ enum ndr_err_code ndr_err;
+
+ if (rrec->rr_type == DNS_QTYPE_ALL) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+
+ ZERO_STRUCTP(r);
+
+ r->wType = (enum dns_record_type) rrec->rr_type;
+ r->dwTtlSeconds = rrec->ttl;
+ r->rank = DNS_RANK_ZONE;
+ if (name_is_static) {
+ r->dwTimeStamp = 0;
+ } else {
+ r->dwTimeStamp = unix_to_dns_timestamp(time(NULL));
+ }
+
+ /* If we get QCLASS_ANY, we're done here */
+ if (rrec->rr_class == DNS_QCLASS_ANY) {
+ goto done;
+ }
+
+ switch(rrec->rr_type) {
+ case DNS_QTYPE_A:
+ r->data.ipv4 = talloc_strdup(mem_ctx, rrec->rdata.ipv4_record);
+ W_ERROR_HAVE_NO_MEMORY(r->data.ipv4);
+ break;
+ case DNS_QTYPE_AAAA:
+ r->data.ipv6 = talloc_strdup(mem_ctx, rrec->rdata.ipv6_record);
+ W_ERROR_HAVE_NO_MEMORY(r->data.ipv6);
+ break;
+ case DNS_QTYPE_NS:
+ r->data.ns = talloc_strdup(mem_ctx, rrec->rdata.ns_record);
+ W_ERROR_HAVE_NO_MEMORY(r->data.ns);
+ break;
+ case DNS_QTYPE_CNAME:
+ r->data.cname = talloc_strdup(mem_ctx, rrec->rdata.cname_record);
+ W_ERROR_HAVE_NO_MEMORY(r->data.cname);
+ break;
+ case DNS_QTYPE_SRV:
+ r->data.srv.wPriority = rrec->rdata.srv_record.priority;
+ r->data.srv.wWeight = rrec->rdata.srv_record.weight;
+ r->data.srv.wPort = rrec->rdata.srv_record.port;
+ r->data.srv.nameTarget = talloc_strdup(mem_ctx,
+ rrec->rdata.srv_record.target);
+ W_ERROR_HAVE_NO_MEMORY(r->data.srv.nameTarget);
+ break;
+ case DNS_QTYPE_PTR:
+ r->data.ptr = talloc_strdup(mem_ctx, rrec->rdata.ptr_record);
+ W_ERROR_HAVE_NO_MEMORY(r->data.ptr);
+ break;
+ case DNS_QTYPE_MX:
+ r->data.mx.wPriority = rrec->rdata.mx_record.preference;
+ r->data.mx.nameTarget = talloc_strdup(mem_ctx,
+ rrec->rdata.mx_record.exchange);
+ W_ERROR_HAVE_NO_MEMORY(r->data.mx.nameTarget);
+ break;
+ case DNS_QTYPE_TXT:
+ ndr_err = ndr_dnsp_string_list_copy(mem_ctx,
+ &rrec->rdata.txt_record.txt,
+ &r->data.txt);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ break;
+ default:
+ DEBUG(0, ("Got a qytpe of %d\n", rrec->rr_type));
+ return DNS_ERR(NOT_IMPLEMENTED);
+ }
+
+done:
+
+ return WERR_OK;
+}
+
+
+static WERROR handle_one_update(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ const struct dns_name_question *zone,
+ const struct dns_res_rec *update,
+ const struct dns_server_tkey *tkey)
+{
+ struct dnsp_DnssrvRpcRecord *recs = NULL;
+ uint16_t rcount = 0;
+ struct ldb_dn *dn;
+ uint16_t i;
+ uint16_t first = 0;
+ WERROR werror;
+ bool tombstoned = false;
+ bool needs_add = false;
+ bool name_is_static;
+
+ DBG_NOTICE("Looking at record: \n");
+ if (DEBUGLVL(DBGLVL_NOTICE)) {
+ NDR_PRINT_DEBUG(dns_res_rec, discard_const(update));
+ }
+
+ switch (update->rr_type) {
+ case DNS_QTYPE_A:
+ case DNS_QTYPE_NS:
+ case DNS_QTYPE_CNAME:
+ case DNS_QTYPE_SOA:
+ case DNS_QTYPE_PTR:
+ case DNS_QTYPE_MX:
+ case DNS_QTYPE_AAAA:
+ case DNS_QTYPE_SRV:
+ case DNS_QTYPE_TXT:
+ case DNS_QTYPE_ALL:
+ break;
+ default:
+ DEBUG(0, ("Can't handle updates of type %u yet\n",
+ update->rr_type));
+ return DNS_ERR(NOT_IMPLEMENTED);
+ }
+
+ werror = dns_name2dn(dns, mem_ctx, update->name, &dn);
+ DBG_DEBUG("dns_name2dn(): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ werror = dns_common_lookup(dns->samdb, mem_ctx, dn,
+ &recs, &rcount, &tombstoned);
+ DBG_DEBUG("dns_common_lookup(): %s\n", win_errstr(werror));
+ if (W_ERROR_EQUAL(werror, WERR_DNS_ERROR_NAME_DOES_NOT_EXIST)) {
+ needs_add = true;
+ werror = WERR_OK;
+ }
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ if (tombstoned) {
+ /*
+ * we need to keep the existing tombstone record
+ * and ignore it.
+ *
+ * There *should* only be a single record of type TOMBSTONE,
+ * but we don't insist.
+ */
+ if (rcount != 1) {
+ DBG_WARNING("Tombstoned dnsNode has %u records, "
+ "expected 1\n", rcount);
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(dns_res_rec, discard_const(update));
+ }
+ }
+ first = rcount;
+ }
+
+ name_is_static = dns_name_is_static(recs, rcount);
+
+ if (update->rr_class == zone->question_class) {
+ if (update->rr_type == DNS_QTYPE_CNAME) {
+ /*
+ * If there is a record in the directory
+ * that's not a CNAME, ignore update
+ */
+ for (i = first; i < rcount; i++) {
+ if (recs[i].wType != DNS_TYPE_CNAME) {
+ DEBUG(5, ("Skipping update\n"));
+ return WERR_OK;
+ }
+ }
+
+ /*
+ * There should be no entries besides one CNAME record
+ * per name, so replace everything with the new CNAME
+ */
+
+ rcount = first;
+ recs = talloc_realloc(mem_ctx, recs,
+ struct dnsp_DnssrvRpcRecord, rcount + 1);
+ W_ERROR_HAVE_NO_MEMORY(recs);
+
+ werror = dns_rr_to_dnsp(
+ recs, update, &recs[rcount], name_is_static);
+ DBG_DEBUG("dns_rr_to_dnsp(CNAME): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+ rcount += 1;
+
+ werror = dns_replace_records(dns, mem_ctx, dn,
+ needs_add, recs, rcount);
+ DBG_DEBUG("dns_replace_records(CNAME): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ return WERR_OK;
+ } else {
+ /*
+ * If there is a CNAME record for this name,
+ * ignore update
+ */
+ for (i = first; i < rcount; i++) {
+ if (recs[i].wType == DNS_TYPE_CNAME) {
+ DEBUG(5, ("Skipping update\n"));
+ return WERR_OK;
+ }
+ }
+ }
+ if (update->rr_type == DNS_QTYPE_SOA) {
+ bool found = false;
+
+ /*
+ * If the zone has no SOA record?? or update's
+ * serial number is smaller than existing SOA's,
+ * ignore update
+ */
+ for (i = first; i < rcount; i++) {
+ if (recs[i].wType == DNS_TYPE_SOA) {
+ uint16_t n, o;
+
+ n = update->rdata.soa_record.serial;
+ o = recs[i].data.soa.serial;
+ /*
+ * TODO: Implement RFC 1982 comparison
+ * logic for RFC2136
+ */
+ if (n <= o) {
+ DEBUG(5, ("Skipping update\n"));
+ return WERR_OK;
+ }
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ DEBUG(5, ("Skipping update\n"));
+ return WERR_OK;
+ }
+
+ werror = dns_rr_to_dnsp(
+ mem_ctx, update, &recs[i], name_is_static);
+ DBG_DEBUG("dns_rr_to_dnsp(SOA): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ /*
+ * There should only be one SOA, which we have already
+ * found and replaced. We now check for and tombstone
+ * any others.
+ */
+ for (i++; i < rcount; i++) {
+ if (recs[i].wType != DNS_TYPE_SOA) {
+ continue;
+ }
+ DBG_ERR("Duplicate SOA records found.\n");
+ if (DEBUGLVL(DBGLVL_ERR)) {
+ NDR_PRINT_DEBUG(dns_res_rec,
+ discard_const(update));
+ }
+ recs[i] = (struct dnsp_DnssrvRpcRecord) {
+ .wType = DNS_TYPE_TOMBSTONE,
+ };
+ }
+
+ werror = dns_replace_records(dns, mem_ctx, dn,
+ needs_add, recs, rcount);
+ DBG_DEBUG("dns_replace_records(SOA): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ return WERR_OK;
+ }
+ /* All but CNAME, SOA */
+ recs = talloc_realloc(mem_ctx, recs,
+ struct dnsp_DnssrvRpcRecord, rcount+1);
+ W_ERROR_HAVE_NO_MEMORY(recs);
+
+ werror =
+ dns_rr_to_dnsp(recs, update, &recs[rcount], name_is_static);
+ DBG_DEBUG("dns_rr_to_dnsp(GENERIC): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ for (i = first; i < rcount; i++) {
+ if (!dns_record_match(&recs[i], &recs[rcount])) {
+ continue;
+ }
+
+ recs[i].data = recs[rcount].data;
+ recs[i].wType = recs[rcount].wType;
+ recs[i].dwTtlSeconds = recs[rcount].dwTtlSeconds;
+ recs[i].rank = recs[rcount].rank;
+ recs[i].dwReserved = 0;
+ recs[i].flags = 0;
+ werror = dns_replace_records(dns, mem_ctx, dn,
+ needs_add, recs, rcount);
+ DBG_DEBUG("dns_replace_records(REPLACE): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ return WERR_OK;
+ }
+ /* we did not find a matching record. This is new. */
+ werror = dns_replace_records(dns, mem_ctx, dn,
+ needs_add, recs, rcount+1);
+ DBG_DEBUG("dns_replace_records(ADD): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ return WERR_OK;
+ } else if (update->rr_class == DNS_QCLASS_ANY) {
+ /*
+ * Mass-deleting records by type, which we do by adding a
+ * tombstone with zero timestamp. dns_replace_records() will
+ * work out if the node as a whole needs tombstoning.
+ */
+ if (update->rr_type == DNS_QTYPE_ALL) {
+ if (samba_dns_name_equal(update->name, zone->name)) {
+ for (i = first; i < rcount; i++) {
+
+ if (recs[i].wType == DNS_TYPE_SOA) {
+ continue;
+ }
+
+ if (recs[i].wType == DNS_TYPE_NS) {
+ continue;
+ }
+
+ recs[i] = (struct dnsp_DnssrvRpcRecord) {
+ .wType = DNS_TYPE_TOMBSTONE,
+ };
+ }
+
+ } else {
+ for (i = first; i < rcount; i++) {
+ recs[i] = (struct dnsp_DnssrvRpcRecord) {
+ .wType = DNS_TYPE_TOMBSTONE,
+ };
+ }
+ }
+
+ } else if (samba_dns_name_equal(update->name, zone->name)) {
+
+ if (update->rr_type == DNS_QTYPE_SOA) {
+ return WERR_OK;
+ }
+
+ if (update->rr_type == DNS_QTYPE_NS) {
+ return WERR_OK;
+ }
+ }
+ for (i = first; i < rcount; i++) {
+ if (recs[i].wType == (enum dns_record_type) update->rr_type) {
+ recs[i] = (struct dnsp_DnssrvRpcRecord) {
+ .wType = DNS_TYPE_TOMBSTONE,
+ };
+ }
+ }
+
+ werror = dns_replace_records(dns, mem_ctx, dn,
+ needs_add, recs, rcount);
+ DBG_DEBUG("dns_replace_records(DELETE-ANY): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ return WERR_OK;
+ } else if (update->rr_class == DNS_QCLASS_NONE) {
+ /* deleting individual records */
+ struct dnsp_DnssrvRpcRecord *del_rec;
+
+ if (update->rr_type == DNS_QTYPE_SOA) {
+ return WERR_OK;
+ }
+ if (update->rr_type == DNS_QTYPE_NS) {
+ bool found = false;
+ struct dnsp_DnssrvRpcRecord *ns_rec = talloc(mem_ctx,
+ struct dnsp_DnssrvRpcRecord);
+ W_ERROR_HAVE_NO_MEMORY(ns_rec);
+
+ werror = dns_rr_to_dnsp(
+ ns_rec, update, ns_rec, name_is_static);
+ DBG_DEBUG("dns_rr_to_dnsp(NS): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ for (i = first; i < rcount; i++) {
+ if (dns_record_match(ns_rec, &recs[i])) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ return WERR_OK;
+ }
+ }
+
+ del_rec = talloc(mem_ctx, struct dnsp_DnssrvRpcRecord);
+ W_ERROR_HAVE_NO_MEMORY(del_rec);
+
+ werror =
+ dns_rr_to_dnsp(del_rec, update, del_rec, name_is_static);
+ DBG_DEBUG("dns_rr_to_dnsp(DELETE-NONE): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ for (i = first; i < rcount; i++) {
+ if (dns_record_match(del_rec, &recs[i])) {
+ recs[i] = (struct dnsp_DnssrvRpcRecord) {
+ .wType = DNS_TYPE_TOMBSTONE,
+ };
+ }
+ }
+
+ werror = dns_replace_records(dns, mem_ctx, dn,
+ needs_add, recs, rcount);
+ DBG_DEBUG("dns_replace_records(DELETE-NONE): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+ }
+
+ return WERR_OK;
+}
+
+static WERROR handle_updates(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ const struct dns_name_question *zone,
+ const struct dns_res_rec *prereqs, uint16_t pcount,
+ struct dns_res_rec *updates, uint16_t upd_count,
+ struct dns_server_tkey *tkey)
+{
+ struct ldb_dn *zone_dn = NULL;
+ WERROR werror = WERR_OK;
+ int ret;
+ uint16_t ri;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+
+ if (tkey != NULL) {
+ ret = ldb_set_opaque(
+ dns->samdb,
+ DSDB_SESSION_INFO,
+ tkey->session_info);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(1, ("unable to set session info\n"));
+ werror = DNS_ERR(SERVER_FAILURE);
+ goto failed;
+ }
+ }
+
+ werror = dns_name2dn(dns, tmp_ctx, zone->name, &zone_dn);
+ DBG_DEBUG("dns_name2dn(): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_GOTO(werror, failed);
+
+ ret = ldb_transaction_start(dns->samdb);
+ if (ret != LDB_SUCCESS) {
+ werror = DNS_ERR(SERVER_FAILURE);
+ goto failed;
+ }
+
+ werror = check_prerequisites(dns, tmp_ctx, zone, prereqs, pcount);
+ W_ERROR_NOT_OK_GOTO(werror, failed);
+
+ DBG_DEBUG("dns update count is %u\n", upd_count);
+
+ for (ri = 0; ri < upd_count; ri++) {
+ werror = handle_one_update(dns, tmp_ctx, zone,
+ &updates[ri], tkey);
+ DBG_DEBUG("handle_one_update(%u): %s\n",
+ ri, win_errstr(werror));
+ W_ERROR_NOT_OK_GOTO(werror, failed);
+ }
+
+failed:
+ if (W_ERROR_IS_OK(werror)) {
+ ret = ldb_transaction_commit(dns->samdb);
+ if (ret != LDB_SUCCESS) {
+ werror = DNS_ERR(SERVER_FAILURE);
+ }
+ } else {
+ ldb_transaction_cancel(dns->samdb);
+ }
+
+ if (tkey != NULL) {
+ ldb_set_opaque(
+ dns->samdb,
+ DSDB_SESSION_INFO,
+ system_session(dns->task->lp_ctx));
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return werror;
+
+}
+
+static WERROR dns_update_allowed(struct dns_server *dns,
+ const struct dns_request_state *state,
+ struct dns_server_tkey **tkey)
+{
+ if (lpcfg_allow_dns_updates(dns->task->lp_ctx) == DNS_UPDATE_ON) {
+ DEBUG(2, ("All updates allowed.\n"));
+ return WERR_OK;
+ }
+
+ if (lpcfg_allow_dns_updates(dns->task->lp_ctx) == DNS_UPDATE_OFF) {
+ DEBUG(2, ("Updates disabled.\n"));
+ return DNS_ERR(REFUSED);
+ }
+
+ if (state->authenticated == false ) {
+ DEBUG(2, ("Update not allowed for unsigned packet.\n"));
+ return DNS_ERR(REFUSED);
+ }
+
+ *tkey = dns_find_tkey(dns->tkeys, state->key_name);
+ if (*tkey == NULL) {
+ DEBUG(0, ("Authenticated, but key not found. Something is wrong.\n"));
+ return DNS_ERR(REFUSED);
+ }
+
+ return WERR_OK;
+}
+
+
+WERROR dns_server_process_update(struct dns_server *dns,
+ const struct dns_request_state *state,
+ TALLOC_CTX *mem_ctx,
+ const struct dns_name_packet *in,
+ struct dns_res_rec **prereqs, uint16_t *prereq_count,
+ struct dns_res_rec **updates, uint16_t *update_count,
+ struct dns_res_rec **additional, uint16_t *arcount)
+{
+ struct dns_name_question *zone;
+ const struct dns_server_zone *z;
+ size_t host_part_len = 0;
+ WERROR werror = DNS_ERR(NOT_IMPLEMENTED);
+ struct dns_server_tkey *tkey = NULL;
+
+ if (in->qdcount != 1) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+
+ zone = &in->questions[0];
+
+ if (zone->question_class != DNS_QCLASS_IN &&
+ zone->question_class != DNS_QCLASS_ANY) {
+ return DNS_ERR(NOT_IMPLEMENTED);
+ }
+
+ if (zone->question_type != DNS_QTYPE_SOA) {
+ return DNS_ERR(FORMAT_ERROR);
+ }
+
+ DEBUG(2, ("Got a dns update request.\n"));
+
+ for (z = dns->zones; z != NULL; z = z->next) {
+ bool match;
+
+ match = dns_name_match(z->name, zone->name, &host_part_len);
+ if (match) {
+ break;
+ }
+ }
+
+ if (z == NULL) {
+ DEBUG(1, ("We're not authoritative for this zone\n"));
+ return DNS_ERR(NOTAUTH);
+ }
+
+ if (host_part_len != 0) {
+ /* TODO: We need to delegate this one */
+ DEBUG(1, ("Would have to delegate zone '%s'.\n", zone->name));
+ return DNS_ERR(NOT_IMPLEMENTED);
+ }
+
+ *prereq_count = in->ancount;
+ *prereqs = in->answers;
+ werror = check_prerequisites(dns, mem_ctx, in->questions, *prereqs,
+ *prereq_count);
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ werror = dns_update_allowed(dns, state, &tkey);
+ if (!W_ERROR_IS_OK(werror)) {
+ return werror;
+ }
+
+ *update_count = in->nscount;
+ *updates = in->nsrecs;
+ werror = update_prescan(in->questions, *updates, *update_count);
+ DBG_DEBUG("update_prescan(): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ werror = handle_updates(dns, mem_ctx, in->questions, *prereqs,
+ *prereq_count, *updates, *update_count, tkey);
+ DBG_DEBUG("handle_updates(): %s\n", win_errstr(werror));
+ W_ERROR_NOT_OK_RETURN(werror);
+
+ return werror;
+}
diff --git a/source4/dns_server/dns_utils.c b/source4/dns_server/dns_utils.c
new file mode 100644
index 0000000..d0661f3
--- /dev/null
+++ b/source4/dns_server/dns_utils.c
@@ -0,0 +1,130 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ DNS server utils
+
+ Copyright (C) 2010 Kai Blin <kai@samba.org>
+
+ 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/dns_server.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_DNS
+
+
+/*
+ * Lookup a DNS record, performing an exact match.
+ * i.e. DNS wild card records are not considered.
+ */
+WERROR dns_lookup_records(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn,
+ struct dnsp_DnssrvRpcRecord **records,
+ uint16_t *rec_count)
+{
+ return dns_common_lookup(dns->samdb, mem_ctx, dn,
+ records, rec_count, NULL);
+}
+
+/*
+ * Lookup a DNS record, will match DNS wild card records if an exact match
+ * is not found.
+ */
+WERROR dns_lookup_records_wildcard(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn,
+ struct dnsp_DnssrvRpcRecord **records,
+ uint16_t *rec_count)
+{
+ return dns_common_wildcard_lookup(dns->samdb, mem_ctx, dn,
+ records, rec_count);
+}
+
+WERROR dns_replace_records(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *dn,
+ bool needs_add,
+ struct dnsp_DnssrvRpcRecord *records,
+ uint16_t rec_count)
+{
+ /* TODO: Autogenerate this somehow */
+ uint32_t dwSerial = 110;
+ return dns_common_replace(dns->samdb, mem_ctx, dn,
+ needs_add, dwSerial, records, rec_count);
+}
+
+bool dns_authoritative_for_zone(struct dns_server *dns,
+ const char *name)
+{
+ const struct dns_server_zone *z;
+ size_t host_part_len = 0;
+
+ if (name == NULL) {
+ return false;
+ }
+
+ if (strcmp(name, "") == 0) {
+ return true;
+ }
+ for (z = dns->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 false;
+ }
+
+ return true;
+}
+
+const char *dns_get_authoritative_zone(struct dns_server *dns,
+ const char *name)
+{
+ const struct dns_server_zone *z;
+ size_t host_part_len = 0;
+
+ for (z = dns->zones; z != NULL; z = z->next) {
+ bool match;
+ match = dns_name_match(z->name, name, &host_part_len);
+ if (match) {
+ return z->name;
+ }
+ }
+ return NULL;
+}
+
+WERROR dns_name2dn(struct dns_server *dns,
+ TALLOC_CTX *mem_ctx,
+ const char *name,
+ struct ldb_dn **dn)
+{
+ return dns_common_name2dn(dns->samdb, dns->zones,
+ mem_ctx, name, dn);
+}
+
diff --git a/source4/dns_server/dnsserver_common.c b/source4/dns_server/dnsserver_common.c
new file mode 100644
index 0000000..fbe39d9
--- /dev/null
+++ b/source4/dns_server/dnsserver_common.c
@@ -0,0 +1,1594 @@
+/*
+ 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;
+}
diff --git a/source4/dns_server/dnsserver_common.h b/source4/dns_server/dnsserver_common.h
new file mode 100644
index 0000000..a0c1065
--- /dev/null
+++ b/source4/dns_server/dnsserver_common.h
@@ -0,0 +1,139 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ DNS server utils
+
+ Copyright (C) 2014 Stefan Metzmacher
+
+ 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 "rpc_server/dnsserver/dnsserver.h"
+
+#ifndef __DNSSERVER_COMMON_H__
+#define __DNSSERVER_COMMON_H__
+
+uint8_t werr_to_dns_err(WERROR werr);
+#define DNS_ERR(err_str) WERR_DNS_ERROR_RCODE_##err_str
+
+struct ldb_message_element;
+struct ldb_context;
+struct dnsp_DnssrvRpcRecord;
+
+struct dns_server_zone {
+ struct dns_server_zone *prev, *next;
+ const char *name;
+ struct ldb_dn *dn;
+};
+
+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);
+
+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);
+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);
+WERROR dns_name_check(TALLOC_CTX *mem_ctx,
+ size_t len,
+ const char *name);
+WERROR dns_get_zone_properties(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *zone_dn,
+ struct dnsserver_zoneinfo *zoneinfo);
+bool dns_name_is_static(struct dnsp_DnssrvRpcRecord *records,
+ uint16_t rec_count);
+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);
+bool dns_name_match(const char *zone, const char *name, size_t *host_part_len);
+WERROR dns_common_name2dn(struct ldb_context *samdb,
+ struct dns_server_zone *zones,
+ TALLOC_CTX *mem_ctx,
+ const char *name,
+ struct ldb_dn **_dn);
+bool samba_dns_name_equal(const char *name1, const char *name2);
+
+bool dns_record_match(struct dnsp_DnssrvRpcRecord *rec1,
+ struct dnsp_DnssrvRpcRecord *rec2);
+
+/*
+ * For this routine, base_dn is generally NULL. The exception comes
+ * from the python bindings to support setting ACLs on DNS objects
+ * when joining Windows
+ */
+NTSTATUS dns_common_zones(struct ldb_context *samdb,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_dn *base_dn,
+ struct dns_server_zone **zones_ret);
+
+bool dns_zoneinfo_load_zone_property(struct dnsserver_zoneinfo *zoneinfo,
+ struct dnsp_DnsProperty *prop);
+/*
+ * Log a DNS operation along with it's duration
+ * Enabled by setting a log level of "dns:10"
+ *
+ * const char *operation
+ * const char *result
+ * const struct timeval *start
+ * const char *zone
+ * const char *name
+ * const char *data
+ */
+#define DNS_COMMON_LOG_OPERATION(result, start, zone, name, data) \
+ if (CHECK_DEBUGLVLC(DBGC_DNS, DBGLVL_DEBUG)) { \
+ struct timeval now = timeval_current(); \
+ uint64_t duration = usec_time_diff(&now, (start));\
+ const char *re = (result);\
+ const char *zn = (zone); \
+ const char *nm = (name); \
+ const char *dt = (data); \
+ DBG_DEBUG( \
+ "DNS timing: result: [%s] duration: (%" PRIi64 ") " \
+ "zone: [%s] name: [%s] data: [%s]\n", \
+ re == NULL ? "" : re, \
+ duration, \
+ zn == NULL ? "" : zn, \
+ nm == NULL ? "" : nm, \
+ dt == NULL ? "" : dt); \
+ }
+
+/* There are this many nttime jiffies in an hour */
+#define NTTIME_TO_HOURS (3600ULL * 10ULL * 1000ULL * 1000ULL)
+
+/*
+ * convert unix time to a DNS timestamp
+ * (hours in the NTTIME epoch, 32 bit).
+ */
+uint32_t unix_to_dns_timestamp(time_t t);
+
+/*
+ * Convert a DNS timestamp into NTTIME.
+ */
+NTSTATUS dns_timestamp_to_nt_time(NTTIME *_nt, uint32_t t);
+
+#endif /* __DNSSERVER_COMMON_H__ */
diff --git a/source4/dns_server/pydns.c b/source4/dns_server/pydns.c
new file mode 100644
index 0000000..6e99ebd
--- /dev/null
+++ b/source4/dns_server/pydns.c
@@ -0,0 +1,445 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Python DNS server wrapper
+
+ 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 "lib/replace/system/python.h"
+#include "python/py3compat.h"
+#include "includes.h"
+#include "python/modules.h"
+#include <pyldb.h>
+#include <pytalloc.h>
+#include "dns_server/dnsserver_common.h"
+#include "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
+#include "librpc/gen_ndr/ndr_dnsp.h"
+#include "librpc/rpc/pyrpc_util.h"
+
+/* FIXME: These should be in a header file somewhere */
+#define PyErr_LDB_OR_RAISE(py_ldb, ldb) \
+ if (!py_check_dcerpc_type(py_ldb, "ldb", "Ldb")) { \
+ PyErr_SetString(PyExc_TypeError, "Ldb connection object required"); \
+ return NULL; \
+ } \
+ ldb = pyldb_Ldb_AsLdbContext(py_ldb);
+
+#define PyErr_LDB_DN_OR_RAISE(py_ldb_dn, dn) \
+ if (!py_check_dcerpc_type(py_ldb_dn, "ldb", "Dn")) { \
+ PyErr_SetString(PyExc_TypeError, "ldb Dn object required"); \
+ return NULL; \
+ } \
+ dn = pyldb_Dn_AS_DN(py_ldb_dn);
+
+static PyObject *py_dnsp_DnssrvRpcRecord_get_list(struct dnsp_DnssrvRpcRecord *records,
+ uint16_t num_records)
+{
+ PyObject *py_dns_list;
+ int i;
+ py_dns_list = PyList_New(num_records);
+ if (py_dns_list == NULL) {
+ return NULL;
+ }
+ for (i = 0; i < num_records; i++) {
+ PyObject *py_dns_record;
+ py_dns_record = py_return_ndr_struct("samba.dcerpc.dnsp", "DnssrvRpcRecord", records, &records[i]);
+ PyList_SetItem(py_dns_list, i, py_dns_record);
+ }
+ return py_dns_list;
+}
+
+
+static int py_dnsp_DnssrvRpcRecord_get_array(PyObject *value,
+ TALLOC_CTX *mem_ctx,
+ struct dnsp_DnssrvRpcRecord **records,
+ uint16_t *num_records)
+{
+ int i;
+ struct dnsp_DnssrvRpcRecord *recs;
+ PY_CHECK_TYPE(&PyList_Type, value, return -1;);
+ recs = talloc_array(mem_ctx, struct dnsp_DnssrvRpcRecord,
+ PyList_GET_SIZE(value));
+ if (recs == NULL) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ for (i = 0; i < PyList_GET_SIZE(value); i++) {
+ bool type_correct;
+ PyObject *item = PyList_GET_ITEM(value, i);
+ type_correct = py_check_dcerpc_type(item, "samba.dcerpc.dnsp", "DnssrvRpcRecord");
+ if (type_correct == false) {
+ return -1;
+ }
+ if (talloc_reference(mem_ctx, pytalloc_get_mem_ctx(item)) == NULL) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ recs[i] = *(struct dnsp_DnssrvRpcRecord *)pytalloc_get_ptr(item);
+ }
+ *records = recs;
+ *num_records = PyList_GET_SIZE(value);
+ return 0;
+}
+
+static PyObject *py_dsdb_dns_lookup(PyObject *self,
+ PyObject *args, PyObject *kwargs)
+{
+ struct ldb_context *samdb;
+ PyObject *py_ldb, *ret, *pydn;
+ PyObject *py_dns_partition = NULL;
+ PyObject *result = NULL;
+ char *dns_name;
+ TALLOC_CTX *frame;
+ NTSTATUS status;
+ WERROR werr;
+ struct dns_server_zone *zones_list;
+ struct ldb_dn *dn, *dns_partition = NULL;
+ struct dnsp_DnssrvRpcRecord *records;
+ uint16_t num_records;
+ const char * const kwnames[] = { "ldb", "dns_name",
+ "dns_partition", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Os|O",
+ discard_const_p(char *, kwnames),
+ &py_ldb, &dns_name,
+ &py_dns_partition)) {
+ return NULL;
+ }
+ PyErr_LDB_OR_RAISE(py_ldb, samdb);
+
+ if (py_dns_partition) {
+ PyErr_LDB_DN_OR_RAISE(py_dns_partition,
+ dns_partition);
+ }
+
+ frame = talloc_stackframe();
+
+ status = dns_common_zones(samdb, frame, dns_partition,
+ &zones_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(frame);
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ werr = dns_common_name2dn(samdb, zones_list, frame, dns_name, &dn);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(frame);
+ PyErr_SetWERROR(werr);
+ return NULL;
+ }
+
+ werr = dns_common_lookup(samdb,
+ frame,
+ dn,
+ &records,
+ &num_records,
+ NULL);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(frame);
+ PyErr_SetWERROR(werr);
+ return NULL;
+ }
+
+ ret = py_dnsp_DnssrvRpcRecord_get_list(records, num_records);
+ pydn = pyldb_Dn_FromDn(dn);
+ talloc_free(frame);
+ result = Py_BuildValue("(OO)", pydn, ret);
+ Py_CLEAR(ret);
+ Py_CLEAR(pydn);
+ return result;
+}
+
+static PyObject *py_dsdb_dns_extract(PyObject *self, PyObject *args)
+{
+ struct ldb_context *samdb;
+ PyObject *py_dns_el, *ret;
+ PyObject *py_ldb = NULL;
+ TALLOC_CTX *frame;
+ WERROR werr;
+ struct ldb_message_element *dns_el;
+ struct dnsp_DnssrvRpcRecord *records;
+ uint16_t num_records;
+
+ if (!PyArg_ParseTuple(args, "OO", &py_ldb, &py_dns_el)) {
+ return NULL;
+ }
+
+ PyErr_LDB_OR_RAISE(py_ldb, samdb);
+
+ if (!py_check_dcerpc_type(py_dns_el, "ldb", "MessageElement")) {
+ PyErr_SetString(PyExc_TypeError,
+ "ldb MessageElement object required");
+ return NULL;
+ }
+ dns_el = pyldb_MessageElement_AsMessageElement(py_dns_el);
+
+ frame = talloc_stackframe();
+
+ werr = dns_common_extract(samdb, dns_el,
+ frame,
+ &records,
+ &num_records);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(frame);
+ PyErr_SetWERROR(werr);
+ return NULL;
+ }
+
+ ret = py_dnsp_DnssrvRpcRecord_get_list(records, num_records);
+ talloc_free(frame);
+ return ret;
+}
+
+static PyObject *py_dsdb_dns_replace(PyObject *self, PyObject *args)
+{
+ struct ldb_context *samdb;
+ PyObject *py_ldb, *py_dns_records;
+ char *dns_name;
+ TALLOC_CTX *frame;
+ NTSTATUS status;
+ WERROR werr;
+ int ret;
+ struct dns_server_zone *zones_list;
+ struct ldb_dn *dn;
+ struct dnsp_DnssrvRpcRecord *records;
+ uint16_t num_records;
+
+ /*
+ * TODO: This is a shocking abuse, but matches what the
+ * internal DNS server does, it should be pushed into
+ * dns_common_replace()
+ */
+ static const int serial = 110;
+
+ if (!PyArg_ParseTuple(args, "OsO", &py_ldb, &dns_name, &py_dns_records)) {
+ return NULL;
+ }
+ PyErr_LDB_OR_RAISE(py_ldb, samdb);
+
+ frame = talloc_stackframe();
+
+ status = dns_common_zones(samdb, frame, NULL, &zones_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ werr = dns_common_name2dn(samdb, zones_list, frame, dns_name, &dn);
+ if (!W_ERROR_IS_OK(werr)) {
+ PyErr_SetWERROR(werr);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ ret = py_dnsp_DnssrvRpcRecord_get_array(py_dns_records,
+ frame,
+ &records, &num_records);
+ if (ret != 0) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ werr = dns_common_replace(samdb,
+ frame,
+ dn,
+ false, /* Not adding a record */
+ serial,
+ records,
+ num_records);
+ if (!W_ERROR_IS_OK(werr)) {
+ PyErr_SetWERROR(werr);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_dsdb_dns_replace_by_dn(PyObject *self, PyObject *args)
+{
+ struct ldb_context *samdb;
+ PyObject *py_ldb, *py_dn, *py_dns_records;
+ TALLOC_CTX *frame;
+ WERROR werr;
+ int ret;
+ struct ldb_dn *dn;
+ struct dnsp_DnssrvRpcRecord *records;
+ uint16_t num_records;
+
+ /*
+ * TODO: This is a shocking abuse, but matches what the
+ * internal DNS server does, it should be pushed into
+ * dns_common_replace()
+ */
+ static const int serial = 110;
+
+ if (!PyArg_ParseTuple(args, "OOO", &py_ldb, &py_dn, &py_dns_records)) {
+ return NULL;
+ }
+ PyErr_LDB_OR_RAISE(py_ldb, samdb);
+
+ PyErr_LDB_DN_OR_RAISE(py_dn, dn);
+
+ frame = talloc_stackframe();
+
+ ret = py_dnsp_DnssrvRpcRecord_get_array(py_dns_records,
+ frame,
+ &records, &num_records);
+ if (ret != 0) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ werr = dns_common_replace(samdb,
+ frame,
+ dn,
+ false, /* Not adding a node */
+ serial,
+ records,
+ num_records);
+ if (!W_ERROR_IS_OK(werr)) {
+ PyErr_SetWERROR(werr);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_dsdb_dns_records_match(PyObject *self, PyObject *args)
+{
+ PyObject *py_recs[2];
+ struct dnsp_DnssrvRpcRecord *rec1;
+ struct dnsp_DnssrvRpcRecord *rec2;
+ size_t i;
+ bool type_correct;
+ bool match;
+
+ if (!PyArg_ParseTuple(args, "OO", &py_recs[0], &py_recs[1])) {
+ return NULL;
+ }
+
+ for (i = 0; i < 2; i++) {
+ type_correct = py_check_dcerpc_type(py_recs[i],
+ "samba.dcerpc.dnsp",
+ "DnssrvRpcRecord");
+ if (! type_correct) {
+ PyErr_SetString(PyExc_ValueError,
+ "DnssrvRpcRecord expected");
+ return NULL;
+ }
+ }
+
+ rec1 = (struct dnsp_DnssrvRpcRecord *)pytalloc_get_ptr(py_recs[0]);
+ rec2 = (struct dnsp_DnssrvRpcRecord *)pytalloc_get_ptr(py_recs[1]);
+
+ match = dns_record_match(rec1, rec2);
+ return PyBool_FromLong(match);
+}
+
+
+static PyObject *py_dsdb_dns_unix_to_dns_timestamp(PyObject *self, PyObject *args)
+{
+ uint32_t timestamp;
+ time_t t;
+ long long lt;
+
+ if (!PyArg_ParseTuple(args, "L", &lt)) {
+ return NULL;
+ }
+
+ t = lt;
+ if (t != lt) {
+ /* time_t is presumably 32 bit here */
+ PyErr_SetString(PyExc_ValueError, "Time out of range");
+ return NULL;
+ }
+ timestamp = unix_to_dns_timestamp(t);
+ return Py_BuildValue("k", (unsigned long) timestamp);
+}
+
+static PyObject *py_dsdb_dns_timestamp_to_nt_time(PyObject *self, PyObject *args)
+{
+ unsigned long long timestamp;
+ NTSTATUS status;
+ NTTIME nt;
+ if (!PyArg_ParseTuple(args, "K", &timestamp)) {
+ return NULL;
+ }
+
+ if (timestamp > UINT32_MAX) {
+ PyErr_SetString(PyExc_ValueError, "Time out of range");
+ return NULL;
+ }
+ status = dns_timestamp_to_nt_time(&nt, (uint32_t)timestamp);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetString(PyExc_ValueError, "Time out of range");
+ return NULL;
+ }
+ return Py_BuildValue("L", (long long) nt);
+}
+
+
+static PyMethodDef py_dsdb_dns_methods[] = {
+
+ { "lookup", PY_DISCARD_FUNC_SIG(PyCFunction, py_dsdb_dns_lookup),
+ METH_VARARGS|METH_KEYWORDS,
+ "Get the DNS database entries for a DNS name"},
+ { "replace", (PyCFunction)py_dsdb_dns_replace,
+ METH_VARARGS, "Replace the DNS database entries for a DNS name"},
+ { "replace_by_dn", (PyCFunction)py_dsdb_dns_replace_by_dn,
+ METH_VARARGS, "Replace the DNS database entries for a LDB DN"},
+ { "records_match", (PyCFunction)py_dsdb_dns_records_match,
+ METH_VARARGS|METH_KEYWORDS,
+ "Decide whether two records match, according to dns update rules"},
+ { "extract", (PyCFunction)py_dsdb_dns_extract,
+ METH_VARARGS, "Return the DNS database entry as a python structure from an Ldb.MessageElement of type dnsRecord"},
+ { "unix_to_dns_timestamp", (PyCFunction)py_dsdb_dns_unix_to_dns_timestamp,
+ METH_VARARGS,
+ "Convert a time.time() value to a dns timestamp (hours since 1601)"},
+ { "dns_timestamp_to_nt_time", (PyCFunction)py_dsdb_dns_timestamp_to_nt_time,
+ METH_VARARGS,
+ "Convert a dns timestamp to an NTTIME value"},
+ {0}
+};
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "dsdb_dns",
+ .m_doc = "Python bindings for the DNS objects in the directory service databases.",
+ .m_size = -1,
+ .m_methods = py_dsdb_dns_methods,
+};
+
+MODULE_INIT_FUNC(dsdb_dns)
+{
+ PyObject *m;
+
+ m = PyModule_Create(&moduledef);
+
+ if (m == NULL)
+ return NULL;
+
+ return m;
+}
diff --git a/source4/dns_server/wscript_build b/source4/dns_server/wscript_build
new file mode 100644
index 0000000..ab0a241
--- /dev/null
+++ b/source4/dns_server/wscript_build
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+
+# dnsserver_common is enabled without the ad-dc to prevent imports from failing
+# when samba-tool is called where the ad-dc was not built. The server-side dns
+# code is used in the client when we do direct LDAP modification of DNS records.
+bld.SAMBA_LIBRARY('dnsserver_common',
+ source='dnsserver_common.c',
+ deps='samba-util samba-errors ldbsamba clidns',
+ private_library=True
+ )
+
+bld.SAMBA_MODULE('service_dns',
+ source='dns_server.c dns_query.c dns_update.c dns_utils.c dns_crypto.c',
+ subsystem='service',
+ init_function='server_service_dns_init',
+ deps='samba-hostconfig LIBTSOCKET LIBSAMBA_TSOCKET ldbsamba clidns gensec auth samba_server_gensec dnsserver_common',
+ local_include=False,
+ internal_module=False,
+ enabled=bld.AD_DC_BUILD_IS_ENABLED()
+ )
+
+# a bind9 dlz module giving access to the Samba DNS SAM
+bld.SAMBA_LIBRARY('dlz_bind9_10',
+ source='dlz_bind9.c',
+ cflags='-DBIND_VERSION_9_10',
+ private_library=True,
+ link_name='modules/bind9/dlz_bind9_10.so',
+ realname='dlz_bind9_10.so',
+ install_path='${MODULESDIR}/bind9',
+ deps='samba-hostconfig samdb-common gensec popt dnsserver_common',
+ enabled=bld.AD_DC_BUILD_IS_ENABLED())
+
+bld.SAMBA_LIBRARY('dlz_bind9_11',
+ source='dlz_bind9.c',
+ cflags='-DBIND_VERSION_9_11',
+ private_library=True,
+ link_name='modules/bind9/dlz_bind9_11.so',
+ realname='dlz_bind9_11.so',
+ install_path='${MODULESDIR}/bind9',
+ deps='samba-hostconfig samdb-common gensec popt dnsserver_common',
+ enabled=bld.AD_DC_BUILD_IS_ENABLED())
+
+bld.SAMBA_LIBRARY('dlz_bind9_12',
+ source='dlz_bind9.c',
+ cflags='-DBIND_VERSION_9_12',
+ private_library=True,
+ link_name='modules/bind9/dlz_bind9_12.so',
+ realname='dlz_bind9_12.so',
+ install_path='${MODULESDIR}/bind9',
+ deps='samba-hostconfig samdb-common gensec popt dnsserver_common',
+ enabled=bld.AD_DC_BUILD_IS_ENABLED())
+
+bld.SAMBA_LIBRARY('dlz_bind9_14',
+ source='dlz_bind9.c',
+ cflags='-DBIND_VERSION_9_14',
+ private_library=True,
+ link_name='modules/bind9/dlz_bind9_14.so',
+ realname='dlz_bind9_14.so',
+ install_path='${MODULESDIR}/bind9',
+ deps='samba-hostconfig samdb-common gensec popt dnsserver_common',
+ enabled=bld.AD_DC_BUILD_IS_ENABLED())
+
+bld.SAMBA_LIBRARY('dlz_bind9_16',
+ source='dlz_bind9.c',
+ cflags='-DBIND_VERSION_9_16',
+ private_library=True,
+ link_name='modules/bind9/dlz_bind9_16.so',
+ realname='dlz_bind9_16.so',
+ install_path='${MODULESDIR}/bind9',
+ deps='samba-hostconfig samdb-common gensec popt dnsserver_common',
+ enabled=bld.AD_DC_BUILD_IS_ENABLED())
+
+bld.SAMBA_LIBRARY('dlz_bind9_18',
+ source='dlz_bind9.c',
+ cflags='-DBIND_VERSION_9_18',
+ private_library=True,
+ link_name='modules/bind9/dlz_bind9_18.so',
+ realname='dlz_bind9_18.so',
+ install_path='${MODULESDIR}/bind9',
+ deps='samba-hostconfig samdb-common gensec popt dnsserver_common',
+ enabled=bld.AD_DC_BUILD_IS_ENABLED())
+
+bld.SAMBA_LIBRARY('dlz_bind9_for_torture',
+ source='dlz_bind9.c',
+ cflags='-DBIND_VERSION_9_16',
+ private_library=True,
+ deps='samba-hostconfig samdb-common gensec popt dnsserver_common',
+ enabled=bld.AD_DC_BUILD_IS_ENABLED())
+
+pyldb_util = bld.pyembed_libname('pyldb-util')
+pyrpc_util = bld.pyembed_libname('pyrpc_util')
+pytalloc_util = bld.pyembed_libname('pytalloc-util')
+
+bld.SAMBA_PYTHON('python_dsdb_dns',
+ source='pydns.c',
+ deps='samdb %s %s dnsserver_common %s' % (
+ pyldb_util, pyrpc_util, pytalloc_util),
+ realname='samba/dsdb_dns.so')