#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, ...);
static const char *zone_prefixes[] = {
"CN=MicrosoftDNS,DC=DomainDnsZones",
"CN=MicrosoftDNS,DC=ForestDnsZones",
+ "CN=MicrosoftDNS,CN=System",
NULL
};
*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",
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,
/*
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;
}
return result;
}
-struct b9_options {
- const char *url;
-};
-
/*
parse options
*/
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;
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) {
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;
}
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;
}
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;
}
{
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;
{
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);
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);
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
*/
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;
}
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
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 &&
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");
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);
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;
}