>From 7118680c3b7c1c436a641abf8b5c2ef4a94174b2 Mon Sep 17 00:00:00 2001
From: Tomas Hozza <thozza@redhat.com>
Date: Thu, 25 Jun 2015 14:53:31 +0200
Subject: [PATCH] nsupdate: Don't extract REAML from ticket, but leave it up to
 GSSAPI

The current implementation of nsupdate does not work correctly with
GSSAPI in cross realm trust scenarios. The realm is currently
extracted from local kerberos ticket instead of letting GSSAPI to
figure out the realm based on the remote nameserver hostname.

RFC 4752 section 3.1 states that the client should use
GSS_C_NT_HOSTBASED_SERVICE when calling gss_import_name().

nsupdate now leaves the realm detection up to GSSAPI, if the realm is
not specified explicitly using the 'realm' option. If the option is
used, the old behavior is preserved.

Signed-off-by: Tomas Hozza <thozza@redhat.com>
---
 bin/nsupdate/nsupdate.1       |  3 +-
 bin/nsupdate/nsupdate.c       | 68 ++++++++-----------------------------------
 bin/nsupdate/nsupdate.docbook |  2 +-
 bin/nsupdate/nsupdate.html    |  2 +-
 bin/tests/dst/gsstest.c       |  4 +--
 lib/dns/gssapictx.c           | 16 +++++++---
 lib/dns/include/dns/tkey.h    | 24 +++++++++------
 lib/dns/include/dst/gssapi.h  |  8 +++--
 lib/dns/tkey.c                | 28 +++++++++---------
 9 files changed, 64 insertions(+), 91 deletions(-)

diff --git a/bin/nsupdate/nsupdate.1 b/bin/nsupdate/nsupdate.1
index 94b029e..b208d6c 100644
--- a/bin/nsupdate/nsupdate.1
+++ b/bin/nsupdate/nsupdate.1
@@ -305,8 +305,7 @@ on the command line.
 .RS 4
 When using GSS\-TSIG use
 \fIrealm_name\fR
-rather than the default realm in
-\fIkrb5.conf\fR. If no realm is specified the saved realm is cleared.
+rather than leaving the realm detection up to GSSAPI. If no realm is specified the saved realm is cleared.
 .RE
 .PP
 \fBcheck\-names\fR {[yes_or_no]}
