From 7204dc9708e5f5164dcd9b7cc3fcb2ea27dcd62e Mon Sep 17 00:00:00 2001 From: Kai Blin Date: Thu, 30 Aug 2012 09:04:07 +0200 Subject: s4 dns: Negotiate GSSAPI-based TKEYs Autobuild-User(master): Kai Blin Autobuild-Date(master): Fri Aug 31 10:38:35 CEST 2012 on sn-devel-104 --- source4/dns_server/dns_query.c | 224 +++++++++++++++++++++++++++++++++++++++ source4/dns_server/dns_server.c | 18 ++++ source4/dns_server/dns_server.h | 11 ++ source4/dns_server/wscript_build | 2 +- 4 files changed, 254 insertions(+), 1 deletion(-) (limited to 'source4/dns_server') diff --git a/source4/dns_server/dns_query.c b/source4/dns_server/dns_query.c index 5978fe9597..e9c3a24b56 100644 --- a/source4/dns_server/dns_query.c +++ b/source4/dns_server/dns_query.c @@ -33,6 +33,10 @@ #include "libcli/dns/libdns.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" +#include "lib/util/dlinklist.h" static WERROR create_response_rr(const struct dns_name_question *question, const struct dnsp_DnssrvRpcRecord *rec, @@ -317,6 +321,214 @@ static WERROR handle_question(struct dns_server *dns, return WERR_OK; } +static NTSTATUS create_new_tkey(TALLOC_CTX *mem_ctx, + struct dns_server *dns, + struct dns_server_tkey **tkey, + const char* name) +{ + NTSTATUS status; + struct dns_server_tkey *k = talloc_zero(mem_ctx, struct dns_server_tkey); + + if (k == NULL) { + return NT_STATUS_NO_MEMORY; + } + + k->name = talloc_strdup(mem_ctx, name); + + if (k->name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = samba_server_gensec_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_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; + } + + *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; + + status = gensec_update(tkey->gensec, mem_ctx, dns->task->event_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 struct dns_server_tkey *find_tkey(struct dns_server *dns, + const char *name) +{ + struct dns_server_tkey *tkey = NULL; + + for (tkey = dns->tkeys; tkey != NULL; tkey = tkey->next) { + if (dns_name_equal(name, tkey->name)) { + break; + } + } + + return tkey; +} + +static WERROR handle_tkey(struct dns_server *dns, + TALLOC_CTX *mem_ctx, + const struct dns_name_packet *in, + 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_NOMEM; + } + + ret_tkey->name = talloc_strdup(ret_tkey, in_tkey->name); + if (ret_tkey->name == NULL) { + return WERR_NOMEM; + } + + 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, ret_tkey->name); + if (ret_tkey->rdata.tkey_record.algorithm == NULL) { + return WERR_NOMEM; + } + + 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 = find_tkey(dns, 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_new_tkey(dns, dns, &tkey, + in->questions[0].name); + if (!NT_STATUS_IS_OK(status)) { + ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADKEY; + return ntstatus_to_werror(status); + } + + DLIST_ADD_END(dns->tkeys, tkey, NULL); + } + + 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)) { + DEBUG(1, ("Tkey handshake completed\n")); + } else { + DEBUG(0, ("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 dns_res_rec *answers; uint16_t ancount; @@ -352,6 +564,18 @@ struct tevent_req *dns_server_process_query_send( return tevent_req_post(req, ev); } + if (in->questions[0].question_type == DNS_QTYPE_TKEY) { + WERROR err; + + err = handle_tkey(dns, state, in, &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); + } + if (dns_authorative_for_zone(dns, in->questions[0].name)) { WERROR err; diff --git a/source4/dns_server/dns_server.c b/source4/dns_server/dns_server.c index 3592258a8b..70fb6a262d 100644 --- a/source4/dns_server/dns_server.c +++ b/source4/dns_server/dns_server.c @@ -43,6 +43,8 @@ #include "auth/session.h" #include "lib/util/dlinklist.h" #include "lib/util/tevent_werror.h" +#include "auth/auth.h" +#include "auth/credentials/credentials.h" NTSTATUS server_service_dns_init(void); @@ -720,6 +722,22 @@ static void dns_task_init(struct task_server *task) 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; + } + + cli_credentials_set_conf(dns->server_credentials, task->lp_ctx); + 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; + } + dns->samdb = samdb_connect(dns, dns->task->event_ctx, dns->task->lp_ctx, system_session(dns->task->lp_ctx), 0); if (!dns->samdb) { diff --git a/source4/dns_server/dns_server.h b/source4/dns_server/dns_server.h index f871544ddd..c2fe6cf9e8 100644 --- a/source4/dns_server/dns_server.h +++ b/source4/dns_server/dns_server.h @@ -33,10 +33,21 @@ struct dns_server_zone { struct ldb_dn *dn; }; +struct dns_server_tkey { + struct dns_server_tkey *prev, *next; + const char *name; + enum dns_tkey_mode mode; + struct auth_session_info *session_info; + struct gensec_security *gensec; + bool complete; +}; + struct dns_server { struct task_server *task; struct ldb_context *samdb; struct dns_server_zone *zones; + struct dns_server_tkey *tkeys; + struct cli_credentials *server_credentials; }; struct dns_request_state { diff --git a/source4/dns_server/wscript_build b/source4/dns_server/wscript_build index c541d08d55..8cb23ee938 100644 --- a/source4/dns_server/wscript_build +++ b/source4/dns_server/wscript_build @@ -4,7 +4,7 @@ bld.SAMBA_MODULE('service_dns', source='dns_server.c dns_query.c dns_update.c dns_utils.c', subsystem='service', init_function='server_service_dns_init', - deps='samba-hostconfig LIBTSOCKET LIBSAMBA_TSOCKET ldbsamba clidns', + deps='samba-hostconfig LIBTSOCKET LIBSAMBA_TSOCKET ldbsamba clidns gensec auth', local_include=False, internal_module=False, enabled=bld.AD_DC_BUILD_IS_ENABLED() -- cgit