changeset 10721:2a4f0c5ca772

6791302 RPCSEC_GSS svc should be able to handle a misbehaving client
author Glenn Barry <Glenn.Barry@Sun.COM>
date Mon, 05 Oct 2009 19:17:50 -0700
parents c45747dcac4f
children ead952aa2ab7
files usr/src/uts/common/rpc/sec_gss/svc_rpcsec_gss.c usr/src/uts/common/rpc/svc.c usr/src/uts/common/rpc/svc.h
diffstat 3 files changed, 809 insertions(+), 505 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/uts/common/rpc/sec_gss/svc_rpcsec_gss.c	Mon Oct 05 17:27:50 2009 -0700
+++ b/usr/src/uts/common/rpc/sec_gss/svc_rpcsec_gss.c	Mon Oct 05 19:17:50 2009 -0700
@@ -19,12 +19,10 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 /*
  * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved.
  *
@@ -45,6 +43,8 @@
 #include <gssapi/gssapi_ext.h>
 #include <rpc/rpc.h>
 #include <rpc/rpcsec_defs.h>
+#include <sys/sunddi.h>
+#include <sys/atomic.h>
 
 extern bool_t __rpc_gss_make_principal(rpc_gss_principal_t *, gss_buffer_t);
 
@@ -222,6 +222,32 @@
 	svc_rpc_gss_unwrap,
 };
 
+/* taskq(9F) */
+typedef struct svcrpcsec_gss_taskq_arg {
+	SVCXPRT			*rq_xprt;
+	rpc_gss_init_arg	*rpc_call_arg;
+	struct rpc_msg		*msg;
+	svc_rpc_gss_data	*client_data;
+	uint_t			cr_version;
+	rpc_gss_service_t	cr_service;
+} svcrpcsec_gss_taskq_arg_t;
+
+/* gssd is single threaded, so 1 thread for the taskq is probably good/ok */
+int rpcsec_gss_init_taskq_nthreads = 1;
+static ddi_taskq_t *svcrpcsec_gss_init_taskq = NULL;
+
+extern struct rpc_msg *rpc_msg_dup(struct rpc_msg *);
+extern void rpc_msg_free(struct rpc_msg **, int);
+
+/*
+ * from svc_clts.c:
+ * Transport private data.
+ * Kept in xprt->xp_p2buf.
+ */
+struct udp_data {
+	mblk_t	*ud_resp;			/* buffer for response */
+	mblk_t	*ud_inmp;			/* mblk chain of request */
+};
 
 /*ARGSUSED*/
 static int
@@ -266,15 +292,23 @@
 	mutex_init(&ctx_mutex, NULL, MUTEX_DEFAULT, NULL);
 	rw_init(&cred_lock, NULL, RW_DEFAULT, NULL);
 	clients = (svc_rpc_gss_data **)
-		kmem_zalloc(svc_rpc_gss_hashmod * sizeof (svc_rpc_gss_data *),
-			KM_SLEEP);
+	    kmem_zalloc(svc_rpc_gss_hashmod * sizeof (svc_rpc_gss_data *),
+	    KM_SLEEP);
 	svc_data_handle = kmem_cache_create("rpc_gss_data_cache",
-					    sizeof (svc_rpc_gss_data), 0,
-					    svc_gss_data_create,
-					    svc_gss_data_destroy,
-					    svc_gss_data_reclaim,
-					    NULL, NULL, 0);
+	    sizeof (svc_rpc_gss_data), 0,
+	    svc_gss_data_create,
+	    svc_gss_data_destroy,
+	    svc_gss_data_reclaim,
+	    NULL, NULL, 0);
 
+	if (svcrpcsec_gss_init_taskq == NULL) {
+		svcrpcsec_gss_init_taskq = ddi_taskq_create(NULL,
+		    "rpcsec_gss_init_taskq", rpcsec_gss_init_taskq_nthreads,
+		    TASKQ_DEFAULTPRI, 0);
+		if (svcrpcsec_gss_init_taskq == NULL)
+			cmn_err(CE_NOTE,
+			    "svc_gss_init: ddi_taskq_create failed");
+	}
 }
 
 /*
@@ -641,48 +675,29 @@
 	return (TRUE);
 }
 
-
 /*
- * Server side authentication for RPCSEC_GSS.
+ * do_gss_accept is called from a taskq and does all the work for a
+ * RPCSEC_GSS_INIT call (mostly calling kgss_accept_sec_context()).
  */
