s4-dns: a dlz module for bind9
authorAndrew Tridgell <tridge@samba.org>
Mon, 6 Dec 2010 03:12:07 +0000 (14:12 +1100)
committerAndrew Tridgell <tridge@samba.org>
Mon, 6 Dec 2010 04:48:30 +0000 (05:48 +0100)
this module can be loaded into bind9 if bind9 has been built with the
--with-dlz-dlopen option. It provides access bind9 access to the
DNS records in SAMDB.

You can then start bind9 with this in named.conf:

dlz "Samba zone" {
database "dlopen /usr/lib/samba/modules/bind9/dlz_bind9.so";
};

See http://git.samba.org/tridge/bind9.git for a bind9 tree with the
dlz_dlopen driver. See also the discussion on the bind-workers mailing
list

Autobuild-User: Andrew Tridgell <tridge@samba.org>
Autobuild-Date: Mon Dec  6 05:48:30 CET 2010 on sn-devel-104

source4/dns_server/dlz_bind9.c [new file with mode: 0644]
source4/dns_server/dlz_bind9.h [new file with mode: 0644]
source4/dns_server/wscript_build

diff --git a/source4/dns_server/dlz_bind9.c b/source4/dns_server/dlz_bind9.c
new file mode 100644 (file)
index 0000000..0260a79
--- /dev/null
@@ -0,0 +1,523 @@
+/*
+   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 "dsdb/samdb/samdb.h"
+#include "dsdb/common/util.h"
+#include "auth/session.h"
+#include "gen_ndr/ndr_dnsp.h"
+#include "dlz_bind9.h"
+
+struct dlz_bind9_data {
+       struct ldb_context *samdb;
+       struct tevent_context *ev_ctx;
+       struct loadparm_context *lp;
+
+       /* helper functions from the dlz_dlopen driver */
+       void (*log)(int level, const char *fmt, ...);
+       isc_result_t (*putrr)(dns_sdlzlookup_t *handle, const char *type,
+                             dns_ttl_t ttl, const char *data);
+       isc_result_t (*putnamedrr)(dns_sdlzlookup_t *handle, const char *name,
+                                  const char *type, dns_ttl_t ttl, const char *data);
+};
+
+/*
+  return the version of the API
+ */
+_PUBLIC_ int dlz_version(void)
+{
+       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;
+       }
+}
+
+/*
+  format a record for bind9
+ */
+static bool b9_format(struct dlz_bind9_data *state,
+                     TALLOC_CTX *mem_ctx,
+                     struct dnsp_DnssrvRpcRecord *rec,
+                     const char **type, const char **data)
+{
+       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 = rec->data.cname;
+               break;
+
+       case DNS_TYPE_TXT:
+               *type = "txt";
+               *data = rec->data.txt;
+               break;
+
+       case DNS_TYPE_PTR:
+               *type = "ptr";
+               *data = rec->data.ptr;
+               break;
+
+       case DNS_TYPE_SRV:
+               *type = "srv";
+               *data = talloc_asprintf(mem_ctx, "%u %u %u %s",
+                                       rec->data.srv.wPriority,
+                                       rec->data.srv.wWeight,
+                                       rec->data.srv.wPort,
+                                       rec->data.srv.nameTarget);
+               break;
+
+       case DNS_TYPE_MX:
+               *type = "mx";
+               *data = talloc_asprintf(mem_ctx, "%u %s",
+                                       rec->data.srv.wPriority,
+                                       rec->data.srv.nameTarget);
+               break;
+
+       case DNS_TYPE_HINFO:
+               *type = "hinfo";
+               *data = talloc_asprintf(mem_ctx, "%s %s",
+                                       rec->data.hinfo.cpu,
+                                       rec->data.hinfo.os);
+               break;
+
+       case DNS_TYPE_NS:
+               *type = "ns";
+               *data = rec->data.ns;
+               break;
+
+       case DNS_TYPE_SOA:
+               *type = "soa";
+               *data = talloc_asprintf(mem_ctx, "%s %s %u %u %u %u %u",
+                                       rec->data.soa.mname,
+                                       rec->data.soa.rname,
+                                       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 b9_putrr: unhandled record type %u",
+                          rec->wType);
+               return false;
+       }
+
+       return true;
+}
+
+/*
+  send a resource recond 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;
+               }
+       }
+
+       /* FIXME: why does dlz insist on all TTL values being the same
+          for the same name? */
+       result = state->putrr(handle, type, /* rec->dwTtlSeconds */ 900, 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 recond 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;
+       }
+
+       /* FIXME: why does dlz insist on all TTL values being the same
+          for the same name? */
+       result = state->putnamedrr(handle, name, type, /* rec->dwTtlSeconds */ 900, data);
+       if (result != ISC_R_SUCCESS) {
+               state->log(ISC_LOG_ERROR, "Failed to put named rr '%s'", name);
+       }
+       talloc_free(tmp_ctx);
+       return result;
+}
+
+
+/*
+  called to initialise the driver
+ */
+_PUBLIC_ isc_result_t dlz_create(const char *dlzname,
+                                unsigned int argc, char *argv[],
+                                void *driverarg, void **dbdata, ...)
+{
+       struct dlz_bind9_data *state;
+       const char *helper_name;
+       va_list ap;
+       isc_result_t result;
+       const char *url;
+       TALLOC_CTX *tmp_ctx;
+       int ret;
+       struct ldb_dn *dn;
+
+       state = talloc_zero(NULL, struct dlz_bind9_data);
+       if (state == NULL) {
+               return ISC_R_NOMEMORY;
+       }
+
+       tmp_ctx = talloc_new(state);
+
+       /* 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);
+
+       state->lp = loadparm_init_global(true);
+       if (state->lp == NULL) {
+               result = ISC_R_NOMEMORY;
+               goto failed;
+       }
+
+       state->ev_ctx = tevent_context_init(state);
+       if (state->ev_ctx == NULL) {
+               result = ISC_R_NOMEMORY;
+               goto failed;
+       }
+
+       state->samdb = ldb_init(state, state->ev_ctx);
+       if (state->samdb == NULL) {
+               state->log(ISC_LOG_ERROR, "samba dlz_bind9: Failed to create ldb");
+               result = ISC_R_FAILURE;
+               goto failed;
+       }
+
+       url = talloc_asprintf(tmp_ctx, "ldapi://%s",
+                             private_path(tmp_ctx, state->lp, "ldap_priv/ldapi"));
+       if (url == NULL) {
+               result = ISC_R_NOMEMORY;
+               goto failed;
+       }
+
+       ret = ldb_connect(state->samdb, url, 0, NULL);
+       if (ret == -1) {
+               state->log(ISC_LOG_ERROR, "samba dlz_bind9: Failed to connect to %s - %s",
+                          url, ldb_errstring(state->samdb));
+               result = ISC_R_FAILURE;
+               goto failed;
+       }
+
+       dn = ldb_get_default_basedn(state->samdb);
+       if (dn == NULL) {
+               state->log(ISC_LOG_ERROR, "samba dlz_bind9: Unable to get basedn for %s - %s",
+                          url, ldb_errstring(state->samdb));
+               result = ISC_R_FAILURE;
+               goto failed;
+       }
+
+       state->log(ISC_LOG_INFO, "samba dlz_bind9: started for DN %s",
+                  ldb_dn_get_linearized(dn));
+
+       *dbdata = state;
+
+       talloc_free(tmp_ctx);
+       return ISC_R_SUCCESS;
+
+failed:
+       talloc_free(state);
+       return result;
+}
+
+/*
+  shutdown the backend
+ */
+_PUBLIC_ void dlz_destroy(void *driverarg, void *dbdata)
+{
+       struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+       state->log(ISC_LOG_INFO, "samba dlz_bind9: shutting down");
+       talloc_free(state);
+}
+
+
+/*
+  see if we handle a given zone
+ */
+_PUBLIC_ isc_result_t dlz_findzonedb(void *driverarg, void *dbdata, const char *name)
+{
+       struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+       if (strcasecmp(lpcfg_dnsdomain(state->lp), name) == 0) {
+               return ISC_R_SUCCESS;
+       }
+       return ISC_R_NOTFOUND;
+}
+
+
+/*
+  lookup one record
+ */
+_PUBLIC_ isc_result_t dlz_lookup_types(struct dlz_bind9_data *state,
+                                      const char *zone, const char *name,
+                                      void *driverarg, dns_sdlzlookup_t *lookup,
+                                      const char **types)
+{
+       struct ldb_dn *dn;
+       TALLOC_CTX *tmp_ctx = talloc_new(state);
+       const char *attrs[] = { "dnsRecord", NULL };
+       int ret, i;
+       struct ldb_result *res;
+       struct ldb_message_element *el;
+
+       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, "DC=%s,DC=%s,CN=MicrosoftDNS,DC=DomainDnsZones",
+                                 name, zone)) {
+               talloc_free(tmp_ctx);
+               return ISC_R_NOMEMORY;
+       }
+
+       ret = ldb_search(state->samdb, tmp_ctx, &res, dn, LDB_SCOPE_BASE,
+                        attrs, "objectClass=dnsNode");
+       if (ret != LDB_SUCCESS) {
+               talloc_free(tmp_ctx);
+               return ISC_R_NOTFOUND;
+       }
+
+       el = ldb_msg_find_element(res->msgs[0], "dnsRecord");
+       if (el == NULL || el->num_values == 0) {
+               state->log(ISC_LOG_INFO, "failed to find %s",
+                          ldb_dn_get_linearized(dn));
+               talloc_free(tmp_ctx);
+               return ISC_R_NOTFOUND;
+       }
+
+       for (i=0; i<el->num_values; i++) {
+               struct dnsp_DnssrvRpcRecord rec;
+               enum ndr_err_code ndr_err;
+               isc_result_t result;
+
+               ndr_err = ndr_pull_struct_blob(&el->values[i], tmp_ctx, &rec,
+                                              (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
+               if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+                       state->log(ISC_LOG_ERROR, "samba dlz_bind9: failed to parse dnsRecord for %s",
+                                  ldb_dn_get_linearized(dn));
+                       talloc_free(tmp_ctx);
+                       return ISC_R_FAILURE;
+               }
+
+               result = b9_putrr(state, lookup, &rec, 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 *driverarg,
+                                void *dbdata, dns_sdlzlookup_t *lookup)
+{
+       struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+       return dlz_lookup_types(state, zone, name, driverarg, lookup, NULL);
+}
+
+
+/*
+  see if a zone transfer is allowed
+ */
+_PUBLIC_ isc_result_t dlz_allowzonexfr(void *driverarg, void *dbdata, const char *name,
+                                      const char *client)
+{
+       struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+
+       if (strcasecmp(lpcfg_dnsdomain(state->lp), name) == 0) {
+               /* TODO: check an ACL here? client is the IP of the requester */
+               state->log(ISC_LOG_INFO, "samba dlz_bind9: allowing zone transfer for '%s' by '%s'",
+                          name, client);
+               return ISC_R_SUCCESS;
+       }
+       return ISC_R_NOTFOUND;
+}
+
+/*
+  perform a zone transfer
+ */
+_PUBLIC_ isc_result_t dlz_allnodes(const char *zone, void *driverarg, void *dbdata,
+                                  dns_sdlzallnodes_t *allnodes)
+{
+       struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
+       const char *attrs[] = { "dnsRecord", NULL };
+       int ret, i, j;
+       struct ldb_dn *dn;
+       struct ldb_result *res;
+       TALLOC_CTX *tmp_ctx = talloc_new(state);
+
+
+       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, "DC=%s,CN=MicrosoftDNS,DC=DomainDnsZones", zone)) {
+               talloc_free(tmp_ctx);
+               return ISC_R_NOMEMORY;
+       }
+
+       ret = ldb_search(state->samdb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE,
+                        attrs, "objectClass=dnsNode");
+       if (ret != LDB_SUCCESS) {
+               talloc_free(tmp_ctx);
+               return ISC_R_NOTFOUND;
+       }
+
+       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;
+
+               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);
+                       return ISC_R_NOMEMORY;
+               }
+
+               if (strcmp(rdn, "@") == 0) {
+                       name = zone;
+               } else {
+                       name = talloc_asprintf(el_ctx, "%s.%s", rdn, zone);
+               }
+               if (name == NULL) {
+                       talloc_free(tmp_ctx);
+                       return ISC_R_NOMEMORY;
+               }
+
+               for (j=0; j<el->num_values; j++) {
+                       struct dnsp_DnssrvRpcRecord rec;
+                       enum ndr_err_code ndr_err;
+                       isc_result_t result;
+
+                       ndr_err = ndr_pull_struct_blob(&el->values[j], el_ctx, &rec,
+                                                      (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
+                       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+                               state->log(ISC_LOG_ERROR, "samba dlz_bind9: failed to parse dnsRecord for %s",
+                                          ldb_dn_get_linearized(dn));
+                               talloc_free(el_ctx);
+                               continue;
+                       }
+
+                       result = b9_putnamedrr(state, allnodes, name, &rec);
+                       if (result != ISC_R_SUCCESS) {
+                               talloc_free(el_ctx);
+                               continue;
+                       }
+               }
+       }
+
+       talloc_free(tmp_ctx);
+
+       return ISC_R_SUCCESS;
+}
diff --git a/source4/dns_server/dlz_bind9.h b/source4/dns_server/dlz_bind9.h
new file mode 100644 (file)
index 0000000..8cf0680
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+   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/>.
+*/
+
+/*
+  provide the minimal set of prototypes and defines for bind9 interop
+  The aim is to not require the bind9 source when building the
+  dlz_bind9 module
+ */
+typedef unsigned int isc_result_t;
+typedef uint32_t dns_ttl_t;
+
+#define DLZ_DLOPEN_VERSION 1
+
+/* result codes */
+#define ISC_R_SUCCESS                  0
+#define ISC_R_NOMEMORY                 1
+#define ISC_R_NOTFOUND                 23
+#define ISC_R_FAILURE                  25
+
+/* 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)
+
+/* a couple of opaque structures */
+struct dns_sdlzlookup;
+typedef struct dns_sdlzlookup dns_sdlzlookup_t;
+struct dns_sdlzallnodes;
+typedef struct dns_sdlzallnodes dns_sdlzallnodes_t;
index f19a5a2eda596eddf3e12b88db025c6efafb40b5..a543b12a06419bf5270b848eb9d76bd3abbb9272 100644 (file)
@@ -8,3 +8,10 @@ bld.SAMBA_MODULE('service_dns',
         local_include=False,
         internal_module=False,
         )
+
+# a bind9 dlz module giving access to the Samba DNS SAM
+bld.SAMBA_LIBRARY('dlz_bind9',
+                  source='dlz_bind9.c',
+                  private_library=True,
+                  link_name='modules/bind9/dlz_bind9.so',
+                  deps='samba-hostconfig ldbsamba samba-util')