diff options
| -rw-r--r-- | librpc/idl/dns.idl | 14 | ||||
| -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 | 
6 files changed, 366 insertions, 59 deletions
| diff --git a/librpc/idl/dns.idl b/librpc/idl/dns.idl index a92c418dc5..984f2b34a3 100644 --- a/librpc/idl/dns.idl +++ b/librpc/idl/dns.idl @@ -192,6 +192,20 @@ interface dns  		uint8      other_data[other_size];  	} dns_tsig_record; +	typedef [flag(NDR_NOALIGN|NDR_BIG_ENDIAN|NDR_PAHEX),public] struct { +		dns_string	name; +		dns_qclass 	rr_class; +		uint32     	ttl; +		dns_string 	algorithm_name; +		uint16     	time_prefix; /* 0 until February 2106*/ +		uint32     	time; +		uint16     	fudge; +		uint16     	original_id; +		uint16     	error; +		uint16     	other_size; +		uint8      	other_data[other_size]; +	} dns_fake_tsig_rec; +  	typedef [nodiscriminant,public,flag(NDR_NOALIGN)] union {  		[case(DNS_QTYPE_A)]     ipv4address	 ipv4_record;  		[case(DNS_QTYPE_NS)]    dns_string       ns_record; 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', | 