diff --git a/bin/nsupdate/nsupdate.c b/bin/nsupdate/nsupdate.c
index eca42b4..5cb0826 100644
--- a/bin/nsupdate/nsupdate.c
+++ b/bin/nsupdate/nsupdate.c
@@ -2688,56 +2688,6 @@ sendrequest(isc_sockaddr_t *destaddr, dns_message_t *msg,
 
 #ifdef GSSAPI
 
-/*
- * Get the realm from the users kerberos ticket if possible
- */
-static void
-get_ticket_realm(isc_mem_t *mctx) {
-	krb5_context ctx;
-	krb5_error_code rc;
-	krb5_ccache ccache;
-	krb5_principal princ;
-	char *name, *ticket_realm;
-
-	rc = krb5_init_context(&ctx);
-	if (rc != 0)
-		return;
-
-	rc = krb5_cc_default(ctx, &ccache);
-	if (rc != 0) {
-		krb5_free_context(ctx);
-		return;
-	}
-
-	rc = krb5_cc_get_principal(ctx, ccache, &princ);
-	if (rc != 0) {
-		krb5_cc_close(ctx, ccache);
-		krb5_free_context(ctx);
-		return;
-	}
-
-	rc = krb5_unparse_name(ctx, princ, &name);
-	if (rc != 0) {
-		krb5_free_principal(ctx, princ);
-		krb5_cc_close(ctx, ccache);
-		krb5_free_context(ctx);
-		return;
-	}
-
-	ticket_realm = strrchr(name, '@');
-	if (ticket_realm != NULL) {
-		realm = isc_mem_strdup(mctx, ticket_realm);
-	}
-
-	free(name);
-	krb5_free_principal(ctx, princ);
-	krb5_cc_close(ctx, ccache);
-	krb5_free_context(ctx);
-	if (realm != NULL && debugging)
-		fprintf(stderr, "Found realm from ticket: %s\n", realm+1);
-}
-
-
 static void
 start_gssrequest(dns_name_t *master) {
 	gss_ctx_id_t context;
@@ -2778,11 +2728,15 @@ start_gssrequest(dns_name_t *master) {
 	dns_fixedname_init(&fname);
 	servname = dns_fixedname_name(&fname);
 
-	if (realm == NULL)
-		get_ticket_realm(gmctx);
-
-	result = isc_string_printf(servicename, sizeof(servicename),
-				   "DNS/%s%s", namestr, realm ? realm : "");
+	if (realm != NULL) {
+		/* Use explicit REALM passed as argument */
+		result = isc_string_printf(servicename, sizeof(servicename),
+				   "DNS/%s%s", namestr, realm);
+	} else {
+		/* Use service@host as advised in RFC4752 section 3.1 */
+		result = isc_string_printf(servicename, sizeof(servicename),
+				   "DNS@%s", namestr);
+	}
 	if (result != ISC_R_SUCCESS)
 		fatal("isc_string_printf(servicename) failed: %s",
 		      isc_result_totext(result));
@@ -2821,7 +2775,8 @@ start_gssrequest(dns_name_t *master) {
 
 	/* Build first request. */
 	context = GSS_C_NO_CONTEXT;
-	result = dns_tkey_buildgssquery(rmsg, keyname, servname, NULL, 0,
+	result = dns_tkey_buildgssquery(rmsg, keyname, servname,
+					realm != NULL ? ISC_TRUE : ISC_FALSE, NULL, 0,
 					&context, use_win2k_gsstsig,
 					gmctx, &err_message);
 	if (result == ISC_R_FAILURE)
@@ -2960,6 +2915,7 @@ recvgss(isc_task_t *task, isc_event_t *event) {
 
 	tsigkey = NULL;
 	result = dns_tkey_gssnegotiate(tsigquery, rcvmsg, servname,
+				       realm != NULL ? ISC_TRUE : ISC_FALSE,
 				       &context, &tsigkey, gssring,
 				       use_win2k_gsstsig,
 				       &err_message);
diff --git a/bin/nsupdate/nsupdate.docbook b/bin/nsupdate/nsupdate.docbook
index daa6d7e..2113665 100644
--- a/bin/nsupdate/nsupdate.docbook
+++ b/bin/nsupdate/nsupdate.docbook
@@ -529,7 +529,7 @@
 	  <listitem>
 	    <para>
 	      When using GSS-TSIG use <parameter>realm_name</parameter> rather
-	      than the default realm in <filename>krb5.conf</filename>.  If no
+	      than leaving the realm detection up to GSSAPI.  If no
 	      realm is specified the saved realm is cleared.
 	    </para>
 	  </listitem>
diff --git a/bin/nsupdate/nsupdate.html b/bin/nsupdate/nsupdate.html
index 80b20d5..534eb00 100644
--- a/bin/nsupdate/nsupdate.html
+++ b/bin/nsupdate/nsupdate.html
@@ -361,7 +361,7 @@
 	  </span></dt>
 <dd><p>
 	      When using GSS-TSIG use <em class="parameter"><code>realm_name</code></em> rather
-	      than the default realm in <code class="filename">krb5.conf</code>.  If no
+	      than leaving the realm detection up to GSSAPI.  If no
 	      realm is specified the saved realm is cleared.
 	    </p></dd>
 <dt><span class="term">
diff --git a/bin/tests/dst/gsstest.c b/bin/tests/dst/gsstest.c
index 4ed37e8..f1aa2ae 100644
--- a/bin/tests/dst/gsstest.c
+++ b/bin/tests/dst/gsstest.c
@@ -306,7 +306,7 @@ initctx2(isc_task_t *task, isc_event_t *event) {
 	printf("Received token from server, calling gss_init_sec_context()\n");
 	isc_buffer_init(&outtoken, array, DNS_NAME_MAXTEXT + 1);
 	result = dns_tkey_processgssresponse(query, response,
-					     dns_fixedname_name(&gssname),
+					     dns_fixedname_name(&gssname), ISC_FALSE,
 					     &gssctx, &outtoken,
 					     &tsigkey, ring, NULL);
 	gssctx = *gssctxp;
@@ -396,7 +396,7 @@ initctx1(isc_task_t *task, isc_event_t *event) {
 	printf("Calling gss_init_sec_context()\n");
 	gssctx = GSS_C_NO_CONTEXT;
 	result = dns_tkey_buildgssquery(query, dns_fixedname_name(&servername),
-					dns_fixedname_name(&gssname),
+					dns_fixedname_name(&gssname), ISC_FALSE,
 					NULL, 36000, &gssctx, ISC_TRUE,
 					mctx, NULL);
 	CHECK("dns_tkey_buildgssquery", result);
diff --git a/lib/dns/gssapictx.c b/lib/dns/gssapictx.c
index 9cbfe79..010c227 100644
--- a/lib/dns/gssapictx.c
+++ b/lib/dns/gssapictx.c
@@ -562,14 +562,15 @@ gss_err_message(isc_mem_t *mctx, isc_uint32_t major, isc_uint32_t minor,
 #endif
 
 isc_result_t
-dst_gssapi_initctx(dns_name_t *name, isc_buffer_t *intoken,
-		   isc_buffer_t *outtoken, gss_ctx_id_t *gssctx,
-		   isc_mem_t *mctx, char **err_message)
+dst_gssapi_initctx(dns_name_t *name, isc_boolean_t explicit_realm,
+		   isc_buffer_t *intoken, isc_buffer_t *outtoken,
+		   gss_ctx_id_t *gssctx, isc_mem_t *mctx, char **err_message)
 {
 #ifdef GSSAPI
 	isc_region_t r;
 	isc_buffer_t namebuf;
 	gss_name_t gname;
+	gss_OID gname_type;
 	OM_uint32 gret, minor, ret_flags, flags;
 	gss_buffer_desc gintoken, *gintokenp, gouttoken = GSS_C_EMPTY_BUFFER;
 	isc_result_t result;
@@ -584,7 +585,13 @@ dst_gssapi_initctx(dns_name_t *name, isc_buffer_t *intoken,
 	name_to_gbuffer(name, &namebuf, &gnamebuf);
 
 	/* Get the name as a GSS name */
-	gret = gss_import_name(&minor, &gnamebuf, GSS_C_NO_OID, &gname);
+	if (explicit_realm == ISC_TRUE) {
+		gname_type = GSS_C_NO_OID;
+	} else {
+		gname_type = GSS_C_NT_HOSTBASED_SERVICE;
+	}
+
+	gret = gss_import_name(&minor, &gnamebuf, gname_type, &gname);
 	if (gret != GSS_S_COMPLETE) {
 		gss_err_message(mctx, gret, minor, err_message);
 		result = ISC_R_FAILURE;
@@ -646,6 +653,7 @@ dst_gssapi_initctx(dns_name_t *name, isc_buffer_t *intoken,
 	return (result);
 #else
 	UNUSED(name);
+	UNUSED(explicit_realm);
 	UNUSED(intoken);
 	UNUSED(outtoken);
 	UNUSED(gssctx);
diff --git a/lib/dns/include/dns/tkey.h b/lib/dns/include/dns/tkey.h
index 0dcec1e..a0e6c2a 100644
--- a/lib/dns/include/dns/tkey.h
+++ b/lib/dns/include/dns/tkey.h
@@ -123,9 +123,9 @@ dns_tkey_builddhquery(dns_message_t *msg, dst_key_t *key, dns_name_t *name,
 
 isc_result_t
 dns_tkey_buildgssquery(dns_message_t *msg, dns_name_t *name, dns_name_t *gname,
-		       isc_buffer_t *intoken, isc_uint32_t lifetime,
-		       gss_ctx_id_t *context, isc_boolean_t win2k,
-		       isc_mem_t *mctx, char **err_message);
+		       isc_boolean_t explicit_realm, isc_buffer_t *intoken,
+		       isc_uint32_t lifetime, gss_ctx_id_t *context,
+		       isc_boolean_t win2k, isc_mem_t *mctx, char **err_message);
 /*%<
  *	Builds a query containing a TKEY that will generate a GSSAPI context.
  *	The key is requested to have the specified lifetime (in seconds).
@@ -134,6 +134,8 @@ dns_tkey_buildgssquery(dns_message_t *msg, dns_name_t *name, dns_name_t *gname,
  *\li		'msg'	  is a valid message
  *\li		'name'	  is a valid name
  *\li		'gname'	  is a valid name
+ *\li		'explicit_realm' ISC_TRUE if an explicit realm is used,
+ *			  ISC_FALSE if the realm detection is left up to GSSAPI.
  *\li		'context' is a pointer to a valid gss_ctx_id_t
  *			  (which may have the value GSS_C_NO_CONTEXT)
  *\li		'win2k'   when true says to turn on some hacks to work
@@ -188,9 +190,10 @@ dns_tkey_processdhresponse(dns_message_t *qmsg, dns_message_t *rmsg,
 
 isc_result_t
 dns_tkey_processgssresponse(dns_message_t *qmsg, dns_message_t *rmsg,
-			    dns_name_t *gname, gss_ctx_id_t *context,
-			    isc_buffer_t *outtoken, dns_tsigkey_t **outkey,
-			    dns_tsig_keyring_t *ring, char **err_message);
+			    dns_name_t *gname, isc_boolean_t explicit_realm,
+			    gss_ctx_id_t *context, isc_buffer_t *outtoken,
+			    dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring,
+			    char **err_message);
 /*%<
  * XXX
  */
@@ -216,9 +219,10 @@ dns_tkey_processdeleteresponse(dns_message_t *qmsg, dns_message_t *rmsg,
 
 isc_result_t
 dns_tkey_gssnegotiate(dns_message_t *qmsg, dns_message_t *rmsg,
-		      dns_name_t *server, gss_ctx_id_t *context,
-		      dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring,
-		      isc_boolean_t win2k, char **err_message);
+		      dns_name_t *server, isc_boolean_t explicit_realm,
+		      gss_ctx_id_t *context, dns_tsigkey_t **outkey,
+		      dns_tsig_keyring_t *ring, isc_boolean_t win2k,
+		      char **err_message);
 
 /*
  *	Client side negotiation of GSS-TSIG.  Process the response
@@ -231,6 +235,8 @@ dns_tkey_gssnegotiate(dns_message_t *qmsg, dns_message_t *rmsg,
  *			     it will be filled with the new message to send
  *		'rmsg'    is a valid message, the incoming TKEY message
  *		'server'  is the server name
+ *		'explicit_realm' ISC_TRUE if an explicit realm is used,
+ *			      ISC_FALSE if the realm detection is left up to GSSAPI.
  *		'context' is the input context handle
  *		'outkey'  receives the established key, if non-NULL;
  *			      if non-NULL must point to NULL
diff --git a/lib/dns/include/dst/gssapi.h b/lib/dns/include/dst/gssapi.h
index 53c594e..2d220c0 100644
--- a/lib/dns/include/dst/gssapi.h
+++ b/lib/dns/include/dst/gssapi.h
@@ -93,15 +93,17 @@ dst_gssapi_releasecred(gss_cred_id_t *cred);
  */
 
 isc_result_t
-dst_gssapi_initctx(dns_name_t *name, isc_buffer_t *intoken,
-		   isc_buffer_t *outtoken, gss_ctx_id_t *gssctx,
-		   isc_mem_t *mctx, char **err_message);
+dst_gssapi_initctx(dns_name_t *name, isc_boolean_t explicit_realm,
+		   isc_buffer_t *intoken, isc_buffer_t *outtoken,
+		   gss_ctx_id_t *gssctx, isc_mem_t *mctx, char **err_message);
 /*
  *	Initiates a GSS context.
  *
  *	Requires:
  * 	'name'     is a valid name, preferably one known by the GSS
  * 	provider
+ * 	'explicit_realm' True if the REALM is explicitly included in the 'name',
+ *  	   otherwise leave the REALM detection up to GSSAPI
  * 	'intoken'  is a token received from the acceptor, or NULL if
  *		   there isn't one
  * 	'outtoken' is a buffer to receive the token generated by
diff --git a/lib/dns/tkey.c b/lib/dns/tkey.c
index 66210d5..87d1815 100644
--- a/lib/dns/tkey.c
+++ b/lib/dns/tkey.c
@@ -1017,9 +1017,9 @@ dns_tkey_builddhquery(dns_message_t *msg, dst_key_t *key, dns_name_t *name,
 
 isc_result_t
 dns_tkey_buildgssquery(dns_message_t *msg, dns_name_t *name, dns_name_t *gname,
-		       isc_buffer_t *intoken, isc_uint32_t lifetime,
-		       gss_ctx_id_t *context, isc_boolean_t win2k,
-		       isc_mem_t *mctx, char **err_message)
+		       isc_boolean_t explicit_realm, isc_buffer_t *intoken,
+		       isc_uint32_t lifetime, gss_ctx_id_t *context,
+		       isc_boolean_t win2k, isc_mem_t *mctx, char **err_message)
 {
 	dns_rdata_tkey_t tkey;
 	isc_result_t result;
@@ -1036,7 +1036,7 @@ dns_tkey_buildgssquery(dns_message_t *msg, dns_name_t *name, dns_name_t *gname,
 	REQUIRE(mctx != NULL);
 
 	isc_buffer_init(&token, array, sizeof(array));
-	result = dst_gssapi_initctx(gname, NULL, &token, context,
+	result = dst_gssapi_initctx(gname, explicit_realm, NULL, &token, context,
 				    mctx, err_message);
 	if (result != DNS_R_CONTINUE && result != ISC_R_SUCCESS)
 		return (result);
@@ -1252,9 +1252,10 @@ dns_tkey_processdhresponse(dns_message_t *qmsg, dns_message_t *rmsg,
 
 isc_result_t
 dns_tkey_processgssresponse(dns_message_t *qmsg, dns_message_t *rmsg,
-			    dns_name_t *gname, gss_ctx_id_t *context,
-			    isc_buffer_t *outtoken, dns_tsigkey_t **outkey,
-			    dns_tsig_keyring_t *ring, char **err_message)
+			    dns_name_t *gname, isc_boolean_t explicit_realm,
+			    gss_ctx_id_t *context, isc_buffer_t *outtoken,
+			    dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring,
+			    char **err_message)
 {
 	dns_rdata_t rtkeyrdata = DNS_RDATA_INIT, qtkeyrdata = DNS_RDATA_INIT;
 	dns_name_t *tkeyname;
@@ -1305,7 +1306,7 @@ dns_tkey_processgssresponse(dns_message_t *qmsg, dns_message_t *rmsg,
 
 	isc_buffer_init(outtoken, array, sizeof(array));
 	isc_buffer_init(&intoken, rtkey.key, rtkey.keylen);
-	RETERR(dst_gssapi_initctx(gname, &intoken, outtoken, context,
+	RETERR(dst_gssapi_initctx(gname, explicit_realm, &intoken, outtoken, context,
 				  ring->mctx, err_message));
 
 	RETERR(dst_key_fromgssapi(dns_rootname, *context, rmsg->mctx,
@@ -1385,9 +1386,10 @@ dns_tkey_processdeleteresponse(dns_message_t *qmsg, dns_message_t *rmsg,
 
 isc_result_t
 dns_tkey_gssnegotiate(dns_message_t *qmsg, dns_message_t *rmsg,
-		      dns_name_t *server, gss_ctx_id_t *context,
-		      dns_tsigkey_t **outkey, dns_tsig_keyring_t *ring,
-		      isc_boolean_t win2k, char **err_message)
+		      dns_name_t *server, isc_boolean_t explicit_realm,
+		      gss_ctx_id_t *context, dns_tsigkey_t **outkey,
+		      dns_tsig_keyring_t *ring, isc_boolean_t win2k,
+		      char **err_message)
 {
 	dns_rdata_t rtkeyrdata = DNS_RDATA_INIT, qtkeyrdata = DNS_RDATA_INIT;
 	dns_name_t *tkeyname;
@@ -1433,8 +1435,8 @@ dns_tkey_gssnegotiate(dns_message_t *qmsg, dns_message_t *rmsg,
 	isc_buffer_init(&intoken, rtkey.key, rtkey.keylen);
 	isc_buffer_init(&outtoken, array, sizeof(array));
 
-	result = dst_gssapi_initctx(server, &intoken, &outtoken, context,
-				    ring->mctx, err_message);
+	result = dst_gssapi_initctx(server, explicit_realm, &intoken, &outtoken,
+					context, ring->mctx, err_message);
 	if (result != DNS_R_CONTINUE && result != ISC_R_SUCCESS)
 		return (result);
 
-- 
2.4.3