-enum auth_stat
-__svcrpcsec_gss(rqst, msg, no_dispatch)
-	struct svc_req		*rqst;
-	struct rpc_msg		*msg;
-	bool_t			*no_dispatch;
+static enum auth_stat
+do_gss_accept(
+	SVCXPRT *xprt,
+	rpc_gss_init_arg *call_arg,
+	struct rpc_msg *msg,
+	svc_rpc_gss_data *client_data,
+	uint_t cr_version,
+	rpc_gss_service_t cr_service)
 {
-	XDR			xdrs;
-	rpc_gss_creds		creds;
-	rpc_gss_init_arg	call_arg;
-	rpc_gss_init_res	call_res, *retrans_result;
+	rpc_gss_init_res	call_res;
 	gss_buffer_desc		output_token;
 	OM_uint32		gssstat, minor, minor_stat, time_rec;
-	struct opaque_auth	*cred;
-	svc_rpc_gss_data	*client_data;
 	int			ret_flags, ret;
-	svc_rpc_gss_parms_t	*gss_parms;
 	gss_OID 		mech_type = GSS_C_NULL_OID;
 	int			free_mech_type = 1;
-
-	*no_dispatch = FALSE;
+	struct svc_req		r, *rqst;
 
-	/*
-	 * Initialize response verifier to NULL verifier.  If
-	 * necessary, this will be changed later.
-	 */
-	rqst->rq_xprt->xp_verf.oa_flavor = AUTH_NONE;
-	rqst->rq_xprt->xp_verf.oa_base = NULL;
-	rqst->rq_xprt->xp_verf.oa_length = 0;
-
-	/*
-	 * Need to null out results to start with.
-	 */
-	bzero((char *)&call_res, sizeof (call_res));
-
-	/*
-	 * Pull out and check credential and verifier.
-	 */
-	cred = &msg->rm_call.cb_cred;
+	rqst = &r;
+	rqst->rq_xprt = xprt;
 
 	/*
 	 * Initialize output_token.
@@ -690,77 +705,8 @@
 	output_token.length = 0;
 	output_token.value = NULL;
 
-	if (cred->oa_length == 0) {
-		RPCGSS_LOG0(1, "_svcrpcsec_gss: zero length cred\n");
-		return (AUTH_BADCRED);
-	}
-
-	xdrmem_create(&xdrs, cred->oa_base, cred->oa_length, XDR_DECODE);
-	bzero((char *)&creds, sizeof (creds));
-	if (!__xdr_rpc_gss_creds(&xdrs, &creds)) {
-		XDR_DESTROY(&xdrs);
-		RPCGSS_LOG0(1, "_svcrpcsec_gss: can't decode creds\n");
-		ret = AUTH_BADCRED;
-		goto error;
-	}
-	XDR_DESTROY(&xdrs);
+	bzero((char *)&call_res, sizeof (call_res));
 
-	/*
-	 * If this is a control message and proc is GSSAPI_INIT, then
-	 * create a client handle for this client.  Otherwise, look up
-	 * the existing handle.
-	 */
-	if (creds.gss_proc == RPCSEC_GSS_INIT) {
-		if (creds.ctx_handle.length != 0) {
-			RPCGSS_LOG0(1, "_svcrpcsec_gss: ctx_handle not null\n");
-			ret = AUTH_BADCRED;
-			goto error;
-		}
-		if ((client_data = create_client()) == NULL) {
-			RPCGSS_LOG0(1,
-			"_svcrpcsec_gss: can't create a new cache entry\n");
-			ret = AUTH_FAILED;
-			goto error;
-		}
-	} else {
-		/*
-		 * Only verify values for service parameter when proc
-		 * not RPCSEC_GSS_INIT or RPCSEC_GSS_CONTINUE_INIT.
-		 * RFC2203 says contents for sequence and service args
-		 * are undefined for creation procs.
-		 *
-		 * Note: only need to check for *CONTINUE_INIT here because
-		 *	 if() clause already checked for RPCSEC_GSS_INIT
-		 */
-		if (creds.gss_proc != RPCSEC_GSS_CONTINUE_INIT) {
-			switch (creds.service) {
-			case rpc_gss_svc_none:
-			case rpc_gss_svc_integrity:
-			case rpc_gss_svc_privacy:
-				break;
-			default:
-				RPCGSS_LOG(1, "_svcrpcsec_gss: unknown service "
-					"type: 0x%x\n", creds.service);
-				ret = AUTH_BADCRED;
-				goto error;
-			}
-		}
-		if (creds.ctx_handle.length == 0) {
-			RPCGSS_LOG0(1, "_svcrpcsec_gss: no ctx_handle\n");
-			ret = AUTH_BADCRED;
-			goto error;
-		}
-		if ((client_data = get_client(&creds.ctx_handle)) == NULL) {
-			ret = RPCSEC_GSS_NOCRED;
-			RPCGSS_LOG0(1, "_svcrpcsec_gss: no security context\n");
-			goto error;
-		}
-	}
-
-	/*
-	 * lock the client data until it's safe; if it's already stale,
-	 * no more processing is possible
-	 */
 	mutex_enter(&client_data->clm);
 	if (client_data->stale) {
 		ret = RPCSEC_GSS_NOCRED;
@@ -776,6 +722,345 @@
 	call_res.ctx_handle.value = (char *)&client_data->key;
 	call_res.seq_window = SEQ_WIN;
 
+	gssstat = GSS_S_FAILURE;
+	minor = 0;
+	minor_stat = 0;
+	rw_enter(&cred_lock, RW_READER);
+
+	if (client_data->client_name.length) {
+		(void) gss_release_buffer(&minor,
+		    &client_data->client_name);
+	}
+	gssstat = kgss_accept_sec_context(&minor_stat,
+	    &client_data->context,
+	    GSS_C_NO_CREDENTIAL,
+	    call_arg,
+	    GSS_C_NO_CHANNEL_BINDINGS,
+	    &client_data->client_name,
+	    &mech_type,
+	    &output_token,
+	    &ret_flags,
+	    &time_rec,
+	    NULL,		/* don't need a delegated cred back */
+	    crgetuid(CRED()));
+
+	RPCGSS_LOG(4, "gssstat 0x%x \n", gssstat);
+
+	if (gssstat == GSS_S_COMPLETE) {
+		/*
+		 * Set the raw and unix credentials at this
+		 * point.  This saves a lot of computation
+		 * later when credentials are retrieved.
+		 */
+		client_data->raw_cred.version = cr_version;
+		client_data->raw_cred.service = cr_service;
+
+		if (client_data->raw_cred.mechanism) {
+			kgss_free_oid(client_data->raw_cred.mechanism);
+			client_data->raw_cred.mechanism = NULL;
+		}
+		client_data->raw_cred.mechanism = (rpc_gss_OID) mech_type;
+		/*
+		 * client_data is now responsible for freeing
+		 * the data of 'mech_type'.
+		 */
+		free_mech_type = 0;
+
+		if (client_data->raw_cred.client_principal) {
+			kmem_free((caddr_t)client_data->\
+			    raw_cred.client_principal,
+			    client_data->raw_cred.\
+			    client_principal->len + sizeof (int));
+			client_data->raw_cred.client_principal = NULL;
+		}
+
+		/*
+		 *  The client_name returned from
+		 *  kgss_accept_sec_context() is in an
+		 *  exported flat format.
+		 */
+		if (! __rpc_gss_make_principal(
+		    &client_data->raw_cred.client_principal,
+		    &client_data->client_name)) {
+			RPCGSS_LOG0(1, "_svcrpcsec_gss: "
+			    "make principal failed\n");
+			gssstat = GSS_S_FAILURE;
+			(void) gss_release_buffer(&minor_stat, &output_token);
+		}
+	}
+
+	rw_exit(&cred_lock);
+
+	call_res.gss_major = gssstat;
+	call_res.gss_minor = minor_stat;
+
+	if (gssstat != GSS_S_COMPLETE &&
+	    gssstat != GSS_S_CONTINUE_NEEDED) {
+		call_res.ctx_handle.length = 0;
+		call_res.ctx_handle.value = NULL;
+		call_res.seq_window = 0;
+		rpc_gss_display_status(gssstat, minor_stat, mech_type,
+		    crgetuid(CRED()),
+		    "_svc_rpcsec_gss gss_accept_sec_context");
+		(void) svc_sendreply(rqst->rq_xprt,
+		    __xdr_rpc_gss_init_res, (caddr_t)&call_res);
+		client_data->stale = TRUE;
+		ret = AUTH_OK;
+		goto error2;
+	}
+
+	/*
+	 * If appropriate, set established to TRUE *after* sending
+	 * response (otherwise, the client will receive the final
+	 * token encrypted)
+	 */
+	if (gssstat == GSS_S_COMPLETE) {
+		/*
+		 * Context is established.  Set expiration time
+		 * for the context.
+		 */
+		client_data->seq_num = 1;
+		if ((time_rec == GSS_C_INDEFINITE) || (time_rec == 0)) {
+			client_data->expiration = GSS_C_INDEFINITE;
+		} else {
+			client_data->expiration =
+			    time_rec + gethrestime_sec();
+		}
+
+		if (!transfer_sec_context(client_data)) {
+			ret = RPCSEC_GSS_FAILED;
+			client_data->stale = TRUE;
+			RPCGSS_LOG0(1,
+			    "_svc_rpcsec_gss: transfer sec context failed\n");
+			goto error2;
+		}
+
+		client_data->established = TRUE;
+	}
+
+	/*
+	 * This step succeeded.  Send a response, along with
+	 * a token if there's one.  Don't dispatch.
+	 */
+
+	if (output_token.length != 0)
+		GSS_COPY_BUFFER(call_res.token, output_token);
+
+	/*
+	 * If GSS_S_COMPLETE: set response verifier to
+	 * checksum of SEQ_WIN
+	 */
+	if (gssstat == GSS_S_COMPLETE) {
+		if (!set_response_verf(rqst, msg, client_data,
+		    (uint_t)SEQ_WIN)) {
+			ret = RPCSEC_GSS_FAILED;
+			client_data->stale = TRUE;
+			RPCGSS_LOG0(1,
+			    "_svc_rpcsec_gss:set response verifier failed\n");
+			goto error2;
+		}
+	}
+
+	if (!svc_sendreply(rqst->rq_xprt, __xdr_rpc_gss_init_res,
+	    (caddr_t)&call_res)) {
+		ret = RPCSEC_GSS_FAILED;
+		client_data->stale = TRUE;
+		RPCGSS_LOG0(1, "_svc_rpcsec_gss:send reply failed\n");
+		goto error2;
+	}
+
+	/*
+	 * Cache last response in case it is lost and the client
+	 * retries on an established context.
+	 */
+	(void) retrans_add(client_data, msg->rm_xid, &call_res);
+	ASSERT(client_data->ref_cnt > 0);
+	client_data->ref_cnt--;
+	mutex_exit(&client_data->clm);
+
+	(void) gss_release_buffer(&minor_stat, &output_token);
+
+	return (AUTH_OK);
+
+error2:
+	ASSERT(client_data->ref_cnt > 0);
+	client_data->ref_cnt--;
+	mutex_exit(&client_data->clm);
+	(void) gss_release_buffer(&minor_stat, &output_token);
+	if (free_mech_type && mech_type)
+		kgss_free_oid(mech_type);
+
+	return (ret);
+}
+
+static void
+svcrpcsec_gss_taskq_func(void *svcrpcsecgss_taskq_arg)
+{
+	enum auth_stat retval;
+	svcrpcsec_gss_taskq_arg_t *arg = svcrpcsecgss_taskq_arg;
+
+	retval = do_gss_accept(arg->rq_xprt, arg->rpc_call_arg, arg->msg,
+	    arg->client_data, arg->cr_version, arg->cr_service);
+	if (retval != AUTH_OK) {
+		cmn_err(CE_NOTE,
+		    "svcrpcsec_gss_taskq_func:  do_gss_accept fail 0x%x",
+		    retval);
+	}
+	rpc_msg_free(&arg->msg, MAX_AUTH_BYTES);
+	svc_clone_unlink(arg->rq_xprt);
+	svc_clone_free(arg->rq_xprt);
+	xdr_free(__xdr_rpc_gss_init_arg, (caddr_t)arg->rpc_call_arg);
+	kmem_free(arg->rpc_call_arg, sizeof (*arg->rpc_call_arg));
+
+	kmem_free(arg, sizeof (*arg));
+}
+
+static enum auth_stat
+rpcsec_gss_init(
+	struct svc_req		*rqst,
+	struct rpc_msg		*msg,
+	rpc_gss_creds		creds,
+	bool_t			*no_dispatch,
+	svc_rpc_gss_data	*c_d) /* client data, can be NULL */
+{
+	svc_rpc_gss_data	*client_data;
+	int ret;
+	svcrpcsec_gss_taskq_arg_t *arg;
+
+	if (creds.ctx_handle.length != 0) {
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: ctx_handle not null\n");
+		ret = AUTH_BADCRED;
+		return (ret);
+	}
+
+	client_data = c_d ? c_d : create_client();
+	if (client_data == NULL) {
+		RPCGSS_LOG0(1,
+		    "_svcrpcsec_gss: can't create a new cache entry\n");
+		ret = AUTH_FAILED;
+		return (ret);
+	}
+
+	mutex_enter(&client_data->clm);
+	if (client_data->stale) {
+		ret = RPCSEC_GSS_NOCRED;
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: client data stale\n");
+		goto error2;
+	}
+
+	/*
+	 * kgss_accept_sec_context()/gssd(1M) can be overly time
+	 * consuming so let's queue it and return asap.
+	 *
+	 * taskq func must free arg.
+	 */
+	arg = kmem_alloc(sizeof (*arg), KM_SLEEP);
+
+	/* taskq func must free rpc_call_arg & deserialized arguments */
+	arg->rpc_call_arg = kmem_alloc(sizeof (*arg->rpc_call_arg), KM_SLEEP);
+
+	/* deserialize arguments */
+	bzero(arg->rpc_call_arg, sizeof (*arg->rpc_call_arg));
+	if (!SVC_GETARGS(rqst->rq_xprt, __xdr_rpc_gss_init_arg,
+	    (caddr_t)arg->rpc_call_arg)) {
+		ret = RPCSEC_GSS_FAILED;
+		client_data->stale = TRUE;
+		goto error2;
+	}
+
+	/* get a xprt clone for taskq thread, taskq func must free it */
+	arg->rq_xprt = svc_clone_init();
+	svc_clone_link(rqst->rq_xprt->xp_master, arg->rq_xprt);
+	arg->rq_xprt->xp_xid = rqst->rq_xprt->xp_xid;
+
+	/* UDP uses the incoming mblk for the response, so dup it. */
+	/* Note this probably should be done by svc_clone_link().  */
+	if (rqst->rq_xprt->xp_type == T_CLTS) {
+		struct udp_data *ud_src =
+		    (struct udp_data *)rqst->rq_xprt->xp_p2buf;
+		struct udp_data *ud_dst =
+		    (struct udp_data *)arg->rq_xprt->xp_p2buf;
+		if (ud_src->ud_resp) {
+			ud_dst->ud_resp = dupb(ud_src->ud_resp);
+		}
+	}
+
+	/* set the appropriate wrap/unwrap routine for RPCSEC_GSS */
+	arg->rq_xprt->xp_auth.svc_ah_ops = svc_rpc_gss_ops;
+	arg->rq_xprt->xp_auth.svc_ah_private = (caddr_t)client_data;
+
+	/* get a dup of rpc msg for taskq thread */
+	arg->msg = rpc_msg_dup(msg);  /* taskq func must free msg dup */
+
+	arg->client_data = client_data;
+	arg->cr_version = creds.version;
+	arg->cr_service = creds.service;
+
+	/* should be ok to hold clm lock as taskq will have new thread(s) */
+	ret = ddi_taskq_dispatch(svcrpcsec_gss_init_taskq,
+	    svcrpcsec_gss_taskq_func, arg, DDI_SLEEP);
+	if (ret == DDI_FAILURE) {
+		cmn_err(CE_NOTE, "rpcsec_gss_init: taskq dispatch fail");
+		ret = RPCSEC_GSS_FAILED;
+		rpc_msg_free(&arg->msg, MAX_AUTH_BYTES);
+		svc_clone_unlink(arg->rq_xprt);
+		svc_clone_free(arg->rq_xprt);
+		kmem_free(arg, sizeof (*arg));
+		goto error2;
+	}
+
+	mutex_exit(&client_data->clm);
+	*no_dispatch = TRUE;
+	return (AUTH_OK);
+
+error2:
+	ASSERT(client_data->ref_cnt > 0);
+	client_data->ref_cnt--;
+	mutex_exit(&client_data->clm);
+	cmn_err(CE_NOTE, "rpcsec_gss_init: error 0x%x", ret);
+	return (ret);
+}
+
+static enum auth_stat
+rpcsec_gss_continue_init(
+	struct svc_req		*rqst,
+	struct rpc_msg		*msg,
+	rpc_gss_creds		creds,
+	bool_t			*no_dispatch)
+{
+	int ret;
+	svc_rpc_gss_data	*client_data;
+	svc_rpc_gss_parms_t	*gss_parms;
+	rpc_gss_init_res	*retrans_result;
+
+	if (creds.ctx_handle.length == 0) {
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: no ctx_handle\n");
+		ret = AUTH_BADCRED;
+		return (ret);
+	}
+	if ((client_data = get_client(&creds.ctx_handle)) == NULL) {
+		ret = RPCSEC_GSS_NOCRED;
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: no security context\n");
+		return (ret);
+	}
+
+	mutex_enter(&client_data->clm);
+	if (client_data->stale) {
+		ret = RPCSEC_GSS_NOCRED;
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: client data stale\n");
+		goto error2;
+	}
+
+	/*
+	 * If context not established, go thru INIT code but with
+	 * this client handle.
+	 */
+	if (!client_data->established) {
+		mutex_exit(&client_data->clm);
+		return (rpcsec_gss_init(rqst, msg, creds, no_dispatch,
+		    client_data));
+	}
+
 	/*
 	 * Set the appropriate wrap/unwrap routine for RPCSEC_GSS.
 	 */
@@ -795,405 +1080,329 @@
 	gss_parms->context = (void *)client_data->context;
 	gss_parms->seq_num = creds.seq_num;
 
-	if (!client_data->established) {
-		if (creds.gss_proc == RPCSEC_GSS_DATA) {
-			RPCGSS_LOG0(1, "_svcrpcsec_gss: data exchange "
-				"message but context not established\n");
-
-			ret = RPCSEC_GSS_FAILED;
-			client_data->stale = TRUE;
-			goto error2;
-		}
-
-		/*
-		 * If the context is not established, then only
-		 * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT
-		 * requests are valid.
-		 */
-		if (creds.gss_proc != RPCSEC_GSS_INIT && creds.gss_proc !=
-						RPCSEC_GSS_CONTINUE_INIT) {
-			RPCGSS_LOG(1, "_svcrpcsec_gss: not an INIT or "
-				"CONTINUE_INIT message (0x%x) and context not "
-				"established\n", creds.gss_proc);
-
-			ret = RPCSEC_GSS_FAILED;
-			client_data->stale = TRUE;
-			goto error2;
-		}
-
-		/*
-		 * call is for us, deserialize arguments
-		 */
-		bzero(&call_arg, sizeof (call_arg));
-		if (!SVC_GETARGS(rqst->rq_xprt, __xdr_rpc_gss_init_arg,
-							(caddr_t)&call_arg)) {
-			RPCGSS_LOG0(1, "_svcrpcsec_gss: SVC_GETARGS failed\n");
-			ret = RPCSEC_GSS_FAILED;
-			client_data->stale = TRUE;
-			goto error2;
-		}
-
-		gssstat = GSS_S_FAILURE;
-		minor = 0;
-		minor_stat = 0;
-		rw_enter(&cred_lock, RW_READER);
-
-		if (client_data->client_name.length) {
-			(void) gss_release_buffer(&minor,
-				&client_data->client_name);
-		}
-		gssstat = kgss_accept_sec_context(&minor_stat,
-				&client_data->context,
-				GSS_C_NO_CREDENTIAL,
-				&call_arg,
-				GSS_C_NO_CHANNEL_BINDINGS,
-				&client_data->client_name,
-				&mech_type,
-				&output_token,
-				&ret_flags,
-				&time_rec,
-				/*
-				 * Don't need a delegated cred back.
-				 * No memory will be allocated if
-				 * passing NULL.
-				 */
-				NULL,
-				crgetuid(CRED()));
-
-		RPCGSS_LOG(4, "gssstat 0x%x \n", gssstat);
-
-		if (gssstat == GSS_S_COMPLETE) {
-			/*
-			 * Server_creds was right - set it.  Also
-			 * set the raw and unix credentials at this
-			 * point.  This saves a lot of computation
-			 * later when credentials are retrieved.
-			 */
-			client_data->raw_cred.version = creds.version;
-			client_data->raw_cred.service = creds.service;
-
-			if (client_data->raw_cred.mechanism) {
-			    kgss_free_oid(client_data->\
-				raw_cred.mechanism);
-			    client_data->raw_cred.mechanism = NULL;
-			}
-			client_data->raw_cred.mechanism =
-				(rpc_gss_OID) mech_type;
-			/*
-			 * client_data is now responsible for freeing
-			 * the data of 'mech_type'.
-			 */
-			free_mech_type = 0;
-
-			if (client_data->raw_cred.client_principal) {
-			    kmem_free((caddr_t)client_data->\
-				raw_cred.client_principal,
-				client_data->raw_cred.\
-				client_principal->len + sizeof (int));
-			    client_data->raw_cred.client_principal =
-				NULL;
-			}
-			/*
-			 *  The client_name returned from
-			 *  kgss_accept_sec_context() is in an
-			 *  exported flat format.
-			 */
-			if (! __rpc_gss_make_principal(
-			    &client_data->raw_cred.client_principal,
-			    &client_data->client_name)) {
-				RPCGSS_LOG0(1, "_svcrpcsec_gss: "
-				    "make principal failed\n");
-				gssstat = GSS_S_FAILURE;
-				(void) gss_release_buffer(&minor_stat,
-							&output_token);
-			}
-		}
-
-		rw_exit(&cred_lock);
-
-		call_res.gss_major = gssstat;
-		call_res.gss_minor = minor_stat;
-
-		xdr_free(__xdr_rpc_gss_init_arg, (caddr_t)&call_arg);
-
-		if (gssstat != GSS_S_COMPLETE &&
-		    gssstat != GSS_S_CONTINUE_NEEDED) {
-			/*
-			 * We have a failure - send response and delete
-			 * the context.  Don't dispatch.  Set ctx_handle
-			 * to NULL and seq_window to 0.
-			 */
-			call_res.ctx_handle.length = 0;
-			call_res.ctx_handle.value = NULL;
-			call_res.seq_window = 0;
-			rpc_gss_display_status(gssstat,
-				minor_stat,
-				mech_type,
-				crgetuid(CRED()),
-				"_svc_rpcsec_gss gss_accept_sec_context");
+	/*
+	 * This is an established context. Continue to
+	 * satisfy retried continue init requests out of
+	 * the retransmit cache.  Throw away any that don't
+	 * have a matching xid or the cach is empty.
+	 * Delete the retransmit cache once the client sends
+	 * a data request.
+	 */
+	if (client_data->retrans_data &&
+	    (client_data->retrans_data->xid == msg->rm_xid)) {
+		retrans_result = &client_data->retrans_data->result;
+		if (set_response_verf(rqst, msg, client_data,
+		    (uint_t)retrans_result->seq_window)) {
+			gss_parms->established = FALSE;
 			(void) svc_sendreply(rqst->rq_xprt,
-				__xdr_rpc_gss_init_res, (caddr_t)&call_res);
-			*no_dispatch = TRUE;
-			client_data->stale = TRUE;
-			ret = AUTH_OK;
-			goto error2;
-		}
-
-		/*
-		 * If appropriate, set established to TRUE *after* sending
-		 * response (otherwise, the client will receive the final
-		 * token encrypted)
-		 */
-
-		if (gssstat == GSS_S_COMPLETE) {
-			/*
-			 * Context is established.  Set expiration time
-			 * for the context.
-			 */
-			client_data->seq_num = 1;
-			if ((time_rec == GSS_C_INDEFINITE) || (time_rec == 0)) {
-				client_data->expiration = GSS_C_INDEFINITE;
-			} else {
-				client_data->expiration =
-				    time_rec + gethrestime_sec();
-			}
-
-			if (!transfer_sec_context(client_data)) {
-				ret = RPCSEC_GSS_FAILED;
-				client_data->stale = TRUE;
-				RPCGSS_LOG0(1,
-			    "_svc_rpcsec_gss: transfer sec context failed\n");
-				goto error2;
-			}
-
-			client_data->established = TRUE;
-		}
-
-		/*
-		 * This step succeeded.  Send a response, along with
-		 * a token if there's one.  Don't dispatch.
-		 */
-
-		if (output_token.length != 0) {
-			GSS_COPY_BUFFER(call_res.token, output_token);
-		}
-		/*
-		 * If GSS_S_COMPLETE: set response verifier to
-		 * checksum of SEQ_WIN
-		 */
-
-		if (gssstat == GSS_S_COMPLETE) {
-		    if (!set_response_verf(rqst, msg, client_data,
-				(uint_t)SEQ_WIN)) {
-			ret = RPCSEC_GSS_FAILED;
-			client_data->stale = TRUE;
-			RPCGSS_LOG0(1,
-			"_svc_rpcsec_gss:set response verifier failed\n");
-			goto error2;
-		    }
-		}
-
-		(void) svc_sendreply(rqst->rq_xprt, __xdr_rpc_gss_init_res,
-							(caddr_t)&call_res);
-		/*
-		 * Cache last response in case it is lost and the client
-		 * retries on an established context.
-		 */
-		(void) retrans_add(client_data, msg->rm_xid, &call_res);
-		*no_dispatch = TRUE;
-		ASSERT(client_data->ref_cnt > 0);
-		client_data->ref_cnt--;
-		(void) gss_release_buffer(&minor_stat, &output_token);
-
-	} else {
-		if ((creds.gss_proc != RPCSEC_GSS_DATA) &&
-		    (creds.gss_proc != RPCSEC_GSS_DESTROY)) {
-
-		    switch (creds.gss_proc) {
-
-		    case RPCSEC_GSS_CONTINUE_INIT:
-			/*
-			 * This is an established context. Continue to
-			 * satisfy retried continue init requests out of
-			 * the retransmit cache.  Throw away any that don't
-			 * have a matching xid or the cach is empty.
-			 * Delete the retransmit cache once the client sends
-			 * a data request.
-			 */
-			if (client_data->retrans_data &&
-			    (client_data->retrans_data->xid == msg->rm_xid)) {
-
-			    retrans_result = &client_data->retrans_data->result;
-			    if (set_response_verf(rqst, msg, client_data,
-				(uint_t)retrans_result->seq_window)) {
-
-				gss_parms->established = FALSE;
-				(void) svc_sendreply(rqst->rq_xprt,
-					__xdr_rpc_gss_init_res,
-					(caddr_t)retrans_result);
-				*no_dispatch = TRUE;
-				ASSERT(client_data->ref_cnt > 0);
-				client_data->ref_cnt--;
-				goto success;
-			    }
-			}
-			/* fall thru to default */
-
-		    default:
-			RPCGSS_LOG0(1, "_svcrpcsec_gss: non-data request "
-				"on an established context\n");
-			ret = AUTH_FAILED;
-			goto error2;
-		    }
-		}
-
-		/*
-		 * Once the context is established and there is no more
-		 * retransmission of last continue init request, it is safe
-		 * to delete the retransmit cache entry.
-		 */
-		if (client_data->retrans_data)
-			retrans_del(client_data);
-
-		/*
-		 * Context is already established.  Check verifier, and
-		 * note parameters we will need for response in gss_parms.
-		 */
-		if (!check_verf(msg, client_data->context,
-			(int *)&gss_parms->qop_rcvd, client_data->u_cred.uid)) {
-			ret = RPCSEC_GSS_NOCRED;
-			RPCGSS_LOG0(1, "_svcrpcsec_gss: check verf failed\n");
-			goto error2;
-		}
-
-		/*
-		 *  Check and invoke callback if necessary.
-		 */
-		if (!client_data->done_docallback) {
-			client_data->done_docallback = TRUE;
-			client_data->qop = gss_parms->qop_rcvd;
-			client_data->raw_cred.qop = gss_parms->qop_rcvd;
-			client_data->raw_cred.service = creds.service;
-			if (!do_callback(rqst, client_data)) {
-				ret = AUTH_FAILED;
-				RPCGSS_LOG0(1,
-					"_svc_rpcsec_gss:callback failed\n");
-				goto error2;
-			}
-		}
-
-		/*
-		 * If the context was locked, make sure that the client
-		 * has not changed QOP.
-		 */
-		if (client_data->locked &&
-				gss_parms->qop_rcvd != client_data->qop) {
-			ret = AUTH_BADVERF;
-			RPCGSS_LOG0(1, "_svcrpcsec_gss: can not change qop\n");
-			goto error2;
-		}
-
-		/*
-		 * Validate sequence number.
-		 */
-		if (!check_seq(client_data, creds.seq_num,
-						&client_data->stale)) {
-			if (client_data->stale) {
-				ret = RPCSEC_GSS_FAILED;
-				RPCGSS_LOG0(1,
-					"_svc_rpcsec_gss:check seq failed\n");
-			} else {
-				RPCGSS_LOG0(4, "_svc_rpcsec_gss:check seq "
-					"failed on good context. Ignoring "
-					"request\n");
-				/*
-				 * Operational error, drop packet silently.
-				 * The client will recover after timing out,
-				 * assuming this is a client error and not
-				 * a relpay attack.  Don't dispatch.
-				 */
-				ret = AUTH_OK;
-				*no_dispatch = TRUE;
-			}
-			goto error2;
-		}
-
-		/*
-		 * set response verifier
-		 */
-		if (!set_response_verf(rqst, msg, client_data,
-				creds.seq_num)) {
-			ret = RPCSEC_GSS_FAILED;
-			client_data->stale = TRUE;
-			RPCGSS_LOG0(1,
-			"_svc_rpcsec_gss:set response verifier failed\n");
-			goto error2;
-		}
-
-		/*
-		 * If this is a control message RPCSEC_GSS_DESTROY, process
-		 * the call; otherwise, return AUTH_OK so it will be
-		 * dispatched to the application server.
-		 */
-		if (creds.gss_proc == RPCSEC_GSS_DESTROY) {
-			/*
-			 * XXX Kernel client is not issuing this procudure
-			 * right now. Need to revisit.
-			 */
-			(void) svc_sendreply(rqst->rq_xprt, xdr_void, NULL);
+			    __xdr_rpc_gss_init_res, (caddr_t)retrans_result);
 			*no_dispatch = TRUE;
 			ASSERT(client_data->ref_cnt > 0);
 			client_data->ref_cnt--;
-			client_data->stale = TRUE;
-		} else {
-			/* This should be an RPCSEC_GSS_DATA request. */
-			ASSERT(creds.gss_proc == RPCSEC_GSS_DATA);
+		}
+	}
+	mutex_exit(&client_data->clm);
+
+	return (AUTH_OK);
+
+error2:
+	ASSERT(client_data->ref_cnt > 0);
+	client_data->ref_cnt--;
+	mutex_exit(&client_data->clm);
+	return (ret);
+}
+
+static enum auth_stat
+rpcsec_gss_data(
+	struct svc_req		*rqst,
+	struct rpc_msg		*msg,
+	rpc_gss_creds		creds,
+	bool_t			*no_dispatch)
+{
+	int ret;
+	svc_rpc_gss_parms_t	*gss_parms;
+	svc_rpc_gss_data	*client_data;
+
+	switch (creds.service) {
+	case rpc_gss_svc_none:
+	case rpc_gss_svc_integrity:
+	case rpc_gss_svc_privacy:
+		break;
+	default:
+		cmn_err(CE_NOTE, "__svcrpcsec_gss: unknown service type=0x%x",
+		    creds.service);
+		RPCGSS_LOG(1, "_svcrpcsec_gss: unknown service type: 0x%x\n",
+		    creds.service);
+		ret = AUTH_BADCRED;
+		return (ret);
+	}
+
+	if (creds.ctx_handle.length == 0) {
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: no ctx_handle\n");
+		ret = AUTH_BADCRED;
+		return (ret);
+	}
+	if ((client_data = get_client(&creds.ctx_handle)) == NULL) {
+		ret = RPCSEC_GSS_NOCRED;
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: no security context\n");
+		return (ret);
+	}
+
 
-			/*
-			 * If context is locked, make sure that the client
-			 * has not changed the security service.
-			 */
-			if (client_data->locked &&
-			    client_data->raw_cred.service != creds.service) {
-				RPCGSS_LOG0(1, "_svc_rpcsec_gss: "
-					"security service changed.\n");
-				ret = AUTH_FAILED;
-				goto error2;
-			}
+	mutex_enter(&client_data->clm);
+	if (!client_data->established) {
+		ret = AUTH_FAILED;
+		goto error2;
+	}
+	if (client_data->stale) {
+		ret = RPCSEC_GSS_NOCRED;
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: client data stale\n");
+		goto error2;
+	}
+
+	/*
+	 * Once the context is established and there is no more
+	 * retransmission of last continue init request, it is safe
+	 * to delete the retransmit cache entry.
+	 */
+	if (client_data->retrans_data)
+		retrans_del(client_data);
+
+	/*
+	 * Set the appropriate wrap/unwrap routine for RPCSEC_GSS.
+	 */
+	rqst->rq_xprt->xp_auth.svc_ah_ops = svc_rpc_gss_ops;
+	rqst->rq_xprt->xp_auth.svc_ah_private = (caddr_t)client_data;
 
-			/*
-			 * Set client credentials to raw credential
-			 * structure in context.  This is okay, since
-			 * this will not change during the lifetime of
-			 * the context (so it's MT safe).
-			 */
-			rqst->rq_clntcred = (char *)&client_data->raw_cred;
+	/*
+	 * Keep copy of parameters we'll need for response, for the
+	 * sake of reentrancy (we don't want to look in the context
+	 * data because when we are sending a response, another
+	 * request may have come in).
+	 */
+	gss_parms = &rqst->rq_xprt->xp_auth.svc_gss_parms;
+	gss_parms->established = client_data->established;
+	gss_parms->service = creds.service;
+	gss_parms->qop_rcvd = (uint_t)client_data->qop;
+	gss_parms->context = (void *)client_data->context;
+	gss_parms->seq_num = creds.seq_num;
+
+	/*
+	 * Context is already established.  Check verifier, and
+	 * note parameters we will need for response in gss_parms.
+	 */
+	if (!check_verf(msg, client_data->context,
+	    (int *)&gss_parms->qop_rcvd, client_data->u_cred.uid)) {
+		ret = RPCSEC_GSS_NOCRED;
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: check verf failed\n");
+		goto error2;
+	}
+
+	/*
+	 *  Check and invoke callback if necessary.
+	 */
+	if (!client_data->done_docallback) {
+		client_data->done_docallback = TRUE;
+		client_data->qop = gss_parms->qop_rcvd;
+		client_data->raw_cred.qop = gss_parms->qop_rcvd;
+		client_data->raw_cred.service = creds.service;
+		if (!do_callback(rqst, client_data)) {
+			ret = AUTH_FAILED;
+			RPCGSS_LOG0(1, "_svc_rpcsec_gss:callback failed\n");
+			goto error2;
 		}
 	}
 
-success:
 	/*
-	 * Success.
+	 * If the context was locked, make sure that the client
+	 * has not changed QOP.
+	 */
+	if (client_data->locked && gss_parms->qop_rcvd != client_data->qop) {
+		ret = AUTH_BADVERF;
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: can not change qop\n");
+		goto error2;
+	}
+
+	/*
+	 * Validate sequence number.
 	 */
-	if (creds.ctx_handle.length != 0)
-		xdr_free(__xdr_rpc_gss_creds, (caddr_t)&creds);
+	if (!check_seq(client_data, creds.seq_num, &client_data->stale)) {
+		if (client_data->stale) {
+			ret = RPCSEC_GSS_FAILED;
+			RPCGSS_LOG0(1,
+			    "_svc_rpcsec_gss:check seq failed\n");
+		} else {
+			RPCGSS_LOG0(4, "_svc_rpcsec_gss:check seq "
+			    "failed on good context. Ignoring "
+			    "request\n");
+			/*
+			 * Operational error, drop packet silently.
+			 * The client will recover after timing out,
+			 * assuming this is a client error and not
+			 * a relpay attack.  Don't dispatch.
+			 */
+			ret = AUTH_OK;
+			*no_dispatch = TRUE;
+		}
+		goto error2;
+	}
+
+	/*
+	 * set response verifier
+	 */
+	if (!set_response_verf(rqst, msg, client_data, creds.seq_num)) {
+		ret = RPCSEC_GSS_FAILED;
+		client_data->stale = TRUE;
+		RPCGSS_LOG0(1,
+		    "_svc_rpcsec_gss:set response verifier failed\n");
+		goto error2;
+	}
+
+	/*
+	 * If context is locked, make sure that the client
+	 * has not changed the security service.
+	 */
+	if (client_data->locked &&
+	    client_data->raw_cred.service != creds.service) {
+		RPCGSS_LOG0(1, "_svc_rpcsec_gss: "
+		    "security service changed.\n");
+		ret = AUTH_FAILED;
+		goto error2;
+	}
+
+	/*
+	 * Set client credentials to raw credential
+	 * structure in context.  This is okay, since
+	 * this will not change during the lifetime of
+	 * the context (so it's MT safe).
+	 */
+	rqst->rq_clntcred = (char *)&client_data->raw_cred;
+
 	mutex_exit(&client_data->clm);
+	return (AUTH_OK);
 
-	return (AUTH_OK);
 error2:
 	ASSERT(client_data->ref_cnt > 0);
 	client_data->ref_cnt--;
-	(void) gss_release_buffer(&minor_stat, &output_token);
-	if (free_mech_type && mech_type)
-		kgss_free_oid(mech_type);
+	mutex_exit(&client_data->clm);
+	return (ret);
+}
+
+/*
+ * Note we don't have a client yet to use this routine and test it.
+ */
+static enum auth_stat
+rpcsec_gss_destroy(
+	struct svc_req		*rqst,
+	rpc_gss_creds		creds,
+	bool_t			*no_dispatch)
+{
+	svc_rpc_gss_data	*client_data;
+	int ret;
+
+	if (creds.ctx_handle.length == 0) {
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: no ctx_handle\n");
+		ret = AUTH_BADCRED;
+		return (ret);
+	}
+	if ((client_data = get_client(&creds.ctx_handle)) == NULL) {
+		ret = RPCSEC_GSS_NOCRED;
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: no security context\n");
+		return (ret);
+	}
+
+	mutex_enter(&client_data->clm);
+	if (!client_data->established) {
+		ret = AUTH_FAILED;
+		goto error2;
+	}
+	if (client_data->stale) {
+		ret = RPCSEC_GSS_NOCRED;
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: client data stale\n");
+		goto error2;
+	}
+
+	(void) svc_sendreply(rqst->rq_xprt, xdr_void, NULL);
+	*no_dispatch = TRUE;
+	ASSERT(client_data->ref_cnt > 0);
+	client_data->ref_cnt--;
+	client_data->stale = TRUE;
+	mutex_exit(&client_data->clm);
+	return (AUTH_OK);
+
+error2:
+	ASSERT(client_data->ref_cnt > 0);
+	client_data->ref_cnt--;
+	client_data->stale = TRUE;
 	mutex_exit(&client_data->clm);
-error:
+	return (ret);
+}
+
+/*
+ * Server side authentication for RPCSEC_GSS.
+ */
+enum auth_stat
+__svcrpcsec_gss(
+	struct svc_req		*rqst,
+	struct rpc_msg		*msg,
+	bool_t			*no_dispatch)
+{
+	XDR			xdrs;
+	rpc_gss_creds		creds;
+	struct opaque_auth	*cred;
+	int			ret;
+
+	*no_dispatch = FALSE;
+
+	/*
+	 * Initialize response verifier to NULL verifier.  If
+	 * necessary, this will be changed later.
+	 */
+	rqst->rq_xprt->xp_verf.oa_flavor = AUTH_NONE;
+	rqst->rq_xprt->xp_verf.oa_base = NULL;
+	rqst->rq_xprt->xp_verf.oa_length = 0;
+
 	/*
-	 * Failure.
+	 * Pull out and check credential and verifier.
 	 */
+	cred = &msg->rm_call.cb_cred;
+
+	if (cred->oa_length == 0) {
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: zero length cred\n");
+		return (AUTH_BADCRED);
+	}
+
+	xdrmem_create(&xdrs, cred->oa_base, cred->oa_length, XDR_DECODE);
+	bzero((char *)&creds, sizeof (creds));
+	if (!__xdr_rpc_gss_creds(&xdrs, &creds)) {
+		XDR_DESTROY(&xdrs);
+		RPCGSS_LOG0(1, "_svcrpcsec_gss: can't decode creds\n");
+		ret = AUTH_BADCRED;
+		return (AUTH_BADCRED);
+	}
+	XDR_DESTROY(&xdrs);
+
+	switch (creds.gss_proc) {
+	case RPCSEC_GSS_INIT:
+		ret = rpcsec_gss_init(rqst, msg, creds, no_dispatch, NULL);
+		break;
+	case RPCSEC_GSS_CONTINUE_INIT:
+		ret = rpcsec_gss_continue_init(rqst, msg, creds, no_dispatch);
+		break;
+	case RPCSEC_GSS_DATA:
+		ret = rpcsec_gss_data(rqst, msg, creds, no_dispatch);
+		break;
+	case RPCSEC_GSS_DESTROY:
+		ret = rpcsec_gss_destroy(rqst, creds, no_dispatch);
+		break;
+	default:
+		cmn_err(CE_NOTE, "__svcrpcsec_gss: bad proc=%d",
+		    creds.gss_proc);
+		ret = AUTH_BADCRED;
+	}
+
 	if (creds.ctx_handle.length != 0)
 		xdr_free(__xdr_rpc_gss_creds, (caddr_t)&creds);
-
 	return (ret);
 }
 
@@ -1251,16 +1460,16 @@
 	tok_buf.value = oa->oa_base;
 
 	gssstat = kgss_verify(&minor_stat, context, &msg_buf, &tok_buf,
-				qop_state);
+	    qop_state);
 	if (gssstat != GSS_S_COMPLETE) {
 		RPCGSS_LOG(1, "check_verf: kgss_verify status 0x%x\n", gssstat);
 
 		RPCGSS_LOG(4, "check_verf: msg_buf length %d\n", len);
 		RPCGSS_LOG(4, "check_verf: msg_buf value 0x%x\n", *(int *)hdr);
 		RPCGSS_LOG(4, "check_verf: tok_buf length %ld\n",
-				tok_buf.length);
+		    tok_buf.length);
 		RPCGSS_LOG(4, "check_verf: tok_buf value 0x%p\n",
-			(void *)oa->oa_base);
+		    (void *)oa->oa_base);
 		RPCGSS_LOG(4, "check_verf: context 0x%p\n", (void *)context);
 
 		return (FALSE);
