diff options
author | Kai Blin <kai@samba.org> | 2012-09-02 21:43:52 +0200 |
---|---|---|
committer | Kai Blin <kai@samba.org> | 2012-09-05 08:41:23 +0200 |
commit | fc9de264972ba46cfd9e8fc67e25aa7ee1fd51a2 (patch) | |
tree | 3c16f8803b069c135466f43bef6abd5296d9c9de /source4/dns_server | |
parent | 15e3991b39d2e9496d01d18479db2804804a39f6 (diff) | |
download | samba-fc9de264972ba46cfd9e8fc67e25aa7ee1fd51a2.tar.gz samba-fc9de264972ba46cfd9e8fc67e25aa7ee1fd51a2.tar.bz2 samba-fc9de264972ba46cfd9e8fc67e25aa7ee1fd51a2.zip |
s4 dns: Handle GSS-TSIG signatures
Diffstat (limited to 'source4/dns_server')
-rw-r--r-- | source4/dns_server/dns_crypto.c | 269 | ||||
-rw-r--r-- | source4/dns_server/dns_query.c | 110 | ||||
-rw-r--r-- | source4/dns_server/dns_server.c | 14 | ||||
-rw-r--r-- | source4/dns_server/dns_server.h | 16 | ||||
-rw-r--r-- | source4/dns_server/wscript_build | 2 |
5 files changed, 352 insertions, 59 deletions
diff --git a/source4/dns_server/dns_crypto.c b/source4/dns_server/dns_crypto.c new file mode 100644 index 0000000000..ab422221c0 --- /dev/null +++ b/source4/dns_server/dns_crypto.c @@ -0,0 +1,269 @@ +/* + 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 "lib/crypto/hmacmd5.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" + +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 (dns_name_equal(name, tmp_key->name)) { + tkey = tmp_key; + break; + } + } while (i != 0); + + return tkey; +} + +WERROR dns_verify_tsig(struct dns_server *dns, + struct dns_request_state *state, + struct dns_name_packet *packet) +{ + WERROR werror; + bool found_tsig = false; + uint16_t i, mac_length = 0; + struct dns_server_tkey *tkey = NULL; + uint8_t *mac = NULL; + + /* 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) { + found_tsig = true; + break; + } + } + + if (!found_tsig) { + return WERR_OK; + } + + /* The TSIG record needs to be the last additional record */ + if (found_tsig && i + 1 != packet->arcount) { + DEBUG(0, ("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; + + state->tsig = talloc_zero(state, struct dns_res_rec); + W_ERROR_HAVE_NO_MEMORY(state->tsig); + + werror = dns_copy_tsig(state->tsig, &packet->additional[i], + state->tsig); + W_ERROR_NOT_OK_RETURN(werror); + + packet->arcount--; + + tkey = dns_find_tkey(dns->tkeys, state->tsig->name); + if (tkey == NULL) { + state->tsig_error = DNS_RCODE_BADKEY; + return DNS_ERR(NOTAUTH); + } + + /* FIXME: check TSIG here */ + + dump_data(1, mac, mac_length); + state->tsig_error = DNS_RCODE_BADKEY; + return DNS_ERR(NOTAUTH); +} + +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; + NTSTATUS status; + enum ndr_err_code ndr_err; + time_t current_time = time(NULL); + DATA_BLOB packet_blob, tsig_blob, sig; + uint8_t *buffer = NULL; + size_t buffer_len = 0; + struct dns_server_tkey * tkey = NULL; + struct dns_res_rec *tsig = talloc_zero(mem_ctx, struct dns_res_rec); + + struct dns_fake_tsig_rec *check_rec = talloc_zero(mem_ctx, + struct dns_fake_tsig_rec); + + if (tsig == NULL) { + return WERR_NOMEM; + } + + if (check_rec == NULL) { + return WERR_NOMEM; + } + + tkey = dns_find_tkey(dns->tkeys, state->key_name); + if (tkey == NULL) { + /* FIXME: read up on what to do when we can't find a key */ + return WERR_OK; + } + + /* first build and verify check packet */ + check_rec->name = talloc_strdup(check_rec, tkey->name); + if (check_rec->name == NULL) { + return WERR_NOMEM; + } + 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_NOMEM; + } + 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); + } + + buffer_len = packet_blob.length + tsig_blob.length; + buffer = talloc_zero_array(mem_ctx, uint8_t, buffer_len); + if (buffer == NULL) { + return WERR_NOMEM; + } + + memcpy(buffer, packet_blob.data, packet_blob.length); + memcpy(buffer, 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); + } + + tsig->name = talloc_strdup(tsig, check_rec->name); + if (tsig->name == NULL) { + return WERR_NOMEM; + } + tsig->rr_class = check_rec->rr_class; + tsig->rr_type = DNS_QTYPE_TSIG; + tsig->ttl = 0; + tsig->length = UINT16_MAX; + tsig->rdata.tsig_record.algorithm_name = talloc_strdup(tsig, + check_rec->algorithm_name); + tsig->rdata.tsig_record.time_prefix = check_rec->time_prefix; + tsig->rdata.tsig_record.time = check_rec->time; + tsig->rdata.tsig_record.fudge = check_rec->fudge; + 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; + tsig->rdata.tsig_record.mac_size = sig.length; + tsig->rdata.tsig_record.mac = talloc_memdup(tsig, sig.data, sig.length); + + + if (packet->arcount == 0) { + packet->additional = talloc_zero(mem_ctx, struct dns_res_rec); + if (packet->additional == NULL) { + return WERR_NOMEM; + } + } + packet->additional = talloc_realloc(mem_ctx, packet->additional, + struct dns_res_rec, + packet->arcount + 1); + if (packet->additional == NULL) { + return WERR_NOMEM; + } + + werror = dns_copy_tsig(mem_ctx, tsig, + &packet->additional[packet->arcount]); + 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 index 530b7b22bd..98ebc63d97 100644 --- a/source4/dns_server/dns_query.c +++ b/source4/dns_server/dns_query.c @@ -320,62 +320,9 @@ static WERROR handle_question(struct dns_server *dns, return WERR_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_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 (dns_name_equal(name, tmp_key->name)) { - tkey = tmp_key; - break; - } - } while (i != 0); - - return tkey; -} - static NTSTATUS create_tkey(struct dns_server *dns, const char* name, + const char* algorithm, struct dns_server_tkey **tkey) { NTSTATUS status; @@ -392,6 +339,11 @@ static NTSTATUS create_tkey(struct dns_server *dns, return NT_STATUS_NO_MEMORY; } + k->algorithm = talloc_strdup(k, algorithm); + if (k->algorithm == NULL) { + return NT_STATUS_NO_MEMORY; + } + status = samba_server_gensec_start(k, dns->task->event_ctx, dns->task->msg_ctx, @@ -428,9 +380,40 @@ static NTSTATUS create_tkey(struct dns_server *dns, 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 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) { @@ -466,7 +449,8 @@ static WERROR handle_tkey(struct dns_server *dns, 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); + 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_NOMEM; } @@ -487,7 +471,7 @@ static WERROR handle_tkey(struct dns_server *dns, DATA_BLOB key; DATA_BLOB reply; - tkey = find_tkey(dns->tkeys, in->questions[0].name); + 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")); @@ -497,6 +481,7 @@ static WERROR handle_tkey(struct dns_server *dns, if (tkey == NULL) { status = create_tkey(dns, in->questions[0].name, + in_tkey->rdata.tkey_record.algorithm, &tkey); if (!NT_STATUS_IS_OK(status)) { ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADKEY; @@ -514,6 +499,15 @@ static WERROR handle_tkey(struct dns_server *dns, ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADKEY; } else if (NT_STATUS_IS_OK(status)) { DEBUG(1, ("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); + state->sign = true; + state->key_name = talloc_strdup(mem_ctx, tkey->name); + if (state->key_name == NULL) { + return WERR_NOMEM; + } } else { DEBUG(0, ("GSS key negotiation returned %s\n", nt_errstr(status))); ret_tkey->rdata.tkey_record.error = DNS_RCODE_BADKEY; @@ -582,8 +576,8 @@ struct tevent_req *dns_server_process_query_send( if (in->questions[0].question_type == DNS_QTYPE_TKEY) { WERROR err; - err = handle_tkey(dns, state, in, &state->answers, - &state->ancount); + err = handle_tkey(dns, state, in, req_state, + &state->answers, &state->ancount); if (tevent_req_werror(req, err)) { return tevent_req_post(req, ev); } diff --git a/source4/dns_server/dns_server.c b/source4/dns_server/dns_server.c index 887fc8ee1d..cd121f9d8b 100644 --- a/source4/dns_server/dns_server.c +++ b/source4/dns_server/dns_server.c @@ -98,6 +98,7 @@ static void dns_tcp_send(struct stream_connection *conn, uint16_t flags) struct dns_process_state { DATA_BLOB *in; + struct dns_server *dns; struct dns_name_packet in_packet; struct dns_request_state state; uint16_t dns_err; @@ -123,6 +124,8 @@ static struct tevent_req *dns_process_send(TALLOC_CTX *mem_ctx, } state->in = in; + state->dns = dns; + if (in->length < 12) { tevent_req_werror(req, WERR_INVALID_PARAM); return tevent_req_post(req, ev); @@ -142,6 +145,8 @@ static struct tevent_req *dns_process_send(TALLOC_CTX *mem_ctx, NDR_PRINT_DEBUG(dns_name_packet, &state->in_packet); } + ret = dns_verify_tsig(dns, &state->state, &state->in_packet); + state->state.flags = state->in_packet.operation; state->state.flags |= DNS_FLAG_REPLY; @@ -215,6 +220,15 @@ static WERROR dns_process_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, } 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)) { + state->dns_err = DNS_RCODE_SERVFAIL; + goto drop; + } + } + ndr_err = ndr_push_struct_blob( out, mem_ctx, &state->out_packet, (ndr_push_flags_fn_t)ndr_push_dns_name_packet); diff --git a/source4/dns_server/dns_server.h b/source4/dns_server/dns_server.h index 42ae0ba660..6cd60267c0 100644 --- a/source4/dns_server/dns_server.h +++ b/source4/dns_server/dns_server.h @@ -36,6 +36,7 @@ struct dns_server_zone { 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; @@ -59,6 +60,11 @@ struct dns_server { struct dns_request_state { uint16_t flags; + bool authenticated; + bool sign; + char *key_name; + struct dns_res_rec *tsig; + uint16_t tsig_error; }; struct tevent_req *dns_server_process_query_send( @@ -101,6 +107,16 @@ 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, + struct dns_request_state *state, + struct dns_name_packet *packet); +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); #define DNS_ERR(err_str) WERR_DNS_ERROR_RCODE_##err_str #endif /* __DNS_SERVER_H__ */ diff --git a/source4/dns_server/wscript_build b/source4/dns_server/wscript_build index 8cb23ee938..6989f2567b 100644 --- a/source4/dns_server/wscript_build +++ b/source4/dns_server/wscript_build @@ -1,7 +1,7 @@ #!/usr/bin/env python bld.SAMBA_MODULE('service_dns', - source='dns_server.c dns_query.c dns_update.c dns_utils.c', + 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', |