1184588Sdfr/*-
2184588Sdfr * Copyright (c) 2008 Doug Rabson
3184588Sdfr * All rights reserved.
4184588Sdfr *
5184588Sdfr * Redistribution and use in source and binary forms, with or without
6184588Sdfr * modification, are permitted provided that the following conditions
7184588Sdfr * are met:
8184588Sdfr * 1. Redistributions of source code must retain the above copyright
9184588Sdfr *    notice, this list of conditions and the following disclaimer.
10184588Sdfr * 2. Redistributions in binary form must reproduce the above copyright
11184588Sdfr *    notice, this list of conditions and the following disclaimer in the
12184588Sdfr *    documentation and/or other materials provided with the distribution.
13184588Sdfr *
14184588Sdfr * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15184588Sdfr * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16184588Sdfr * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17184588Sdfr * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18184588Sdfr * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19184588Sdfr * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20184588Sdfr * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21184588Sdfr * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22184588Sdfr * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23184588Sdfr * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24184588Sdfr * SUCH DAMAGE.
25184588Sdfr */
26184588Sdfr/*
27184588Sdfr  auth_gss.c
28184588Sdfr
29184588Sdfr  RPCSEC_GSS client routines.
30184588Sdfr
31184588Sdfr  Copyright (c) 2000 The Regents of the University of Michigan.
32184588Sdfr  All rights reserved.
33184588Sdfr
34184588Sdfr  Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
35184588Sdfr  All rights reserved, all wrongs reversed.
36184588Sdfr
37184588Sdfr  Redistribution and use in source and binary forms, with or without
38184588Sdfr  modification, are permitted provided that the following conditions
39184588Sdfr  are met:
40184588Sdfr
41184588Sdfr  1. Redistributions of source code must retain the above copyright
42184588Sdfr     notice, this list of conditions and the following disclaimer.
43184588Sdfr  2. Redistributions in binary form must reproduce the above copyright
44184588Sdfr     notice, this list of conditions and the following disclaimer in the
45184588Sdfr     documentation and/or other materials provided with the distribution.
46184588Sdfr  3. Neither the name of the University nor the names of its
47184588Sdfr     contributors may be used to endorse or promote products derived
48184588Sdfr     from this software without specific prior written permission.
49184588Sdfr
50184588Sdfr  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
51184588Sdfr  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
52184588Sdfr  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
53184588Sdfr  DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
54184588Sdfr  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
55184588Sdfr  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
56184588Sdfr  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
57184588Sdfr  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
58184588Sdfr  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
59184588Sdfr  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
60184588Sdfr  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
61184588Sdfr
62184588Sdfr  $Id: auth_gss.c,v 1.32 2002/01/15 15:43:00 andros Exp $
63184588Sdfr*/
64184588Sdfr
65184588Sdfr#include <sys/cdefs.h>
66184588Sdfr__FBSDID("$FreeBSD: releng/10.3/sys/rpc/rpcsec_gss/rpcsec_gss.c 253049 2013-07-09 01:05:28Z rmacklem $");
67184588Sdfr
68184588Sdfr#include <sys/param.h>
69184588Sdfr#include <sys/systm.h>
70184588Sdfr#include <sys/hash.h>
71184588Sdfr#include <sys/kernel.h>
72184588Sdfr#include <sys/kobj.h>
73184588Sdfr#include <sys/lock.h>
74184588Sdfr#include <sys/malloc.h>
75184588Sdfr#include <sys/mbuf.h>
76184588Sdfr#include <sys/mutex.h>
77184588Sdfr#include <sys/proc.h>
78184588Sdfr#include <sys/refcount.h>
79184588Sdfr#include <sys/sx.h>
80184588Sdfr#include <sys/ucred.h>
81184588Sdfr
82184588Sdfr#include <rpc/rpc.h>
83184588Sdfr#include <rpc/rpcsec_gss.h>
84184588Sdfr
85253049Srmacklem#include <kgssapi/krb5/kcrypto.h>
86253049Srmacklem
87184588Sdfr#include "rpcsec_gss_int.h"
88184588Sdfr
89184588Sdfrstatic void	rpc_gss_nextverf(AUTH*);
90184588Sdfrstatic bool_t	rpc_gss_marshal(AUTH *, uint32_t, XDR *, struct mbuf *);
91184588Sdfrstatic bool_t	rpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret);
92184588Sdfrstatic bool_t	rpc_gss_refresh(AUTH *, void *);
93184588Sdfrstatic bool_t	rpc_gss_validate(AUTH *, uint32_t, struct opaque_auth *,
94184588Sdfr    struct mbuf **);
95184588Sdfrstatic void	rpc_gss_destroy(AUTH *);
96184588Sdfrstatic void	rpc_gss_destroy_context(AUTH *, bool_t);
97184588Sdfr
98184588Sdfrstatic struct auth_ops rpc_gss_ops = {
99184588Sdfr	rpc_gss_nextverf,
100184588Sdfr	rpc_gss_marshal,
101184588Sdfr	rpc_gss_validate,
102184588Sdfr	rpc_gss_refresh,
103184588Sdfr	rpc_gss_destroy,
104184588Sdfr};
105184588Sdfr
106184588Sdfrenum rpcsec_gss_state {
107184588Sdfr	RPCSEC_GSS_START,
108184588Sdfr	RPCSEC_GSS_CONTEXT,
109184588Sdfr	RPCSEC_GSS_ESTABLISHED,
110184588Sdfr	RPCSEC_GSS_DESTROYING
111184588Sdfr};
112184588Sdfr
113184588Sdfrstruct rpc_pending_request {
114184588Sdfr	uint32_t		pr_xid;		/* XID of rpc */
115184588Sdfr	uint32_t		pr_seq;		/* matching GSS seq */
116184588Sdfr	LIST_ENTRY(rpc_pending_request) pr_link;
117184588Sdfr};
118184588SdfrLIST_HEAD(rpc_pending_request_list, rpc_pending_request);
119184588Sdfr
120184588Sdfrstruct rpc_gss_data {
121184588Sdfr	volatile u_int		gd_refs;	/* number of current users */
122184588Sdfr	struct mtx		gd_lock;
123184588Sdfr	uint32_t		gd_hash;
124184588Sdfr	AUTH			*gd_auth;	/* link back to AUTH */
125184588Sdfr	struct ucred		*gd_ucred;	/* matching local cred */
126184588Sdfr	char			*gd_principal;	/* server principal name */
127253049Srmacklem	char			*gd_clntprincipal; /* client principal name */
128184588Sdfr	rpc_gss_options_req_t	gd_options;	/* GSS context options */
129184588Sdfr	enum rpcsec_gss_state	gd_state;	/* connection state */
130184588Sdfr	gss_buffer_desc		gd_verf;	/* save GSS_S_COMPLETE
131184588Sdfr						 * NULL RPC verfier to
132184588Sdfr						 * process at end of
133184588Sdfr						 * context negotiation */
134184588Sdfr	CLIENT			*gd_clnt;	/* client handle */
135184588Sdfr	gss_OID			gd_mech;	/* mechanism to use */
136184588Sdfr	gss_qop_t		gd_qop;		/* quality of protection */
137184588Sdfr	gss_ctx_id_t		gd_ctx;		/* context id */
138184588Sdfr	struct rpc_gss_cred	gd_cred;	/* client credentials */
139184588Sdfr	uint32_t		gd_seq;		/* next sequence number */
140184588Sdfr	u_int			gd_win;		/* sequence window */
141184588Sdfr	struct rpc_pending_request_list gd_reqs;
142184588Sdfr	TAILQ_ENTRY(rpc_gss_data) gd_link;
143184588Sdfr	TAILQ_ENTRY(rpc_gss_data) gd_alllink;
144184588Sdfr};
145184588SdfrTAILQ_HEAD(rpc_gss_data_list, rpc_gss_data);
146184588Sdfr
147184588Sdfr#define	AUTH_PRIVATE(auth)	((struct rpc_gss_data *)auth->ah_private)
148184588Sdfr
149184588Sdfrstatic struct timeval AUTH_TIMEOUT = { 25, 0 };
150184588Sdfr
151184588Sdfr#define RPC_GSS_HASH_SIZE	11
152184588Sdfr#define RPC_GSS_MAX		256
153184588Sdfrstatic struct rpc_gss_data_list rpc_gss_cache[RPC_GSS_HASH_SIZE];
154184588Sdfrstatic struct rpc_gss_data_list rpc_gss_all;
155184588Sdfrstatic struct sx rpc_gss_lock;
156184588Sdfrstatic int rpc_gss_count;
157184588Sdfr
158184588Sdfrstatic AUTH *rpc_gss_seccreate_int(CLIENT *, struct ucred *, const char *,
159253049Srmacklem    const char *, gss_OID, rpc_gss_service_t, u_int, rpc_gss_options_req_t *,
160184588Sdfr    rpc_gss_options_ret_t *);
161184588Sdfr
162184588Sdfrstatic void
163184588Sdfrrpc_gss_hashinit(void *dummy)
164184588Sdfr{
165184588Sdfr	int i;
166184588Sdfr
167184588Sdfr	for (i = 0; i < RPC_GSS_HASH_SIZE; i++)
168184588Sdfr		TAILQ_INIT(&rpc_gss_cache[i]);
169184588Sdfr	TAILQ_INIT(&rpc_gss_all);
170184588Sdfr	sx_init(&rpc_gss_lock, "rpc_gss_lock");
171184588Sdfr}
172184588SdfrSYSINIT(rpc_gss_hashinit, SI_SUB_KMEM, SI_ORDER_ANY, rpc_gss_hashinit, NULL);
173184588Sdfr
174184588Sdfrstatic uint32_t
175184588Sdfrrpc_gss_hash(const char *principal, gss_OID mech,
176184588Sdfr    struct ucred *cred, rpc_gss_service_t service)
177184588Sdfr{
178184588Sdfr	uint32_t h;
179184588Sdfr
180184588Sdfr	h = HASHSTEP(HASHINIT, cred->cr_uid);
181184588Sdfr	h = hash32_str(principal, h);
182184588Sdfr	h = hash32_buf(mech->elements, mech->length, h);
183184588Sdfr	h = HASHSTEP(h, (int) service);
184184588Sdfr
185184588Sdfr	return (h % RPC_GSS_HASH_SIZE);
186184588Sdfr}
187184588Sdfr
188184588Sdfr/*
189184588Sdfr * Simplified interface to create a security association for the
190184588Sdfr * current thread's * ucred.
191184588Sdfr */
192184588SdfrAUTH *
193184588Sdfrrpc_gss_secfind(CLIENT *clnt, struct ucred *cred, const char *principal,
194184588Sdfr    gss_OID mech_oid, rpc_gss_service_t service)
195184588Sdfr{
196184588Sdfr	uint32_t		h, th;
197184588Sdfr	AUTH			*auth;
198184588Sdfr	struct rpc_gss_data	*gd, *tgd;
199194878Srmacklem	rpc_gss_options_ret_t	options;
200184588Sdfr
201184588Sdfr	if (rpc_gss_count > RPC_GSS_MAX) {
202184588Sdfr		while (rpc_gss_count > RPC_GSS_MAX) {
203184588Sdfr			sx_xlock(&rpc_gss_lock);
204184588Sdfr			tgd = TAILQ_FIRST(&rpc_gss_all);
205184588Sdfr			th = tgd->gd_hash;
206184588Sdfr			TAILQ_REMOVE(&rpc_gss_cache[th], tgd, gd_link);
207184588Sdfr			TAILQ_REMOVE(&rpc_gss_all, tgd, gd_alllink);
208184588Sdfr			rpc_gss_count--;
209184588Sdfr			sx_xunlock(&rpc_gss_lock);
210184588Sdfr			AUTH_DESTROY(tgd->gd_auth);
211184588Sdfr		}
212184588Sdfr	}
213184588Sdfr
214184588Sdfr	/*
215184588Sdfr	 * See if we already have an AUTH which matches.
216184588Sdfr	 */
217184588Sdfr	h = rpc_gss_hash(principal, mech_oid, cred, service);
218184588Sdfr
219184588Sdfragain:
220184588Sdfr	sx_slock(&rpc_gss_lock);
221184588Sdfr	TAILQ_FOREACH(gd, &rpc_gss_cache[h], gd_link) {
222184588Sdfr		if (gd->gd_ucred->cr_uid == cred->cr_uid
223184588Sdfr		    && !strcmp(gd->gd_principal, principal)
224184588Sdfr		    && gd->gd_mech == mech_oid
225184588Sdfr		    && gd->gd_cred.gc_svc == service) {
226184588Sdfr			refcount_acquire(&gd->gd_refs);
227184588Sdfr			if (sx_try_upgrade(&rpc_gss_lock)) {
228184588Sdfr				/*
229184588Sdfr				 * Keep rpc_gss_all LRU sorted.
230184588Sdfr				 */
231184588Sdfr				TAILQ_REMOVE(&rpc_gss_all, gd, gd_alllink);
232184588Sdfr				TAILQ_INSERT_TAIL(&rpc_gss_all, gd,
233184588Sdfr				    gd_alllink);
234184588Sdfr				sx_xunlock(&rpc_gss_lock);
235184588Sdfr			} else {
236184588Sdfr				sx_sunlock(&rpc_gss_lock);
237184588Sdfr			}
238194878Srmacklem
239194878Srmacklem			/*
240194878Srmacklem			 * If the state != ESTABLISHED, try and initialize
241194878Srmacklem			 * the authenticator again. This will happen if the
242194878Srmacklem			 * user's credentials have expired. It may succeed now,
243194878Srmacklem			 * if they have done a kinit or similar.
244194878Srmacklem			 */
245194878Srmacklem			if (gd->gd_state != RPCSEC_GSS_ESTABLISHED) {
246194878Srmacklem				memset(&options, 0, sizeof (options));
247194878Srmacklem				(void) rpc_gss_init(gd->gd_auth, &options);
248194878Srmacklem			}
249184588Sdfr			return (gd->gd_auth);
250184588Sdfr		}
251184588Sdfr	}
252184588Sdfr	sx_sunlock(&rpc_gss_lock);
253184588Sdfr
254184588Sdfr	/*
255184588Sdfr	 * We missed in the cache - create a new association.
256184588Sdfr	 */
257253049Srmacklem	auth = rpc_gss_seccreate_int(clnt, cred, NULL, principal, mech_oid,
258253049Srmacklem	    service, GSS_C_QOP_DEFAULT, NULL, NULL);
259184588Sdfr	if (!auth)
260184588Sdfr		return (NULL);
261184588Sdfr
262184588Sdfr	gd = AUTH_PRIVATE(auth);
263184588Sdfr	gd->gd_hash = h;
264184588Sdfr
265184588Sdfr	sx_xlock(&rpc_gss_lock);
266184588Sdfr	TAILQ_FOREACH(tgd, &rpc_gss_cache[h], gd_link) {
267184588Sdfr		if (tgd->gd_ucred->cr_uid == cred->cr_uid
268184588Sdfr		    && !strcmp(tgd->gd_principal, principal)
269184588Sdfr		    && tgd->gd_mech == mech_oid
270184588Sdfr		    && tgd->gd_cred.gc_svc == service) {
271184588Sdfr			/*
272184588Sdfr			 * We lost a race to create the AUTH that
273184588Sdfr			 * matches this cred.
274184588Sdfr			 */
275184588Sdfr			sx_xunlock(&rpc_gss_lock);
276184588Sdfr			AUTH_DESTROY(auth);
277184588Sdfr			goto again;
278184588Sdfr		}
279184588Sdfr	}
280184588Sdfr
281184588Sdfr	rpc_gss_count++;
282184588Sdfr	TAILQ_INSERT_TAIL(&rpc_gss_cache[h], gd, gd_link);
283184588Sdfr	TAILQ_INSERT_TAIL(&rpc_gss_all, gd, gd_alllink);
284184588Sdfr	refcount_acquire(&gd->gd_refs);	/* one for the cache, one for user */
285184588Sdfr	sx_xunlock(&rpc_gss_lock);
286184588Sdfr
287184588Sdfr	return (auth);
288184588Sdfr}
289184588Sdfr
290184588Sdfrvoid
291184588Sdfrrpc_gss_secpurge(CLIENT *clnt)
292184588Sdfr{
293184588Sdfr	uint32_t		h;
294184588Sdfr	struct rpc_gss_data	*gd, *tgd;
295184588Sdfr
296184588Sdfr	TAILQ_FOREACH_SAFE(gd, &rpc_gss_all, gd_alllink, tgd) {
297184588Sdfr		if (gd->gd_clnt == clnt) {
298184588Sdfr			sx_xlock(&rpc_gss_lock);
299184588Sdfr			h = gd->gd_hash;
300184588Sdfr			TAILQ_REMOVE(&rpc_gss_cache[h], gd, gd_link);
301184588Sdfr			TAILQ_REMOVE(&rpc_gss_all, gd, gd_alllink);
302184588Sdfr			rpc_gss_count--;
303184588Sdfr			sx_xunlock(&rpc_gss_lock);
304184588Sdfr			AUTH_DESTROY(gd->gd_auth);
305184588Sdfr		}
306184588Sdfr	}
307184588Sdfr}
308184588Sdfr
309184588SdfrAUTH *
310253049Srmacklemrpc_gss_seccreate(CLIENT *clnt, struct ucred *cred, const char *clnt_principal,
311253049Srmacklem    const char *principal, const char *mechanism, rpc_gss_service_t service,
312253049Srmacklem    const char *qop, rpc_gss_options_req_t *options_req,
313253049Srmacklem    rpc_gss_options_ret_t *options_ret)
314184588Sdfr{
315184588Sdfr	gss_OID			oid;
316184588Sdfr	u_int			qop_num;
317184588Sdfr
318184588Sdfr	/*
319184588Sdfr	 * Bail out now if we don't know this mechanism.
320184588Sdfr	 */
321184588Sdfr	if (!rpc_gss_mech_to_oid(mechanism, &oid))
322184588Sdfr		return (NULL);
323184588Sdfr
324184588Sdfr	if (qop) {
325184588Sdfr		if (!rpc_gss_qop_to_num(qop, mechanism, &qop_num))
326184588Sdfr			return (NULL);
327184588Sdfr	} else {
328184588Sdfr		qop_num = GSS_C_QOP_DEFAULT;
329184588Sdfr	}
330184588Sdfr
331253049Srmacklem	return (rpc_gss_seccreate_int(clnt, cred, clnt_principal, principal,
332253049Srmacklem		oid, service, qop_num, options_req, options_ret));
333184588Sdfr}
334184588Sdfr
335253049Srmacklemvoid
336253049Srmacklemrpc_gss_refresh_auth(AUTH *auth)
337253049Srmacklem{
338253049Srmacklem	struct rpc_gss_data	*gd;
339253049Srmacklem	rpc_gss_options_ret_t	options;
340253049Srmacklem
341253049Srmacklem	gd = AUTH_PRIVATE(auth);
342253049Srmacklem	/*
343253049Srmacklem	 * If the state != ESTABLISHED, try and initialize
344253049Srmacklem	 * the authenticator again. This will happen if the
345253049Srmacklem	 * user's credentials have expired. It may succeed now,
346253049Srmacklem	 * if they have done a kinit or similar.
347253049Srmacklem	 */
348253049Srmacklem	if (gd->gd_state != RPCSEC_GSS_ESTABLISHED) {
349253049Srmacklem		memset(&options, 0, sizeof (options));
350253049Srmacklem		(void) rpc_gss_init(auth, &options);
351253049Srmacklem	}
352253049Srmacklem}
353253049Srmacklem
354184588Sdfrstatic AUTH *
355253049Srmacklemrpc_gss_seccreate_int(CLIENT *clnt, struct ucred *cred,
356253049Srmacklem    const char *clnt_principal, const char *principal, gss_OID mech_oid,
357253049Srmacklem    rpc_gss_service_t service, u_int qop_num,
358184588Sdfr    rpc_gss_options_req_t *options_req, rpc_gss_options_ret_t *options_ret)
359184588Sdfr{
360184588Sdfr	AUTH			*auth;
361184588Sdfr	rpc_gss_options_ret_t	options;
362184588Sdfr	struct rpc_gss_data	*gd;
363184588Sdfr
364184588Sdfr	/*
365184588Sdfr	 * If the caller doesn't want the options, point at local
366184588Sdfr	 * storage to simplify the code below.
367184588Sdfr	 */
368184588Sdfr	if (!options_ret)
369184588Sdfr		options_ret = &options;
370184588Sdfr
371184588Sdfr	/*
372184588Sdfr	 * Default service is integrity.
373184588Sdfr	 */
374184588Sdfr	if (service == rpc_gss_svc_default)
375184588Sdfr		service = rpc_gss_svc_integrity;
376184588Sdfr
377184588Sdfr	memset(options_ret, 0, sizeof(*options_ret));
378184588Sdfr
379184588Sdfr	rpc_gss_log_debug("in rpc_gss_seccreate()");
380184588Sdfr
381184588Sdfr	memset(&rpc_createerr, 0, sizeof(rpc_createerr));
382184588Sdfr
383184588Sdfr	auth = mem_alloc(sizeof(*auth));
384184588Sdfr	if (auth == NULL) {
385184588Sdfr		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
386184588Sdfr		rpc_createerr.cf_error.re_errno = ENOMEM;
387184588Sdfr		return (NULL);
388184588Sdfr	}
389184588Sdfr	gd = mem_alloc(sizeof(*gd));
390184588Sdfr	if (gd == NULL) {
391184588Sdfr		rpc_createerr.cf_stat = RPC_SYSTEMERROR;
392184588Sdfr		rpc_createerr.cf_error.re_errno = ENOMEM;
393184588Sdfr		mem_free(auth, sizeof(*auth));
394184588Sdfr		return (NULL);
395184588Sdfr	}
396184588Sdfr
397184588Sdfr	auth->ah_ops = &rpc_gss_ops;
398184588Sdfr	auth->ah_private = (caddr_t) gd;
399184588Sdfr	auth->ah_cred.oa_flavor = RPCSEC_GSS;
400184588Sdfr
401184588Sdfr	refcount_init(&gd->gd_refs, 1);
402184588Sdfr	mtx_init(&gd->gd_lock, "gd->gd_lock", NULL, MTX_DEF);
403184588Sdfr	gd->gd_auth = auth;
404184588Sdfr	gd->gd_ucred = crdup(cred);
405184588Sdfr	gd->gd_principal = strdup(principal, M_RPC);
406253049Srmacklem	if (clnt_principal != NULL)
407253049Srmacklem		gd->gd_clntprincipal = strdup(clnt_principal, M_RPC);
408253049Srmacklem	else
409253049Srmacklem		gd->gd_clntprincipal = NULL;
410184588Sdfr
411184588Sdfr
412184588Sdfr	if (options_req) {
413184588Sdfr		gd->gd_options = *options_req;
414184588Sdfr	} else {
415184588Sdfr		gd->gd_options.req_flags = GSS_C_MUTUAL_FLAG;
416184588Sdfr		gd->gd_options.time_req = 0;
417184588Sdfr		gd->gd_options.my_cred = GSS_C_NO_CREDENTIAL;
418184588Sdfr		gd->gd_options.input_channel_bindings = NULL;
419184588Sdfr	}
420184588Sdfr	CLNT_ACQUIRE(clnt);
421184588Sdfr	gd->gd_clnt = clnt;
422184588Sdfr	gd->gd_ctx = GSS_C_NO_CONTEXT;
423184588Sdfr	gd->gd_mech = mech_oid;
424184588Sdfr	gd->gd_qop = qop_num;
425184588Sdfr
426184588Sdfr	gd->gd_cred.gc_version = RPCSEC_GSS_VERSION;
427184588Sdfr	gd->gd_cred.gc_proc = RPCSEC_GSS_INIT;
428184588Sdfr	gd->gd_cred.gc_seq = 0;
429184588Sdfr	gd->gd_cred.gc_svc = service;
430184588Sdfr	LIST_INIT(&gd->gd_reqs);
431184588Sdfr
432184588Sdfr	if (!rpc_gss_init(auth, options_ret)) {
433184588Sdfr		goto bad;
434184588Sdfr	}
435184588Sdfr
436184588Sdfr	return (auth);
437184588Sdfr
438184588Sdfr bad:
439184588Sdfr	AUTH_DESTROY(auth);
440184588Sdfr	return (NULL);
441184588Sdfr}
442184588Sdfr
443184588Sdfrbool_t
444184588Sdfrrpc_gss_set_defaults(AUTH *auth, rpc_gss_service_t service, const char *qop)
445184588Sdfr{
446184588Sdfr	struct rpc_gss_data	*gd;
447184588Sdfr	u_int			qop_num;
448184588Sdfr	const char		*mechanism;
449184588Sdfr
450184588Sdfr	gd = AUTH_PRIVATE(auth);
451184588Sdfr	if (!rpc_gss_oid_to_mech(gd->gd_mech, &mechanism)) {
452184588Sdfr		return (FALSE);
453184588Sdfr	}
454184588Sdfr
455184588Sdfr	if (qop) {
456184588Sdfr		if (!rpc_gss_qop_to_num(qop, mechanism, &qop_num)) {
457184588Sdfr			return (FALSE);
458184588Sdfr		}
459184588Sdfr	} else {
460184588Sdfr		qop_num = GSS_C_QOP_DEFAULT;
461184588Sdfr	}
462184588Sdfr
463184588Sdfr	gd->gd_cred.gc_svc = service;
464184588Sdfr	gd->gd_qop = qop_num;
465184588Sdfr	return (TRUE);
466184588Sdfr}
467184588Sdfr
468184588Sdfrstatic void
469184588Sdfrrpc_gss_purge_xid(struct rpc_gss_data *gd, uint32_t xid)
470184588Sdfr{
471184588Sdfr	struct rpc_pending_request *pr, *npr;
472184588Sdfr	struct rpc_pending_request_list reqs;
473184588Sdfr
474184588Sdfr	LIST_INIT(&reqs);
475184588Sdfr	mtx_lock(&gd->gd_lock);
476184588Sdfr	LIST_FOREACH_SAFE(pr, &gd->gd_reqs, pr_link, npr) {
477184588Sdfr		if (pr->pr_xid == xid) {
478184588Sdfr			LIST_REMOVE(pr, pr_link);
479184588Sdfr			LIST_INSERT_HEAD(&reqs, pr, pr_link);
480184588Sdfr		}
481184588Sdfr	}
482184588Sdfr
483184588Sdfr	mtx_unlock(&gd->gd_lock);
484184588Sdfr
485184588Sdfr	LIST_FOREACH_SAFE(pr, &reqs, pr_link, npr) {
486184588Sdfr		mem_free(pr, sizeof(*pr));
487184588Sdfr	}
488184588Sdfr}
489184588Sdfr
490184588Sdfrstatic uint32_t
491184588Sdfrrpc_gss_alloc_seq(struct rpc_gss_data *gd)
492184588Sdfr{
493184588Sdfr	uint32_t seq;
494184588Sdfr
495184588Sdfr	mtx_lock(&gd->gd_lock);
496184588Sdfr	seq = gd->gd_seq;
497184588Sdfr	gd->gd_seq++;
498184588Sdfr	mtx_unlock(&gd->gd_lock);
499184588Sdfr
500184588Sdfr	return (seq);
501184588Sdfr}
502184588Sdfr
503184588Sdfrstatic void
504184588Sdfrrpc_gss_nextverf(__unused AUTH *auth)
505184588Sdfr{
506184588Sdfr
507184588Sdfr	/* not used */
508184588Sdfr}
509184588Sdfr
510184588Sdfrstatic bool_t
511184588Sdfrrpc_gss_marshal(AUTH *auth, uint32_t xid, XDR *xdrs, struct mbuf *args)
512184588Sdfr{
513184588Sdfr	struct rpc_gss_data	*gd;
514184588Sdfr	struct rpc_pending_request *pr;
515184588Sdfr	uint32_t		 seq;
516184588Sdfr	XDR			 tmpxdrs;
517184588Sdfr	struct rpc_gss_cred	 gsscred;
518184588Sdfr	char			 credbuf[MAX_AUTH_BYTES];
519184588Sdfr	struct opaque_auth	 creds, verf;
520184588Sdfr	gss_buffer_desc		 rpcbuf, checksum;
521184588Sdfr	OM_uint32		 maj_stat, min_stat;
522184588Sdfr	bool_t			 xdr_stat;
523184588Sdfr
524184588Sdfr	rpc_gss_log_debug("in rpc_gss_marshal()");
525184588Sdfr
526184588Sdfr	gd = AUTH_PRIVATE(auth);
527184588Sdfr
528184588Sdfr	gsscred = gd->gd_cred;
529184588Sdfr	seq = rpc_gss_alloc_seq(gd);
530184588Sdfr	gsscred.gc_seq = seq;
531184588Sdfr
532184588Sdfr	xdrmem_create(&tmpxdrs, credbuf, sizeof(credbuf), XDR_ENCODE);
533184588Sdfr	if (!xdr_rpc_gss_cred(&tmpxdrs, &gsscred)) {
534184588Sdfr		XDR_DESTROY(&tmpxdrs);
535184588Sdfr		_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
536184588Sdfr		return (FALSE);
537184588Sdfr	}
538184588Sdfr	creds.oa_flavor = RPCSEC_GSS;
539184588Sdfr	creds.oa_base = credbuf;
540184588Sdfr	creds.oa_length = XDR_GETPOS(&tmpxdrs);
541184588Sdfr	XDR_DESTROY(&tmpxdrs);
542184588Sdfr
543184588Sdfr	xdr_opaque_auth(xdrs, &creds);
544184588Sdfr
545184588Sdfr	if (gd->gd_cred.gc_proc == RPCSEC_GSS_INIT ||
546184588Sdfr	    gd->gd_cred.gc_proc == RPCSEC_GSS_CONTINUE_INIT) {
547184588Sdfr		if (!xdr_opaque_auth(xdrs, &_null_auth)) {
548184588Sdfr			_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
549184588Sdfr			return (FALSE);
550184588Sdfr		}
551184588Sdfr		xdrmbuf_append(xdrs, args);
552184588Sdfr		return (TRUE);
553184588Sdfr	} else {
554184588Sdfr		/*
555184588Sdfr		 * Keep track of this XID + seq pair so that we can do
556184588Sdfr		 * the matching gss_verify_mic in AUTH_VALIDATE.
557184588Sdfr		 */
558184588Sdfr		pr = mem_alloc(sizeof(struct rpc_pending_request));
559184588Sdfr		mtx_lock(&gd->gd_lock);
560184588Sdfr		pr->pr_xid = xid;
561184588Sdfr		pr->pr_seq = seq;
562184588Sdfr		LIST_INSERT_HEAD(&gd->gd_reqs, pr, pr_link);
563184588Sdfr		mtx_unlock(&gd->gd_lock);
564184588Sdfr
565184588Sdfr		/*
566184588Sdfr		 * Checksum serialized RPC header, up to and including
567184588Sdfr		 * credential. For the in-kernel environment, we
568184588Sdfr		 * assume that our XDR stream is on a contiguous
569184588Sdfr		 * memory buffer (e.g. an mbuf).
570184588Sdfr		 */
571184588Sdfr		rpcbuf.length = XDR_GETPOS(xdrs);
572184588Sdfr		XDR_SETPOS(xdrs, 0);
573184588Sdfr		rpcbuf.value = XDR_INLINE(xdrs, rpcbuf.length);
574184588Sdfr
575184588Sdfr		maj_stat = gss_get_mic(&min_stat, gd->gd_ctx, gd->gd_qop,
576184588Sdfr		    &rpcbuf, &checksum);
577184588Sdfr
578184588Sdfr		if (maj_stat != GSS_S_COMPLETE) {
579184588Sdfr			rpc_gss_log_status("gss_get_mic", gd->gd_mech,
580184588Sdfr			    maj_stat, min_stat);
581184588Sdfr			if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
582184588Sdfr				rpc_gss_destroy_context(auth, TRUE);
583184588Sdfr			}
584184588Sdfr			_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
585184588Sdfr			return (FALSE);
586184588Sdfr		}
587184588Sdfr
588184588Sdfr		verf.oa_flavor = RPCSEC_GSS;
589184588Sdfr		verf.oa_base = checksum.value;
590184588Sdfr		verf.oa_length = checksum.length;
591184588Sdfr
592184588Sdfr		xdr_stat = xdr_opaque_auth(xdrs, &verf);
593184588Sdfr		gss_release_buffer(&min_stat, &checksum);
594184588Sdfr		if (!xdr_stat) {
595184588Sdfr			_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
596184588Sdfr			return (FALSE);
597184588Sdfr		}
598184588Sdfr		if (gd->gd_state != RPCSEC_GSS_ESTABLISHED ||
599184588Sdfr		    gd->gd_cred.gc_svc == rpc_gss_svc_none) {
600184588Sdfr			xdrmbuf_append(xdrs, args);
601184588Sdfr			return (TRUE);
602184588Sdfr		} else {
603184588Sdfr			if (!xdr_rpc_gss_wrap_data(&args,
604184588Sdfr				gd->gd_ctx, gd->gd_qop, gd->gd_cred.gc_svc,
605184588Sdfr				seq))
606184588Sdfr				return (FALSE);
607184588Sdfr			xdrmbuf_append(xdrs, args);
608184588Sdfr			return (TRUE);
609184588Sdfr		}
610184588Sdfr	}
611184588Sdfr
612184588Sdfr	return (TRUE);
613184588Sdfr}
614184588Sdfr
615184588Sdfrstatic bool_t
616184588Sdfrrpc_gss_validate(AUTH *auth, uint32_t xid, struct opaque_auth *verf,
617184588Sdfr    struct mbuf **resultsp)
618184588Sdfr{
619184588Sdfr	struct rpc_gss_data	*gd;
620184588Sdfr	struct rpc_pending_request *pr, *npr;
621184588Sdfr	struct rpc_pending_request_list reqs;
622184588Sdfr	gss_qop_t		qop_state;
623184588Sdfr	uint32_t		num, seq;
624184588Sdfr	gss_buffer_desc		signbuf, checksum;
625184588Sdfr	OM_uint32		maj_stat, min_stat;
626184588Sdfr
627184588Sdfr	rpc_gss_log_debug("in rpc_gss_validate()");
628184588Sdfr
629184588Sdfr	gd = AUTH_PRIVATE(auth);
630184588Sdfr
631184588Sdfr	/*
632184588Sdfr	 * The client will call us with a NULL verf when it gives up
633184588Sdfr	 * on an XID.
634184588Sdfr	 */
635184588Sdfr	if (!verf) {
636184588Sdfr		rpc_gss_purge_xid(gd, xid);
637184588Sdfr		return (TRUE);
638184588Sdfr	}
639184588Sdfr
640184588Sdfr	if (gd->gd_state == RPCSEC_GSS_CONTEXT) {
641184588Sdfr		/*
642184588Sdfr		 * Save the on the wire verifier to validate last INIT
643184588Sdfr		 * phase packet after decode if the major status is
644184588Sdfr		 * GSS_S_COMPLETE.
645184588Sdfr		 */
646184588Sdfr		if (gd->gd_verf.value)
647184588Sdfr			xdr_free((xdrproc_t) xdr_gss_buffer_desc,
648184588Sdfr			    (char *) &gd->gd_verf);
649184588Sdfr		gd->gd_verf.value = mem_alloc(verf->oa_length);
650184588Sdfr		if (gd->gd_verf.value == NULL) {
651184588Sdfr			printf("gss_validate: out of memory\n");
652184588Sdfr			_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
653184588Sdfr			m_freem(*resultsp);
654184588Sdfr			*resultsp = NULL;
655184588Sdfr			return (FALSE);
656184588Sdfr		}
657184588Sdfr		memcpy(gd->gd_verf.value, verf->oa_base, verf->oa_length);
658184588Sdfr		gd->gd_verf.length = verf->oa_length;
659184588Sdfr
660184588Sdfr		return (TRUE);
661184588Sdfr	}
662184588Sdfr
663184588Sdfr	/*
664184588Sdfr	 * We need to check the verifier against all the requests
665184588Sdfr	 * we've send for this XID - for unreliable protocols, we
666184588Sdfr	 * retransmit with the same XID but different sequence
667184588Sdfr	 * number. We temporarily take this set of requests out of the
668184588Sdfr	 * list so that we can work through the list without having to
669184588Sdfr	 * hold the lock.
670184588Sdfr	 */
671184588Sdfr	mtx_lock(&gd->gd_lock);
672184588Sdfr	LIST_INIT(&reqs);
673184588Sdfr	LIST_FOREACH_SAFE(pr, &gd->gd_reqs, pr_link, npr) {
674184588Sdfr		if (pr->pr_xid == xid) {
675184588Sdfr			LIST_REMOVE(pr, pr_link);
676184588Sdfr			LIST_INSERT_HEAD(&reqs, pr, pr_link);
677184588Sdfr		}
678184588Sdfr	}
679184588Sdfr	mtx_unlock(&gd->gd_lock);
680184588Sdfr	LIST_FOREACH(pr, &reqs, pr_link) {
681184588Sdfr		if (pr->pr_xid == xid) {
682184588Sdfr			seq = pr->pr_seq;
683184588Sdfr			num = htonl(seq);
684184588Sdfr			signbuf.value = &num;
685184588Sdfr			signbuf.length = sizeof(num);
686184588Sdfr
687184588Sdfr			checksum.value = verf->oa_base;
688184588Sdfr			checksum.length = verf->oa_length;
689184588Sdfr
690184588Sdfr			maj_stat = gss_verify_mic(&min_stat, gd->gd_ctx,
691184588Sdfr			    &signbuf, &checksum, &qop_state);
692184588Sdfr			if (maj_stat != GSS_S_COMPLETE
693184588Sdfr			    || qop_state != gd->gd_qop) {
694184588Sdfr				continue;
695184588Sdfr			}
696184588Sdfr			if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
697184588Sdfr				rpc_gss_destroy_context(auth, TRUE);
698184588Sdfr				break;
699184588Sdfr			}
700184588Sdfr			//rpc_gss_purge_reqs(gd, seq);
701184588Sdfr			LIST_FOREACH_SAFE(pr, &reqs, pr_link, npr)
702184588Sdfr				mem_free(pr, sizeof(*pr));
703184588Sdfr
704184588Sdfr			if (gd->gd_cred.gc_svc == rpc_gss_svc_none) {
705184588Sdfr				return (TRUE);
706184588Sdfr			} else {
707184588Sdfr				if (!xdr_rpc_gss_unwrap_data(resultsp,
708184588Sdfr					gd->gd_ctx, gd->gd_qop,
709184588Sdfr					gd->gd_cred.gc_svc, seq)) {
710184588Sdfr					return (FALSE);
711184588Sdfr				}
712184588Sdfr			}
713184588Sdfr			return (TRUE);
714184588Sdfr		}
715184588Sdfr	}
716184588Sdfr
717184588Sdfr	/*
718184588Sdfr	 * We didn't match - put back any entries for this XID so that
719184588Sdfr	 * a future call to validate can retry.
720184588Sdfr	 */
721184588Sdfr	mtx_lock(&gd->gd_lock);
722184588Sdfr	LIST_FOREACH_SAFE(pr, &reqs, pr_link, npr) {
723184588Sdfr		LIST_REMOVE(pr, pr_link);
724184588Sdfr		LIST_INSERT_HEAD(&gd->gd_reqs, pr, pr_link);
725184588Sdfr	}
726184588Sdfr	mtx_unlock(&gd->gd_lock);
727184588Sdfr
728184588Sdfr	/*
729184588Sdfr	 * Nothing matches - give up.
730184588Sdfr	 */
731184588Sdfr	_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
732184588Sdfr	m_freem(*resultsp);
733184588Sdfr	*resultsp = NULL;
734184588Sdfr	return (FALSE);
735184588Sdfr}
736184588Sdfr
737184588Sdfrstatic bool_t
738184588Sdfrrpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret)
739184588Sdfr{
740184588Sdfr	struct thread		*td = curthread;
741184588Sdfr	struct ucred		*crsave;
742184588Sdfr	struct rpc_gss_data	*gd;
743184588Sdfr	struct rpc_gss_init_res	 gr;
744184588Sdfr	gss_buffer_desc		principal_desc;
745184588Sdfr	gss_buffer_desc		*recv_tokenp, recv_token, send_token;
746184588Sdfr	gss_name_t		name;
747184588Sdfr	OM_uint32		 maj_stat, min_stat, call_stat;
748184588Sdfr	const char		*mech;
749184588Sdfr	struct rpc_callextra	 ext;
750253049Srmacklem	gss_OID			mech_oid;
751253049Srmacklem	gss_OID_set		mechlist;
752184588Sdfr
753184588Sdfr	rpc_gss_log_debug("in rpc_gss_refresh()");
754184588Sdfr
755184588Sdfr	gd = AUTH_PRIVATE(auth);
756184588Sdfr
757184588Sdfr	mtx_lock(&gd->gd_lock);
758184588Sdfr	/*
759184588Sdfr	 * If the context isn't in START state, someone else is
760184588Sdfr	 * refreshing - we wait till they are done. If they fail, they
761184588Sdfr	 * will put the state back to START and we can try (most
762184588Sdfr	 * likely to also fail).
763184588Sdfr	 */
764184588Sdfr	while (gd->gd_state != RPCSEC_GSS_START
765184588Sdfr	    && gd->gd_state != RPCSEC_GSS_ESTABLISHED) {
766184588Sdfr		msleep(gd, &gd->gd_lock, 0, "gssstate", 0);
767184588Sdfr	}
768184588Sdfr	if (gd->gd_state == RPCSEC_GSS_ESTABLISHED) {
769184588Sdfr		mtx_unlock(&gd->gd_lock);
770184588Sdfr		return (TRUE);
771184588Sdfr	}
772184588Sdfr	gd->gd_state = RPCSEC_GSS_CONTEXT;
773184588Sdfr	mtx_unlock(&gd->gd_lock);
774184588Sdfr
775194878Srmacklem	gd->gd_cred.gc_proc = RPCSEC_GSS_INIT;
776194878Srmacklem	gd->gd_cred.gc_seq = 0;
777194878Srmacklem
778253049Srmacklem	/*
779253049Srmacklem	 * For KerberosV, if there is a client principal name, that implies
780253049Srmacklem	 * that this is a host based initiator credential in the default
781253049Srmacklem	 * keytab file. For this case, it is necessary to do a
782253049Srmacklem	 * gss_acquire_cred(). When this is done, the gssd daemon will
783253049Srmacklem	 * do the equivalent of "kinit -k" to put a TGT for the name in
784253049Srmacklem	 * the credential cache file for the gssd daemon.
785253049Srmacklem	 */
786253049Srmacklem	if (gd->gd_clntprincipal != NULL &&
787253049Srmacklem	    rpc_gss_mech_to_oid("kerberosv5", &mech_oid) &&
788253049Srmacklem	    gd->gd_mech == mech_oid) {
789253049Srmacklem		/* Get rid of any old credential. */
790253049Srmacklem		if (gd->gd_options.my_cred != GSS_C_NO_CREDENTIAL) {
791253049Srmacklem			gss_release_cred(&min_stat, &gd->gd_options.my_cred);
792253049Srmacklem			gd->gd_options.my_cred = GSS_C_NO_CREDENTIAL;
793253049Srmacklem		}
794253049Srmacklem
795253049Srmacklem		/*
796253049Srmacklem		 * The mechanism must be set to KerberosV for acquisition
797253049Srmacklem		 * of credentials to work reliably.
798253049Srmacklem		 */
799253049Srmacklem		maj_stat = gss_create_empty_oid_set(&min_stat, &mechlist);
800253049Srmacklem		if (maj_stat != GSS_S_COMPLETE) {
801253049Srmacklem			options_ret->major_status = maj_stat;
802253049Srmacklem			options_ret->minor_status = min_stat;
803253049Srmacklem			goto out;
804253049Srmacklem		}
805253049Srmacklem		maj_stat = gss_add_oid_set_member(&min_stat, gd->gd_mech,
806253049Srmacklem		    &mechlist);
807253049Srmacklem		if (maj_stat != GSS_S_COMPLETE) {
808253049Srmacklem			options_ret->major_status = maj_stat;
809253049Srmacklem			options_ret->minor_status = min_stat;
810253049Srmacklem			gss_release_oid_set(&min_stat, &mechlist);
811253049Srmacklem			goto out;
812253049Srmacklem		}
813253049Srmacklem
814253049Srmacklem		principal_desc.value = (void *)gd->gd_clntprincipal;
815253049Srmacklem		principal_desc.length = strlen(gd->gd_clntprincipal);
816253049Srmacklem		maj_stat = gss_import_name(&min_stat, &principal_desc,
817253049Srmacklem		    GSS_C_NT_HOSTBASED_SERVICE, &name);
818253049Srmacklem		if (maj_stat != GSS_S_COMPLETE) {
819253049Srmacklem			options_ret->major_status = maj_stat;
820253049Srmacklem			options_ret->minor_status = min_stat;
821253049Srmacklem			gss_release_oid_set(&min_stat, &mechlist);
822253049Srmacklem			goto out;
823253049Srmacklem		}
824253049Srmacklem		/* Acquire the credentials. */
825253049Srmacklem		maj_stat = gss_acquire_cred(&min_stat, name, 0,
826253049Srmacklem		    mechlist, GSS_C_INITIATE,
827253049Srmacklem		    &gd->gd_options.my_cred, NULL, NULL);
828253049Srmacklem		gss_release_name(&min_stat, &name);
829253049Srmacklem		gss_release_oid_set(&min_stat, &mechlist);
830253049Srmacklem		if (maj_stat != GSS_S_COMPLETE) {
831253049Srmacklem			options_ret->major_status = maj_stat;
832253049Srmacklem			options_ret->minor_status = min_stat;
833253049Srmacklem			goto out;
834253049Srmacklem		}
835253049Srmacklem	}
836253049Srmacklem
837184588Sdfr	principal_desc.value = (void *)gd->gd_principal;
838184588Sdfr	principal_desc.length = strlen(gd->gd_principal);
839184588Sdfr	maj_stat = gss_import_name(&min_stat, &principal_desc,
840184588Sdfr	    GSS_C_NT_HOSTBASED_SERVICE, &name);
841184588Sdfr	if (maj_stat != GSS_S_COMPLETE) {
842184588Sdfr		options_ret->major_status = maj_stat;
843184588Sdfr		options_ret->minor_status = min_stat;
844184588Sdfr		goto out;
845184588Sdfr	}
846184588Sdfr
847184588Sdfr	/* GSS context establishment loop. */
848184588Sdfr	memset(&recv_token, 0, sizeof(recv_token));
849184588Sdfr	memset(&gr, 0, sizeof(gr));
850184588Sdfr	memset(options_ret, 0, sizeof(*options_ret));
851184588Sdfr	options_ret->major_status = GSS_S_FAILURE;
852184588Sdfr	recv_tokenp = GSS_C_NO_BUFFER;
853184588Sdfr
854184588Sdfr	for (;;) {
855184588Sdfr		crsave = td->td_ucred;
856184588Sdfr		td->td_ucred = gd->gd_ucred;
857184588Sdfr		maj_stat = gss_init_sec_context(&min_stat,
858184588Sdfr		    gd->gd_options.my_cred,
859184588Sdfr		    &gd->gd_ctx,
860184588Sdfr		    name,
861184588Sdfr		    gd->gd_mech,
862184588Sdfr		    gd->gd_options.req_flags,
863184588Sdfr		    gd->gd_options.time_req,
864184588Sdfr		    gd->gd_options.input_channel_bindings,
865184588Sdfr		    recv_tokenp,
866184588Sdfr		    &gd->gd_mech,	/* used mech */
867184588Sdfr		    &send_token,
868184588Sdfr		    &options_ret->ret_flags,
869184588Sdfr		    &options_ret->time_req);
870184588Sdfr		td->td_ucred = crsave;
871184588Sdfr
872184588Sdfr		/*
873184588Sdfr		 * Free the token which we got from the server (if
874184588Sdfr		 * any).  Remember that this was allocated by XDR, not
875184588Sdfr		 * GSS-API.
876184588Sdfr		 */
877184588Sdfr		if (recv_tokenp != GSS_C_NO_BUFFER) {
878184588Sdfr			xdr_free((xdrproc_t) xdr_gss_buffer_desc,
879184588Sdfr			    (char *) &recv_token);
880184588Sdfr			recv_tokenp = GSS_C_NO_BUFFER;
881184588Sdfr		}
882184588Sdfr		if (gd->gd_mech && rpc_gss_oid_to_mech(gd->gd_mech, &mech)) {
883184588Sdfr			strlcpy(options_ret->actual_mechanism,
884184588Sdfr			    mech,
885184588Sdfr			    sizeof(options_ret->actual_mechanism));
886184588Sdfr		}
887184588Sdfr		if (maj_stat != GSS_S_COMPLETE &&
888184588Sdfr		    maj_stat != GSS_S_CONTINUE_NEEDED) {
889184588Sdfr			rpc_gss_log_status("gss_init_sec_context", gd->gd_mech,
890184588Sdfr			    maj_stat, min_stat);
891184588Sdfr			options_ret->major_status = maj_stat;
892184588Sdfr			options_ret->minor_status = min_stat;
893184588Sdfr			break;
894184588Sdfr		}
895184588Sdfr		if (send_token.length != 0) {
896184588Sdfr			memset(&gr, 0, sizeof(gr));
897184588Sdfr
898184588Sdfr			bzero(&ext, sizeof(ext));
899184588Sdfr			ext.rc_auth = auth;
900184588Sdfr			call_stat = CLNT_CALL_EXT(gd->gd_clnt, &ext, NULLPROC,
901184588Sdfr			    (xdrproc_t)xdr_gss_buffer_desc,
902184588Sdfr			    &send_token,
903184588Sdfr			    (xdrproc_t)xdr_rpc_gss_init_res,
904184588Sdfr			    (caddr_t)&gr, AUTH_TIMEOUT);
905184588Sdfr
906184588Sdfr			gss_release_buffer(&min_stat, &send_token);
907184588Sdfr
908184588Sdfr			if (call_stat != RPC_SUCCESS)
909184588Sdfr				break;
910184588Sdfr
911184588Sdfr			if (gr.gr_major != GSS_S_COMPLETE &&
912184588Sdfr			    gr.gr_major != GSS_S_CONTINUE_NEEDED) {
913184588Sdfr				rpc_gss_log_status("server reply", gd->gd_mech,
914184588Sdfr				    gr.gr_major, gr.gr_minor);
915184588Sdfr				options_ret->major_status = gr.gr_major;
916184588Sdfr				options_ret->minor_status = gr.gr_minor;
917184588Sdfr				break;
918184588Sdfr			}
919184588Sdfr
920184588Sdfr			/*
921184588Sdfr			 * Save the server's gr_handle value, freeing
922184588Sdfr			 * what we have already (remember that this
923184588Sdfr			 * was allocated by XDR, not GSS-API).
924184588Sdfr			 */
925184588Sdfr			if (gr.gr_handle.length != 0) {
926184588Sdfr				xdr_free((xdrproc_t) xdr_gss_buffer_desc,
927184588Sdfr				    (char *) &gd->gd_cred.gc_handle);
928184588Sdfr				gd->gd_cred.gc_handle = gr.gr_handle;
929184588Sdfr			}
930184588Sdfr
931184588Sdfr			/*
932184588Sdfr			 * Save the server's token as well.
933184588Sdfr			 */
934184588Sdfr			if (gr.gr_token.length != 0) {
935184588Sdfr				recv_token = gr.gr_token;
936184588Sdfr				recv_tokenp = &recv_token;
937184588Sdfr			}
938184588Sdfr
939184588Sdfr			/*
940184588Sdfr			 * Since we have copied out all the bits of gr
941184588Sdfr			 * which XDR allocated for us, we don't need
942184588Sdfr			 * to free it.
943184588Sdfr			 */
944184588Sdfr			gd->gd_cred.gc_proc = RPCSEC_GSS_CONTINUE_INIT;
945184588Sdfr		}
946184588Sdfr
947184588Sdfr		if (maj_stat == GSS_S_COMPLETE) {
948184588Sdfr			gss_buffer_desc   bufin;
949184588Sdfr			u_int seq, qop_state = 0;
950184588Sdfr
951184588Sdfr			/*
952184588Sdfr			 * gss header verifier,
953184588Sdfr			 * usually checked in gss_validate
954184588Sdfr			 */
955184588Sdfr			seq = htonl(gr.gr_win);
956184588Sdfr			bufin.value = (unsigned char *)&seq;
957184588Sdfr			bufin.length = sizeof(seq);
958184588Sdfr
959184588Sdfr			maj_stat = gss_verify_mic(&min_stat, gd->gd_ctx,
960184588Sdfr			    &bufin, &gd->gd_verf, &qop_state);
961184588Sdfr
962184588Sdfr			if (maj_stat != GSS_S_COMPLETE ||
963184588Sdfr			    qop_state != gd->gd_qop) {
964184588Sdfr				rpc_gss_log_status("gss_verify_mic", gd->gd_mech,
965184588Sdfr				    maj_stat, min_stat);
966184588Sdfr				if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
967184588Sdfr					rpc_gss_destroy_context(auth, TRUE);
968184588Sdfr				}
969184588Sdfr				_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR,
970184588Sdfr				    EPERM);
971184588Sdfr				options_ret->major_status = maj_stat;
972184588Sdfr				options_ret->minor_status = min_stat;
973184588Sdfr				break;
974184588Sdfr			}
975184588Sdfr
976184588Sdfr			options_ret->major_status = GSS_S_COMPLETE;
977184588Sdfr			options_ret->minor_status = 0;
978184588Sdfr			options_ret->rpcsec_version = gd->gd_cred.gc_version;
979184588Sdfr			options_ret->gss_context = gd->gd_ctx;
980184588Sdfr
981184588Sdfr			gd->gd_cred.gc_proc = RPCSEC_GSS_DATA;
982184588Sdfr			gd->gd_seq = 1;
983184588Sdfr			gd->gd_win = gr.gr_win;
984184588Sdfr			break;
985184588Sdfr		}
986184588Sdfr	}
987184588Sdfr
988184588Sdfr	gss_release_name(&min_stat, &name);
989184588Sdfr	xdr_free((xdrproc_t) xdr_gss_buffer_desc,
990184588Sdfr	    (char *) &gd->gd_verf);
991184588Sdfr
992184588Sdfrout:
993184588Sdfr	/* End context negotiation loop. */
994184588Sdfr	if (gd->gd_cred.gc_proc != RPCSEC_GSS_DATA) {
995184588Sdfr		rpc_createerr.cf_stat = RPC_AUTHERROR;
996184588Sdfr		_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
997184588Sdfr		if (gd->gd_ctx) {
998184588Sdfr			gss_delete_sec_context(&min_stat, &gd->gd_ctx,
999184588Sdfr				GSS_C_NO_BUFFER);
1000184588Sdfr		}
1001184588Sdfr		mtx_lock(&gd->gd_lock);
1002184588Sdfr		gd->gd_state = RPCSEC_GSS_START;
1003184588Sdfr		wakeup(gd);
1004184588Sdfr		mtx_unlock(&gd->gd_lock);
1005184588Sdfr		return (FALSE);
1006184588Sdfr	}
1007184588Sdfr
1008184588Sdfr	mtx_lock(&gd->gd_lock);
1009184588Sdfr	gd->gd_state = RPCSEC_GSS_ESTABLISHED;
1010184588Sdfr	wakeup(gd);
1011184588Sdfr	mtx_unlock(&gd->gd_lock);
1012184588Sdfr
1013184588Sdfr	return (TRUE);
1014184588Sdfr}
1015184588Sdfr
1016184588Sdfrstatic bool_t
1017184588Sdfrrpc_gss_refresh(AUTH *auth, void *msg)
1018184588Sdfr{
1019184588Sdfr	struct rpc_msg *reply = (struct rpc_msg *) msg;
1020184588Sdfr	rpc_gss_options_ret_t options;
1021195246Srmacklem	struct rpc_gss_data *gd;
1022184588Sdfr
1023195246Srmacklem	gd = AUTH_PRIVATE(auth);
1024195246Srmacklem
1025184588Sdfr	/*
1026195246Srmacklem	 * If the context is in DESTROYING state, then just return, since
1027195246Srmacklem	 * there is no point in refreshing the credentials.
1028195246Srmacklem	 */
1029195246Srmacklem	mtx_lock(&gd->gd_lock);
1030195246Srmacklem	if (gd->gd_state == RPCSEC_GSS_DESTROYING) {
1031195246Srmacklem		mtx_unlock(&gd->gd_lock);
1032195246Srmacklem		return (FALSE);
1033195246Srmacklem	}
1034195246Srmacklem	mtx_unlock(&gd->gd_lock);
1035195246Srmacklem
1036195246Srmacklem	/*
1037184588Sdfr	 * If the error was RPCSEC_GSS_CREDPROBLEM of
1038184588Sdfr	 * RPCSEC_GSS_CTXPROBLEM we start again from scratch. All
1039184588Sdfr	 * other errors are fatal.
1040184588Sdfr	 */
1041184588Sdfr	if (reply->rm_reply.rp_stat == MSG_DENIED
1042184588Sdfr	    && reply->rm_reply.rp_rjct.rj_stat == AUTH_ERROR
1043184588Sdfr	    && (reply->rm_reply.rp_rjct.rj_why == RPCSEC_GSS_CREDPROBLEM
1044184588Sdfr		|| reply->rm_reply.rp_rjct.rj_why == RPCSEC_GSS_CTXPROBLEM)) {
1045184588Sdfr		rpc_gss_destroy_context(auth, FALSE);
1046184588Sdfr		memset(&options, 0, sizeof(options));
1047184588Sdfr		return (rpc_gss_init(auth, &options));
1048184588Sdfr	}
1049184588Sdfr
1050184588Sdfr	return (FALSE);
1051184588Sdfr}
1052184588Sdfr
1053184588Sdfrstatic void
1054184588Sdfrrpc_gss_destroy_context(AUTH *auth, bool_t send_destroy)
1055184588Sdfr{
1056184588Sdfr	struct rpc_gss_data	*gd;
1057184588Sdfr	struct rpc_pending_request *pr;
1058184588Sdfr	OM_uint32		 min_stat;
1059184588Sdfr	struct rpc_callextra	 ext;
1060184588Sdfr
1061184588Sdfr	rpc_gss_log_debug("in rpc_gss_destroy_context()");
1062184588Sdfr
1063184588Sdfr	gd = AUTH_PRIVATE(auth);
1064184588Sdfr
1065184588Sdfr	mtx_lock(&gd->gd_lock);
1066184588Sdfr	/*
1067184588Sdfr	 * If the context isn't in ESTABISHED state, someone else is
1068184588Sdfr	 * destroying/refreshing - we wait till they are done.
1069184588Sdfr	 */
1070184588Sdfr	if (gd->gd_state != RPCSEC_GSS_ESTABLISHED) {
1071184588Sdfr		while (gd->gd_state != RPCSEC_GSS_START
1072184588Sdfr		    && gd->gd_state != RPCSEC_GSS_ESTABLISHED)
1073184588Sdfr			msleep(gd, &gd->gd_lock, 0, "gssstate", 0);
1074184588Sdfr		mtx_unlock(&gd->gd_lock);
1075184588Sdfr		return;
1076184588Sdfr	}
1077184588Sdfr	gd->gd_state = RPCSEC_GSS_DESTROYING;
1078184588Sdfr	mtx_unlock(&gd->gd_lock);
1079184588Sdfr
1080184588Sdfr	if (send_destroy) {
1081184588Sdfr		gd->gd_cred.gc_proc = RPCSEC_GSS_DESTROY;
1082184588Sdfr		bzero(&ext, sizeof(ext));
1083184588Sdfr		ext.rc_auth = auth;
1084184588Sdfr		CLNT_CALL_EXT(gd->gd_clnt, &ext, NULLPROC,
1085184588Sdfr		    (xdrproc_t)xdr_void, NULL,
1086184588Sdfr		    (xdrproc_t)xdr_void, NULL, AUTH_TIMEOUT);
1087184588Sdfr	}
1088184588Sdfr
1089184588Sdfr	while ((pr = LIST_FIRST(&gd->gd_reqs)) != NULL) {
1090184588Sdfr		LIST_REMOVE(pr, pr_link);
1091184588Sdfr		mem_free(pr, sizeof(*pr));
1092184588Sdfr	}
1093184588Sdfr
1094184588Sdfr	/*
1095184588Sdfr	 * Free the context token. Remember that this was
1096184588Sdfr	 * allocated by XDR, not GSS-API.
1097184588Sdfr	 */
1098184588Sdfr	xdr_free((xdrproc_t) xdr_gss_buffer_desc,
1099184588Sdfr	    (char *) &gd->gd_cred.gc_handle);
1100184588Sdfr	gd->gd_cred.gc_handle.length = 0;
1101184588Sdfr
1102184588Sdfr	if (gd->gd_ctx != GSS_C_NO_CONTEXT)
1103184588Sdfr		gss_delete_sec_context(&min_stat, &gd->gd_ctx, NULL);
1104184588Sdfr
1105184588Sdfr	mtx_lock(&gd->gd_lock);
1106184588Sdfr	gd->gd_state = RPCSEC_GSS_START;
1107184588Sdfr	wakeup(gd);
1108184588Sdfr	mtx_unlock(&gd->gd_lock);
1109184588Sdfr}
1110184588Sdfr
1111184588Sdfrstatic void
1112184588Sdfrrpc_gss_destroy(AUTH *auth)
1113184588Sdfr{
1114184588Sdfr	struct rpc_gss_data	*gd;
1115184588Sdfr
1116184588Sdfr	rpc_gss_log_debug("in rpc_gss_destroy()");
1117184588Sdfr
1118184588Sdfr	gd = AUTH_PRIVATE(auth);
1119184588Sdfr
1120184588Sdfr	if (!refcount_release(&gd->gd_refs))
1121184588Sdfr		return;
1122184588Sdfr
1123184588Sdfr	rpc_gss_destroy_context(auth, TRUE);
1124184588Sdfr
1125184588Sdfr	CLNT_RELEASE(gd->gd_clnt);
1126184588Sdfr	crfree(gd->gd_ucred);
1127184588Sdfr	free(gd->gd_principal, M_RPC);
1128253049Srmacklem	if (gd->gd_clntprincipal != NULL)
1129253049Srmacklem		free(gd->gd_clntprincipal, M_RPC);
1130184588Sdfr	if (gd->gd_verf.value)
1131184588Sdfr		xdr_free((xdrproc_t) xdr_gss_buffer_desc,
1132184588Sdfr		    (char *) &gd->gd_verf);
1133184588Sdfr	mtx_destroy(&gd->gd_lock);
1134184588Sdfr
1135184588Sdfr	mem_free(gd, sizeof(*gd));
1136184588Sdfr	mem_free(auth, sizeof(*auth));
1137184588Sdfr}
1138184588Sdfr
1139184588Sdfrint
1140184588Sdfrrpc_gss_max_data_length(AUTH *auth, int max_tp_unit_len)
1141184588Sdfr{
1142184588Sdfr	struct rpc_gss_data	*gd;
1143184588Sdfr	int			want_conf;
1144184588Sdfr	OM_uint32		max;
1145184588Sdfr	OM_uint32		maj_stat, min_stat;
1146184588Sdfr	int			result;
1147184588Sdfr
1148184588Sdfr	gd = AUTH_PRIVATE(auth);
1149184588Sdfr
1150184588Sdfr	switch (gd->gd_cred.gc_svc) {
1151184588Sdfr	case rpc_gss_svc_none:
1152184588Sdfr		return (max_tp_unit_len);
1153184588Sdfr		break;
1154184588Sdfr
1155184588Sdfr	case rpc_gss_svc_default:
1156184588Sdfr	case rpc_gss_svc_integrity:
1157184588Sdfr		want_conf = FALSE;
1158184588Sdfr		break;
1159184588Sdfr
1160184588Sdfr	case rpc_gss_svc_privacy:
1161184588Sdfr		want_conf = TRUE;
1162184588Sdfr		break;
1163184588Sdfr
1164184588Sdfr	default:
1165184588Sdfr		return (0);
1166184588Sdfr	}
1167184588Sdfr
1168184588Sdfr	maj_stat = gss_wrap_size_limit(&min_stat, gd->gd_ctx, want_conf,
1169184588Sdfr	    gd->gd_qop, max_tp_unit_len, &max);
1170184588Sdfr
1171184588Sdfr	if (maj_stat == GSS_S_COMPLETE) {
1172184588Sdfr		result = (int) max;
1173184588Sdfr		if (result < 0)
1174184588Sdfr			result = 0;
1175184588Sdfr		return (result);
1176184588Sdfr	} else {
1177184588Sdfr		rpc_gss_log_status("gss_wrap_size_limit", gd->gd_mech,
1178184588Sdfr		    maj_stat, min_stat);
1179184588Sdfr		return (0);
1180184588Sdfr	}
1181184588Sdfr}
1182