@@ -1268,6 +1477,7 @@
 	return (TRUE);
 }
 
+
 /*
  * Set response verifier.  This is the checksum of the given number.
  * (e.g. sequence number or sequence window)
@@ -1287,6 +1497,7 @@
 	in_buf.length = sizeof (num);
 	in_buf.value = (char *)&num_net;
 /* XXX uid ? */
+
 	if ((kgss_sign(&minor, cl->context, cl->qop, &in_buf,
 				&out_buf)) != GSS_S_COMPLETE)
 		return (FALSE);
@@ -1309,7 +1520,7 @@
 	static uint_t		key = 1;
 
 	client_data = (svc_rpc_gss_data *) kmem_cache_alloc(svc_data_handle,
-							    KM_SLEEP);
+	    KM_SLEEP);
 	if (client_data == NULL)
 		return (NULL);
 
@@ -1524,8 +1735,7 @@
 	ASSERT(mutex_owned(&ctx_mutex));
 
 	last_reference_needed = now - (from_reclaim ?
-				    svc_rpc_gss_active_delta :
-				    svc_rpc_gss_inactive_delta);
+	    svc_rpc_gss_active_delta : svc_rpc_gss_inactive_delta);
 
 	cl = lru_last;
 	while (cl) {
@@ -1580,6 +1790,7 @@
 	caddr_t			xdr_ptr;
 {
 	svc_rpc_gss_parms_t	*gss_parms = SVCAUTH_GSSPARMS(auth);
+	bool_t ret;
 
 	/*
 	 * If context is not established, or if neither integrity nor
@@ -1590,11 +1801,12 @@
 				gss_parms->service == rpc_gss_svc_none)
 		return ((*xdr_func)(out_xdrs, xdr_ptr));
 
-	return (__rpc_gss_wrap_data(gss_parms->service,
+	ret = __rpc_gss_wrap_data(gss_parms->service,
 				(OM_uint32)gss_parms->qop_rcvd,
 				(gss_ctx_id_t)gss_parms->context,
 				gss_parms->seq_num,
-				out_xdrs, xdr_func, xdr_ptr));
+				out_xdrs, xdr_func, xdr_ptr);
+	return (ret);
 }
 
 /*
@@ -1699,8 +1911,8 @@
 
 	if (client_data->raw_cred.client_principal) {
 		kmem_free((caddr_t)client_data->raw_cred.client_principal,
-			client_data->raw_cred.client_principal->len +
-			sizeof (int));
+		    client_data->raw_cred.client_principal->len +
+		    sizeof (int));
 		client_data->raw_cred.client_principal = NULL;
 	}
 
--- a/usr/src/uts/common/rpc/svc.c	Mon Oct 05 17:27:50 2009 -0700
+++ b/usr/src/uts/common/rpc/svc.c	Mon Oct 05 19:17:50 2009 -0700
@@ -1440,7 +1440,7 @@
 /*
  * Allocate new clone transport handle.
  */
-static SVCXPRT *
+SVCXPRT *
 svc_clone_init(void)
 {
 	SVCXPRT *clone_xprt;
@@ -1453,7 +1453,7 @@
 /*
  * Free memory allocated by svc_clone_init.
  */
-static void
+void
 svc_clone_free(SVCXPRT *clone_xprt)
 {
 	/* Fre credentials from crget() */
@@ -1468,7 +1468,7 @@
  * - copy some of the master's fields to the clone
  * - call a transport specific clone routine.
  */
-static void
+void
 svc_clone_link(SVCMASTERXPRT *xprt, SVCXPRT *clone_xprt)
 {
 	cred_t *cred = clone_xprt->xp_cred;
@@ -1515,7 +1515,7 @@
  * - call transport specific function to destroy the clone handle
  * - clear xp_master to avoid recursion.
  */
-static void
+void
 svc_clone_unlink(SVCXPRT *clone_xprt)
 {
 	SVCMASTERXPRT *xprt = clone_xprt->xp_master;
@@ -2639,3 +2639,83 @@
 			break;
 	}
 }
+
+
+/*
+ * rpc_msg_dup/rpc_msg_free
+ * Currently only used by svc_rpcsec_gss.c but put in this file as it
+ * may be useful to others in the future.
+ * But future consumers should be careful cuz so far
+ *   - only tested/used for call msgs (not reply)
+ *   - only tested/used with call verf oa_length==0
+ */
+struct rpc_msg *
+rpc_msg_dup(struct rpc_msg *src)
+{
+	struct rpc_msg *dst;
+	struct opaque_auth oa_src, oa_dst;
+
+	dst = kmem_alloc(sizeof (*dst), KM_SLEEP);
+
+	dst->rm_xid = src->rm_xid;
+	dst->rm_direction = src->rm_direction;
+
+	dst->rm_call.cb_rpcvers = src->rm_call.cb_rpcvers;
+	dst->rm_call.cb_prog = src->rm_call.cb_prog;
+	dst->rm_call.cb_vers = src->rm_call.cb_vers;
+	dst->rm_call.cb_proc = src->rm_call.cb_proc;
+
+	/* dup opaque auth call body cred */
+	oa_src = src->rm_call.cb_cred;
+
+	oa_dst.oa_flavor = oa_src.oa_flavor;
+	oa_dst.oa_base = kmem_alloc(oa_src.oa_length, KM_SLEEP);
+
+	bcopy(oa_src.oa_base, oa_dst.oa_base, oa_src.oa_length);
+	oa_dst.oa_length = oa_src.oa_length;
+
+	dst->rm_call.cb_cred = oa_dst;
+
+	/* dup or just alloc opaque auth call body verifier */
+	if (src->rm_call.cb_verf.oa_length > 0) {
+		oa_src = src->rm_call.cb_verf;
+
+		oa_dst.oa_flavor = oa_src.oa_flavor;
+		oa_dst.oa_base = kmem_alloc(oa_src.oa_length, KM_SLEEP);
+
+		bcopy(oa_src.oa_base, oa_dst.oa_base, oa_src.oa_length);
+		oa_dst.oa_length = oa_src.oa_length;
+
+		dst->rm_call.cb_verf = oa_dst;
+	} else {
+		oa_dst.oa_flavor = -1;  /* will be set later */
+		oa_dst.oa_base = kmem_alloc(MAX_AUTH_BYTES, KM_SLEEP);
+
+		oa_dst.oa_length = 0;   /* will be set later */
+
+		dst->rm_call.cb_verf = oa_dst;
+	}
+	return (dst);
+
+error:
+	kmem_free(dst->rm_call.cb_cred.oa_base,	dst->rm_call.cb_cred.oa_length);
+	kmem_free(dst, sizeof (*dst));
+	return (NULL);
+}
+
+void
+rpc_msg_free(struct rpc_msg **msg, int cb_verf_oa_length)
+{
+	struct rpc_msg *m = *msg;
+
+	kmem_free(m->rm_call.cb_cred.oa_base, m->rm_call.cb_cred.oa_length);
+	m->rm_call.cb_cred.oa_base = NULL;
+	m->rm_call.cb_cred.oa_length = 0;
+
+	kmem_free(m->rm_call.cb_verf.oa_base, cb_verf_oa_length);
+	m->rm_call.cb_verf.oa_base = NULL;
+	m->rm_call.cb_verf.oa_length = 0;
+
+	kmem_free(m, sizeof (*m));
+	m = NULL;
+}
--- a/usr/src/uts/common/rpc/svc.h	Mon Oct 05 17:27:50 2009 -0700
+++ b/usr/src/uts/common/rpc/svc.h	Mon Oct 05 19:17:50 2009 -0700
@@ -1095,6 +1095,18 @@
 #endif	/* __STDC__ */
 #endif	/* _KERNEL */
 
+#ifdef	_KERNEL
+/*
+ * Private interfaces and structures for SVCXPRT cloning.
+ * The interfaces and data structures are not committed and subject to
+ * change in future releases.
+ */
+extern SVCXPRT *svc_clone_init(void);
+extern void svc_clone_free(SVCXPRT *);
+extern void svc_clone_link(SVCMASTERXPRT *, SVCXPRT *);
+extern void svc_clone_unlink(SVCXPRT *);
+#endif	/* _KERNEL */
+
 #ifdef	__cplusplus
 }
 #endif