dlz_bind9: For creating a child entry, use only SEC_ADS_CREATE_CHILD
[kai/samba.git] / source4 / dns_server / dlz_bind9.c
index 3c8107a3f230070497dbf7f7df917fa1217c3774..cb4144d5987a4e1cb58ac03e6bc7ccceab9e16ec 100644 (file)
 #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 "lib/cmdline/popt_common.h"
-#include "lib/cmdline/popt_credentials.h"
-#include "ldb_module.h"
+#include "gen_ndr/server_id.h"
+#include "messaging/messaging.h"
 #include "dlz_minimal.h"
 
+
+struct b9_options {
+       const char *url;
+};
+
 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;
+
+       /* Used for dynamic update */
+       struct smb_krb5_context *smb_krb5_ctx;
 
        /* helper functions from the dlz_dlopen driver */
        void (*log)(int level, const char *fmt, ...);
@@ -52,6 +66,7 @@ struct dlz_bind9_data {
 static const char *zone_prefixes[] = {
        "CN=MicrosoftDNS,DC=DomainDnsZones",
        "CN=MicrosoftDNS,DC=ForestDnsZones",
+       "CN=MicrosoftDNS,CN=System",
        NULL
 };
 
@@ -149,7 +164,7 @@ static bool b9_format(struct dlz_bind9_data *state,
                *type = "soa";
 
                /* we need to fake the authoritative nameserver to
-                * point at ourselves. This is now AD DNS servers
+                * 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",
@@ -162,6 +177,8 @@ static bool b9_format(struct dlz_bind9_data *state,
                        return false;
                }
 
+               state->soa_serial = rec->data.soa.serial;
+
                *data = talloc_asprintf(mem_ctx, "%s %s %u %u %u %u %u",
                                        mname,
                                        rec->data.soa.rname,
@@ -217,15 +234,16 @@ static bool b9_single_valued(enum dns_record_type dns_type)
 /*
   see if a DNS type is single valued
  */
-static enum dns_record_type b9_dns_type(const char *type)
+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) {
-                       return dns_typemap[i].dns_type;
+                       *dtype = dns_typemap[i].dns_type;
+                       return true;
                }
        }
-       return DNS_TYPE_ZERO;
+       return false;
 }
 
 
@@ -410,10 +428,6 @@ static isc_result_t b9_putnamedrr(struct dlz_bind9_data *state,
        return result;
 }
 
-struct b9_options {
-       const char *url;
-};
-
 /*
    parse options
  */
@@ -421,42 +435,12 @@ static isc_result_t parse_options(struct dlz_bind9_data *state,
                                  unsigned int argc, 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" },
-               { NULL }
-       };
-       struct poptOption **popt_options;
-       int ret;
-
-       fault_setup_disable();
-
-       popt_options = ldb_module_popt_options(state->samdb);
-       (*popt_options) = long_options;
-
-       ret = ldb_modules_hook(state->samdb, LDB_MODULE_HOOK_CMDLINE_OPTIONS);
-       if (ret != LDB_SUCCESS) {
-               state->log(ISC_LOG_ERROR, "dlz samba: failed cmdline hook");
-               return ISC_R_FAILURE;
-       }
-
-       pc = poptGetContext("dlz_bind9", argc, (const char **)argv, *popt_options,
-                           POPT_CONTEXT_KEEP_FIRST);
-
-       while ((opt = poptGetNextOpt(pc)) != -1) {
-               switch (opt) {
-               default:
-                       state->log(ISC_LOG_ERROR, "dlz samba: Invalid option %s: %s",
-                                  poptBadOption(pc, 0), poptStrerror(opt));
-                       return ISC_R_FAILURE;
+       if (argc == 2) {
+               options->url = talloc_strdup(state, argv[1]);
+               if (options->url == NULL) {
+                       return ISC_R_NOMEMORY;
                }
-       }
-
-       ret = ldb_modules_hook(state->samdb, LDB_MODULE_HOOK_CMDLINE_PRECONNECT);
-       if (ret != LDB_SUCCESS) {
-               state->log(ISC_LOG_ERROR, "dlz samba: failed cmdline preconnect");
-               return ISC_R_FAILURE;
+               state->log(ISC_LOG_INFO, "samba_dlz: Using samdb URL %s", options->url);
        }
 
        return ISC_R_SUCCESS;
@@ -475,11 +459,8 @@ _PUBLIC_ isc_result_t dlz_create(const char *dlzname,
        va_list ap;
        isc_result_t result;
        TALLOC_CTX *tmp_ctx;
-       int ret;
        struct ldb_dn *dn;
-       struct b9_options options;
-
-       ZERO_STRUCT(options);
+       NTSTATUS nt_status;
 
        state = talloc_zero(NULL, struct dlz_bind9_data);
        if (state == NULL) {
@@ -501,14 +482,7 @@ _PUBLIC_ isc_result_t dlz_create(const char *dlzname,
                goto failed;
        }
 
-       state->samdb = ldb_init(state, state->ev_ctx);
-       if (state->samdb == NULL) {
-               state->log(ISC_LOG_ERROR, "samba_dlz: Failed to create ldb");
-               result = ISC_R_FAILURE;
-               goto failed;
-       }
-
-       result = parse_options(state, argc, argv, &options);
+       result = parse_options(state, argc, argv, &state->options);
        if (result != ISC_R_SUCCESS) {
                goto failed;
        }
@@ -519,27 +493,33 @@ _PUBLIC_ isc_result_t dlz_create(const char *dlzname,
                goto failed;
        }
 
-       if (options.url == NULL) {
-               options.url = talloc_asprintf(tmp_ctx, "ldapi://%s",
-                                             private_path(tmp_ctx, state->lp, "ldap_priv/ldapi"));
-               if (options.url == NULL) {
+       if (smb_krb5_init_context(state, state->ev_ctx, state->lp, &state->smb_krb5_ctx) != 0) {
+               result = ISC_R_NOMEMORY;
+               goto failed;
+       }
+
+       nt_status = gensec_init();
+       if (!NT_STATUS_IS_OK(nt_status)) {
+               talloc_free(tmp_ctx);
+               return false;
+       }
+
+       if (state->options.url == NULL) {
+               state->options.url = lpcfg_private_path(state, state->lp, "dns/sam.ldb");
+               if (state->options.url == NULL) {
                        result = ISC_R_NOMEMORY;
                        goto failed;
                }
        }
 
-       ret = ldb_connect(state->samdb, options.url, 0, NULL);
-       if (ret == -1) {
-               state->log(ISC_LOG_ERROR, "samba_dlz: Failed to connect to %s - %s",
-                          options.url, ldb_errstring(state->samdb));
-               result = ISC_R_FAILURE;
-               goto failed;
-       }
+       /* Do not install samba signal handlers */
+       fault_setup_disable();
 
-       ret = ldb_modules_hook(state->samdb, LDB_MODULE_HOOK_CMDLINE_POSTCONNECT);
-       if (ret != LDB_SUCCESS) {
-               state->log(ISC_LOG_ERROR, "samba_dlz: Failed postconnect for %s - %s",
-                          options.url, ldb_errstring(state->samdb));
+       state->samdb = samdb_connect_url(state, state->ev_ctx, state->lp,
+                                       system_session(state->lp), 0, state->options.url);
+       if (state->samdb == NULL) {
+               state->log(ISC_LOG_ERROR, "samba_dlz: Failed to connect to %s",
+                       state->options.url);
                result = ISC_R_FAILURE;
                goto failed;
        }
@@ -547,7 +527,7 @@ _PUBLIC_ isc_result_t dlz_create(const char *dlzname,
        dn = ldb_get_default_basedn(state->samdb);
        if (dn == NULL) {
                state->log(ISC_LOG_ERROR, "samba_dlz: Unable to get basedn for %s - %s",
-                          options.url, ldb_errstring(state->samdb));
+                          state->options.url, ldb_errstring(state->samdb));
                result = ISC_R_FAILURE;
                goto failed;
        }
@@ -677,7 +657,7 @@ static isc_result_t dlz_lookup_types(struct dlz_bind9_data *state,
 {
        TALLOC_CTX *tmp_ctx = talloc_new(state);
        const char *attrs[] = { "dnsRecord", NULL };
-       int ret, i;
+       int ret = LDB_SUCCESS, i;
        struct ldb_result *res;
        struct ldb_message_element *el;
        struct ldb_dn *dn;
@@ -764,7 +744,7 @@ _PUBLIC_ isc_result_t dlz_allnodes(const char *zone, void *dbdata,
 {
        struct dlz_bind9_data *state = talloc_get_type_abort(dbdata, struct dlz_bind9_data);
        const char *attrs[] = { "dnsRecord", NULL };
-       int ret, i, j;
+       int ret = LDB_SUCCESS, i, j;
        struct ldb_dn *dn;
        struct ldb_result *res;
        TALLOC_CTX *tmp_ctx = talloc_new(state);
@@ -1010,10 +990,18 @@ _PUBLIC_ isc_result_t dlz_configure(dns_view_t *view, void *dbdata)
                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;
                        }
-                       if (!b9_has_soa(state, dn, zone)) {
+                       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;
                        }
                        result = state->writeable_zone(view, zone);
@@ -1031,6 +1019,17 @@ _PUBLIC_ isc_result_t dlz_configure(dns_view_t *view, void *dbdata)
        return ISC_R_SUCCESS;
 }
 
+static char *strlower(char *str)
+{
+       int i;
+
+       for (i=0; i<strlen(str); i++) {
+               str[i] = (char) tolower(str[i]);
+       }
+
+       return str;
+}
+
 /*
   authorize a zone update
  */
@@ -1039,9 +1038,127 @@ _PUBLIC_ isc_boolean_t dlz_ssumatch(const char *signer, const char *name, const
                                    void *dbdata)
 {
        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, *username;
+       bool 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 result;
+       struct ldb_result *res;
+       const char * attrs[] = { NULL };
+       uint32_t access_mask;
+
+       tmp_ctx = talloc_new(NULL);
+       if (tmp_ctx == NULL) {
+               state->log(ISC_LOG_ERROR, "samba_dlz: no memory");
+               return false;
+       }
+
+       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);
+               return false;
+       }
+
+       cli_credentials_set_krb5_context(server_credentials, state->smb_krb5_ctx);
+       cli_credentials_set_conf(server_credentials, state->lp);
+
+       username = talloc_asprintf(tmp_ctx, "dns-%s", lpcfg_netbios_name(state->lp));
+       username = strlower(username);
+       cli_credentials_set_username(server_credentials, username, CRED_SPECIFIED);
+       talloc_free(username);
+
+       keytab_name = talloc_asprintf(tmp_ctx, "file:%s/dns.keytab",
+                                       lpcfg_private_dir(state->lp));
+       ret = cli_credentials_set_keytab_name(server_credentials, state->lp, keytab_name,
+                                               CRED_SPECIFIED);
+       talloc_free(keytab_name);
+       if (ret != 0) {
+               state->log(ISC_LOG_ERROR, "samba_dlz: failed to obtain server credentials for %s",
+                               username);
+               talloc_free(tmp_ctx);
+               return false;
+       }
+
+       nt_status = gensec_server_start(tmp_ctx,
+                                       lpcfg_gensec_settings(tmp_ctx, state->lp),
+                                       NULL, &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);
+               return false;
+       }
+
+       gensec_set_credentials(gensec_ctx, server_credentials);
+       gensec_set_target_service(gensec_ctx, "dns");
+
+       nt_status = gensec_start_mech_by_name(gensec_ctx, "spnego");
+       if (!NT_STATUS_IS_OK(nt_status)) {
+               state->log(ISC_LOG_ERROR, "samba_dlz: failed to start spnego");
+               talloc_free(tmp_ctx);
+               return false;
+       }
+
+       nt_status = gensec_update(gensec_ctx, tmp_ctx, state->ev_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);
+               return false;
+       }
+
+       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);
+               return false;
+       }
+
+       /* Get the DN from name */
+       result = b9_find_name_dn(state, name, tmp_ctx, &dn);
+       if (result != ISC_R_SUCCESS) {
+               state->log(ISC_LOG_ERROR, "samba_dlz: failed to find name %s", name);
+               talloc_free(tmp_ctx);
+               return false;
+       }
+
+       /* 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);
+               return false;
+       }
 
-       state->log(ISC_LOG_INFO, "samba_dlz: allowing update of signer=%s name=%s tcpaddr=%s type=%s key=%s keydatalen=%u",
-                  signer, name, tcpaddr, type, key, keydatalen);
+       /* 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);
+               return false;
+       }
+
+       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);
        return true;
 }
 
@@ -1085,6 +1202,21 @@ static isc_result_t b9_add_record(struct dlz_bind9_data *state, const char *name
        return ISC_R_SUCCESS;
 }
 
+/*
+  see if two DNS names are the same
+ */
+static bool dns_name_equal(const char *name1, const char *name2)
+{
+       size_t len1 = strlen(name1);
+       size_t len2 = strlen(name2);
+       if (name1[len1-1] == '.') len1--;
+       if (name2[len2-1] == '.') len2--;
+       if (len1 != len2) {
+               return false;
+       }
+       return strncasecmp_m(name1, name2, len1) == 0;
+}
+
 
 /*
   see if two dns records match
@@ -1107,31 +1239,31 @@ static bool b9_record_match(struct dlz_bind9_data *state,
        case DNS_TYPE_AAAA:
                return strcmp(rec1->data.ipv6, rec2->data.ipv6) == 0;
        case DNS_TYPE_CNAME:
-               return strcmp(rec1->data.cname, rec2->data.cname) == 0;
+               return dns_name_equal(rec1->data.cname, rec2->data.cname);
        case DNS_TYPE_TXT:
                return strcmp(rec1->data.txt, rec2->data.txt) == 0;
        case DNS_TYPE_PTR:
                return strcmp(rec1->data.ptr, rec2->data.ptr) == 0;
        case DNS_TYPE_NS:
-               return strcmp(rec1->data.ns, rec2->data.ns) == 0;
+               return 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 &&
-                       strcmp(rec1->data.srv.nameTarget, rec2->data.srv.nameTarget) == 0;
+                       dns_name_equal(rec1->data.srv.nameTarget, rec2->data.srv.nameTarget);
 
        case DNS_TYPE_MX:
                return rec1->data.mx.wPriority == rec2->data.mx.wPriority &&
-                       strcmp(rec1->data.mx.nameTarget, rec2->data.mx.nameTarget) == 0;
+                       dns_name_equal(rec1->data.mx.nameTarget, rec2->data.mx.nameTarget);
 
        case DNS_TYPE_HINFO:
                return strcmp(rec1->data.hinfo.cpu, rec2->data.hinfo.cpu) == 0 &&
                        strcmp(rec1->data.hinfo.os, rec2->data.hinfo.os) == 0;
 
        case DNS_TYPE_SOA:
-               return strcmp(rec1->data.soa.mname, rec2->data.soa.mname) == 0 &&
-                       strcmp(rec1->data.soa.rname, rec2->data.soa.rname) == 0 &&
+               return dns_name_equal(rec1->data.soa.mname, rec2->data.soa.mname) &&
+                       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 &&
@@ -1161,6 +1293,7 @@ _PUBLIC_ isc_result_t dlz_addrdataset(const char *name, const char *rdatastr, vo
        int ret, i;
        struct ldb_message_element *el;
        enum ndr_err_code ndr_err;
+       NTTIME t;
 
        if (state->transaction_token != (void*)version) {
                state->log(ISC_LOG_INFO, "samba_dlz: bad transaction version");
@@ -1172,6 +1305,14 @@ _PUBLIC_ isc_result_t dlz_addrdataset(const char *name, const char *rdatastr, vo
                return ISC_R_NOMEMORY;
        }
 
+       unix_to_nt_time(&t, time(NULL));
+       t /= 10*1000*1000; /* convert to seconds (NT time is in 100ns units) */
+       t /= 3600;         /* convert to hours */
+
+       rec->rank        = DNS_RANK_ZONE;
+       rec->dwSerial    = state->soa_serial;
+       rec->dwTimeStamp = (uint32_t)t;
+
        if (!b9_parse(state, rdatastr, rec)) {
                state->log(ISC_LOG_INFO, "samba_dlz: failed to parse rdataset '%s'", rdatastr);
                talloc_free(rec);
@@ -1383,8 +1524,7 @@ _PUBLIC_ isc_result_t dlz_delrdataset(const char *name, const char *type, void *
                return ISC_R_FAILURE;
        }
 
-       dns_type = b9_dns_type(type);
-       if (dns_type == DNS_TYPE_ZERO) {
+       if (!b9_dns_type(type, &dns_type)) {
                state->log(ISC_LOG_INFO, "samba_dlz: bad dns type %s in delete", type);
                return ISC_R_FAILURE;
        }