xref: /freebsd/sys/rpc/rpcsec_gss/svc_rpcsec_gss.c (revision aa64588d28258aef88cc33b8043112e8856948d0)
1 /*-
2  * Copyright (c) 2008 Doug Rabson
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 /*
27   svc_rpcsec_gss.c
28 
29   Copyright (c) 2000 The Regents of the University of Michigan.
30   All rights reserved.
31 
32   Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
33   All rights reserved, all wrongs reversed.
34 
35   Redistribution and use in source and binary forms, with or without
36   modification, are permitted provided that the following conditions
37   are met:
38 
39   1. Redistributions of source code must retain the above copyright
40      notice, this list of conditions and the following disclaimer.
41   2. Redistributions in binary form must reproduce the above copyright
42      notice, this list of conditions and the following disclaimer in the
43      documentation and/or other materials provided with the distribution.
44   3. Neither the name of the University nor the names of its
45      contributors may be used to endorse or promote products derived
46      from this software without specific prior written permission.
47 
48   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
49   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
50   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
51   DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
52   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
53   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
54   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
55   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
56   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
57   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
58   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
59 
60   $Id: svc_auth_gss.c,v 1.27 2002/01/15 15:43:00 andros Exp $
61  */
62 
63 #include <sys/cdefs.h>
64 __FBSDID("$FreeBSD$");
65 
66 #include <sys/param.h>
67 #include <sys/systm.h>
68 #include <sys/jail.h>
69 #include <sys/kernel.h>
70 #include <sys/kobj.h>
71 #include <sys/lock.h>
72 #include <sys/malloc.h>
73 #include <sys/mbuf.h>
74 #include <sys/mutex.h>
75 #include <sys/proc.h>
76 #include <sys/sx.h>
77 #include <sys/ucred.h>
78 
79 #include <rpc/rpc.h>
80 #include <rpc/rpcsec_gss.h>
81 
82 #include "rpcsec_gss_int.h"
83 
84 static bool_t   svc_rpc_gss_wrap(SVCAUTH *, struct mbuf **);
85 static bool_t   svc_rpc_gss_unwrap(SVCAUTH *, struct mbuf **);
86 static void     svc_rpc_gss_release(SVCAUTH *);
87 static enum auth_stat svc_rpc_gss(struct svc_req *, struct rpc_msg *);
88 static int rpc_gss_svc_getcred(struct svc_req *, struct ucred **, int *);
89 
90 static struct svc_auth_ops svc_auth_gss_ops = {
91 	svc_rpc_gss_wrap,
92 	svc_rpc_gss_unwrap,
93 	svc_rpc_gss_release,
94 };
95 
96 struct sx svc_rpc_gss_lock;
97 
98 struct svc_rpc_gss_callback {
99 	SLIST_ENTRY(svc_rpc_gss_callback) cb_link;
100 	rpc_gss_callback_t	cb_callback;
101 };
102 static SLIST_HEAD(svc_rpc_gss_callback_list, svc_rpc_gss_callback)
103 	svc_rpc_gss_callbacks = SLIST_HEAD_INITIALIZER(svc_rpc_gss_callbacks);
104 
105 struct svc_rpc_gss_svc_name {
106 	SLIST_ENTRY(svc_rpc_gss_svc_name) sn_link;
107 	char			*sn_principal;
108 	gss_OID			sn_mech;
109 	u_int			sn_req_time;
110 	gss_cred_id_t		sn_cred;
111 	u_int			sn_program;
112 	u_int			sn_version;
113 };
114 static SLIST_HEAD(svc_rpc_gss_svc_name_list, svc_rpc_gss_svc_name)
115 	svc_rpc_gss_svc_names = SLIST_HEAD_INITIALIZER(svc_rpc_gss_svc_names);
116 
117 enum svc_rpc_gss_client_state {
118 	CLIENT_NEW,				/* still authenticating */
119 	CLIENT_ESTABLISHED,			/* context established */
120 	CLIENT_STALE				/* garbage to collect */
121 };
122 
123 #define SVC_RPC_GSS_SEQWINDOW	128
124 #ifndef RPCAUTH_UNIXGIDS
125 #define RPCAUTH_UNIXGIDS	16
126 #endif
127 
128 struct svc_rpc_gss_clientid {
129 	unsigned long		ci_hostid;
130 	uint32_t		ci_boottime;
131 	uint32_t		ci_id;
132 };
133 
134 struct svc_rpc_gss_client {
135 	TAILQ_ENTRY(svc_rpc_gss_client) cl_link;
136 	TAILQ_ENTRY(svc_rpc_gss_client) cl_alllink;
137 	volatile u_int		cl_refs;
138 	struct sx		cl_lock;
139 	struct svc_rpc_gss_clientid cl_id;
140 	time_t			cl_expiration;	/* when to gc */
141 	enum svc_rpc_gss_client_state cl_state;	/* client state */
142 	bool_t			cl_locked;	/* fixed service+qop */
143 	gss_ctx_id_t		cl_ctx;		/* context id */
144 	gss_cred_id_t		cl_creds;	/* delegated creds */
145 	gss_name_t		cl_cname;	/* client name */
146 	struct svc_rpc_gss_svc_name *cl_sname;	/* server name used */
147 	rpc_gss_rawcred_t	cl_rawcred;	/* raw credentials */
148 	rpc_gss_ucred_t		cl_ucred;	/* unix-style credentials */
149 	struct ucred		*cl_cred;	/* kernel-style credentials */
150 	int			cl_rpcflavor;	/* RPC pseudo sec flavor */
151 	bool_t			cl_done_callback; /* TRUE after call */
152 	void			*cl_cookie;	/* user cookie from callback */
153 	gid_t			cl_gid_storage[RPCAUTH_UNIXGIDS];
154 	gss_OID			cl_mech;	/* mechanism */
155 	gss_qop_t		cl_qop;		/* quality of protection */
156 	uint32_t		cl_seqlast;	/* sequence window origin */
157 	uint32_t		cl_seqmask[SVC_RPC_GSS_SEQWINDOW/32]; /* bitmask of seqnums */
158 };
159 TAILQ_HEAD(svc_rpc_gss_client_list, svc_rpc_gss_client);
160 
161 /*
162  * This structure holds enough information to unwrap arguments or wrap
163  * results for a given request. We use the rq_clntcred area for this
164  * (which is a per-request buffer).
165  */
166 struct svc_rpc_gss_cookedcred {
167 	struct svc_rpc_gss_client *cc_client;
168 	rpc_gss_service_t	cc_service;
169 	uint32_t		cc_seq;
170 };
171 
172 #define CLIENT_HASH_SIZE	256
173 #define CLIENT_MAX		128
174 struct svc_rpc_gss_client_list svc_rpc_gss_client_hash[CLIENT_HASH_SIZE];
175 struct svc_rpc_gss_client_list svc_rpc_gss_clients;
176 static size_t svc_rpc_gss_client_count;
177 static uint32_t svc_rpc_gss_next_clientid = 1;
178 
179 static void
180 svc_rpc_gss_init(void *arg)
181 {
182 	int i;
183 
184 	for (i = 0; i < CLIENT_HASH_SIZE; i++)
185 		TAILQ_INIT(&svc_rpc_gss_client_hash[i]);
186 	TAILQ_INIT(&svc_rpc_gss_clients);
187 	svc_auth_reg(RPCSEC_GSS, svc_rpc_gss, rpc_gss_svc_getcred);
188 	sx_init(&svc_rpc_gss_lock, "gsslock");
189 }
190 SYSINIT(svc_rpc_gss_init, SI_SUB_KMEM, SI_ORDER_ANY, svc_rpc_gss_init, NULL);
191 
192 bool_t
193 rpc_gss_set_callback(rpc_gss_callback_t *cb)
194 {
195 	struct svc_rpc_gss_callback *scb;
196 
197 	scb = mem_alloc(sizeof(struct svc_rpc_gss_callback));
198 	if (!scb) {
199 		_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
200 		return (FALSE);
201 	}
202 	scb->cb_callback = *cb;
203 	sx_xlock(&svc_rpc_gss_lock);
204 	SLIST_INSERT_HEAD(&svc_rpc_gss_callbacks, scb, cb_link);
205 	sx_xunlock(&svc_rpc_gss_lock);
206 
207 	return (TRUE);
208 }
209 
210 void
211 rpc_gss_clear_callback(rpc_gss_callback_t *cb)
212 {
213 	struct svc_rpc_gss_callback *scb;
214 
215 	sx_xlock(&svc_rpc_gss_lock);
216 	SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
217 		if (scb->cb_callback.program == cb->program
218 		    && scb->cb_callback.version == cb->version
219 		    && scb->cb_callback.callback == cb->callback) {
220 			SLIST_REMOVE(&svc_rpc_gss_callbacks, scb,
221 			    svc_rpc_gss_callback, cb_link);
222 			sx_xunlock(&svc_rpc_gss_lock);
223 			mem_free(scb, sizeof(*scb));
224 			return;
225 		}
226 	}
227 	sx_xunlock(&svc_rpc_gss_lock);
228 }
229 
230 static bool_t
231 rpc_gss_acquire_svc_cred(struct svc_rpc_gss_svc_name *sname)
232 {
233 	OM_uint32		maj_stat, min_stat;
234 	gss_buffer_desc		namebuf;
235 	gss_name_t		name;
236 	gss_OID_set_desc	oid_set;
237 
238 	oid_set.count = 1;
239 	oid_set.elements = sname->sn_mech;
240 
241 	namebuf.value = (void *) sname->sn_principal;
242 	namebuf.length = strlen(sname->sn_principal);
243 
244 	maj_stat = gss_import_name(&min_stat, &namebuf,
245 				   GSS_C_NT_HOSTBASED_SERVICE, &name);
246 	if (maj_stat != GSS_S_COMPLETE)
247 		return (FALSE);
248 
249 	if (sname->sn_cred != GSS_C_NO_CREDENTIAL)
250 		gss_release_cred(&min_stat, &sname->sn_cred);
251 
252 	maj_stat = gss_acquire_cred(&min_stat, name,
253 	    sname->sn_req_time, &oid_set, GSS_C_ACCEPT, &sname->sn_cred,
254 	    NULL, NULL);
255 	if (maj_stat != GSS_S_COMPLETE) {
256 		gss_release_name(&min_stat, &name);
257 		return (FALSE);
258 	}
259 	gss_release_name(&min_stat, &name);
260 
261 	return (TRUE);
262 }
263 
264 bool_t
265 rpc_gss_set_svc_name(const char *principal, const char *mechanism,
266     u_int req_time, u_int program, u_int version)
267 {
268 	struct svc_rpc_gss_svc_name *sname;
269 	gss_OID			mech_oid;
270 
271 	if (!rpc_gss_mech_to_oid(mechanism, &mech_oid))
272 		return (FALSE);
273 
274 	sname = mem_alloc(sizeof(*sname));
275 	if (!sname)
276 		return (FALSE);
277 	sname->sn_principal = strdup(principal, M_RPC);
278 	sname->sn_mech = mech_oid;
279 	sname->sn_req_time = req_time;
280 	sname->sn_cred = GSS_C_NO_CREDENTIAL;
281 	sname->sn_program = program;
282 	sname->sn_version = version;
283 
284 	if (!rpc_gss_acquire_svc_cred(sname)) {
285 		free(sname->sn_principal, M_RPC);
286 		mem_free(sname, sizeof(*sname));
287 		return (FALSE);
288 	}
289 
290 	sx_xlock(&svc_rpc_gss_lock);
291 	SLIST_INSERT_HEAD(&svc_rpc_gss_svc_names, sname, sn_link);
292 	sx_xunlock(&svc_rpc_gss_lock);
293 
294 	return (TRUE);
295 }
296 
297 void
298 rpc_gss_clear_svc_name(u_int program, u_int version)
299 {
300 	OM_uint32		min_stat;
301 	struct svc_rpc_gss_svc_name *sname;
302 
303 	sx_xlock(&svc_rpc_gss_lock);
304 	SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
305 		if (sname->sn_program == program
306 		    && sname->sn_version == version) {
307 			SLIST_REMOVE(&svc_rpc_gss_svc_names, sname,
308 			    svc_rpc_gss_svc_name, sn_link);
309 			sx_xunlock(&svc_rpc_gss_lock);
310 			gss_release_cred(&min_stat, &sname->sn_cred);
311 			free(sname->sn_principal, M_RPC);
312 			mem_free(sname, sizeof(*sname));
313 			return;
314 		}
315 	}
316 	sx_xunlock(&svc_rpc_gss_lock);
317 }
318 
319 bool_t
320 rpc_gss_get_principal_name(rpc_gss_principal_t *principal,
321     const char *mech, const char *name, const char *node, const char *domain)
322 {
323 	OM_uint32		maj_stat, min_stat;
324 	gss_OID			mech_oid;
325 	size_t			namelen;
326 	gss_buffer_desc		buf;
327 	gss_name_t		gss_name, gss_mech_name;
328 	rpc_gss_principal_t	result;
329 
330 	if (!rpc_gss_mech_to_oid(mech, &mech_oid))
331 		return (FALSE);
332 
333 	/*
334 	 * Construct a gss_buffer containing the full name formatted
335 	 * as "name/node@domain" where node and domain are optional.
336 	 */
337 	namelen = strlen(name);
338 	if (node) {
339 		namelen += strlen(node) + 1;
340 	}
341 	if (domain) {
342 		namelen += strlen(domain) + 1;
343 	}
344 
345 	buf.value = mem_alloc(namelen);
346 	buf.length = namelen;
347 	strcpy((char *) buf.value, name);
348 	if (node) {
349 		strcat((char *) buf.value, "/");
350 		strcat((char *) buf.value, node);
351 	}
352 	if (domain) {
353 		strcat((char *) buf.value, "@");
354 		strcat((char *) buf.value, domain);
355 	}
356 
357 	/*
358 	 * Convert that to a gss_name_t and then convert that to a
359 	 * mechanism name in the selected mechanism.
360 	 */
361 	maj_stat = gss_import_name(&min_stat, &buf,
362 	    GSS_C_NT_USER_NAME, &gss_name);
363 	mem_free(buf.value, buf.length);
364 	if (maj_stat != GSS_S_COMPLETE) {
365 		rpc_gss_log_status("gss_import_name", mech_oid, maj_stat, min_stat);
366 		return (FALSE);
367 	}
368 	maj_stat = gss_canonicalize_name(&min_stat, gss_name, mech_oid,
369 	    &gss_mech_name);
370 	if (maj_stat != GSS_S_COMPLETE) {
371 		rpc_gss_log_status("gss_canonicalize_name", mech_oid, maj_stat,
372 		    min_stat);
373 		gss_release_name(&min_stat, &gss_name);
374 		return (FALSE);
375 	}
376 	gss_release_name(&min_stat, &gss_name);
377 
378 	/*
379 	 * Export the mechanism name and use that to construct the
380 	 * rpc_gss_principal_t result.
381 	 */
382 	maj_stat = gss_export_name(&min_stat, gss_mech_name, &buf);
383 	if (maj_stat != GSS_S_COMPLETE) {
384 		rpc_gss_log_status("gss_export_name", mech_oid, maj_stat, min_stat);
385 		gss_release_name(&min_stat, &gss_mech_name);
386 		return (FALSE);
387 	}
388 	gss_release_name(&min_stat, &gss_mech_name);
389 
390 	result = mem_alloc(sizeof(int) + buf.length);
391 	if (!result) {
392 		gss_release_buffer(&min_stat, &buf);
393 		return (FALSE);
394 	}
395 	result->len = buf.length;
396 	memcpy(result->name, buf.value, buf.length);
397 	gss_release_buffer(&min_stat, &buf);
398 
399 	*principal = result;
400 	return (TRUE);
401 }
402 
403 bool_t
404 rpc_gss_getcred(struct svc_req *req, rpc_gss_rawcred_t **rcred,
405     rpc_gss_ucred_t **ucred, void **cookie)
406 {
407 	struct svc_rpc_gss_cookedcred *cc;
408 	struct svc_rpc_gss_client *client;
409 
410 	if (req->rq_cred.oa_flavor != RPCSEC_GSS)
411 		return (FALSE);
412 
413 	cc = req->rq_clntcred;
414 	client = cc->cc_client;
415 	if (rcred)
416 		*rcred = &client->cl_rawcred;
417 	if (ucred)
418 		*ucred = &client->cl_ucred;
419 	if (cookie)
420 		*cookie = client->cl_cookie;
421 	return (TRUE);
422 }
423 
424 /*
425  * This simpler interface is used by svc_getcred to copy the cred data
426  * into a kernel cred structure.
427  */
428 static int
429 rpc_gss_svc_getcred(struct svc_req *req, struct ucred **crp, int *flavorp)
430 {
431 	struct ucred *cr;
432 	struct svc_rpc_gss_cookedcred *cc;
433 	struct svc_rpc_gss_client *client;
434 	rpc_gss_ucred_t *uc;
435 
436 	if (req->rq_cred.oa_flavor != RPCSEC_GSS)
437 		return (FALSE);
438 
439 	cc = req->rq_clntcred;
440 	client = cc->cc_client;
441 
442 	if (flavorp)
443 		*flavorp = client->cl_rpcflavor;
444 
445 	if (client->cl_cred) {
446 		*crp = crhold(client->cl_cred);
447 		return (TRUE);
448 	}
449 
450 	uc = &client->cl_ucred;
451 	cr = client->cl_cred = crget();
452 	cr->cr_uid = cr->cr_ruid = cr->cr_svuid = uc->uid;
453 	cr->cr_rgid = cr->cr_svgid = uc->gid;
454 	crsetgroups(cr, uc->gidlen, uc->gidlist);
455 	cr->cr_prison = &prison0;
456 	prison_hold(cr->cr_prison);
457 	*crp = crhold(cr);
458 
459 	return (TRUE);
460 }
461 
462 int
463 rpc_gss_svc_max_data_length(struct svc_req *req, int max_tp_unit_len)
464 {
465 	struct svc_rpc_gss_cookedcred *cc = req->rq_clntcred;
466 	struct svc_rpc_gss_client *client = cc->cc_client;
467 	int			want_conf;
468 	OM_uint32		max;
469 	OM_uint32		maj_stat, min_stat;
470 	int			result;
471 
472 	switch (client->cl_rawcred.service) {
473 	case rpc_gss_svc_none:
474 		return (max_tp_unit_len);
475 		break;
476 
477 	case rpc_gss_svc_default:
478 	case rpc_gss_svc_integrity:
479 		want_conf = FALSE;
480 		break;
481 
482 	case rpc_gss_svc_privacy:
483 		want_conf = TRUE;
484 		break;
485 
486 	default:
487 		return (0);
488 	}
489 
490 	maj_stat = gss_wrap_size_limit(&min_stat, client->cl_ctx, want_conf,
491 	    client->cl_qop, max_tp_unit_len, &max);
492 
493 	if (maj_stat == GSS_S_COMPLETE) {
494 		result = (int) max;
495 		if (result < 0)
496 			result = 0;
497 		return (result);
498 	} else {
499 		rpc_gss_log_status("gss_wrap_size_limit", client->cl_mech,
500 		    maj_stat, min_stat);
501 		return (0);
502 	}
503 }
504 
505 static struct svc_rpc_gss_client *
506 svc_rpc_gss_find_client(struct svc_rpc_gss_clientid *id)
507 {
508 	struct svc_rpc_gss_client *client;
509 	struct svc_rpc_gss_client_list *list;
510 	unsigned long hostid;
511 
512 	rpc_gss_log_debug("in svc_rpc_gss_find_client(%d)", id->ci_id);
513 
514 	getcredhostid(curthread->td_ucred, &hostid);
515 	if (id->ci_hostid != hostid || id->ci_boottime != boottime.tv_sec)
516 		return (NULL);
517 
518 	list = &svc_rpc_gss_client_hash[id->ci_id % CLIENT_HASH_SIZE];
519 	sx_xlock(&svc_rpc_gss_lock);
520 	TAILQ_FOREACH(client, list, cl_link) {
521 		if (client->cl_id.ci_id == id->ci_id) {
522 			/*
523 			 * Move this client to the front of the LRU
524 			 * list.
525 			 */
526 			TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
527 			TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client,
528 			    cl_alllink);
529 			refcount_acquire(&client->cl_refs);
530 			break;
531 		}
532 	}
533 	sx_xunlock(&svc_rpc_gss_lock);
534 
535 	return (client);
536 }
537 
538 static struct svc_rpc_gss_client *
539 svc_rpc_gss_create_client(void)
540 {
541 	struct svc_rpc_gss_client *client;
542 	struct svc_rpc_gss_client_list *list;
543 	unsigned long hostid;
544 
545 	rpc_gss_log_debug("in svc_rpc_gss_create_client()");
546 
547 	client = mem_alloc(sizeof(struct svc_rpc_gss_client));
548 	memset(client, 0, sizeof(struct svc_rpc_gss_client));
549 	refcount_init(&client->cl_refs, 1);
550 	sx_init(&client->cl_lock, "GSS-client");
551 	getcredhostid(curthread->td_ucred, &hostid);
552 	client->cl_id.ci_hostid = hostid;
553 	client->cl_id.ci_boottime = boottime.tv_sec;
554 	client->cl_id.ci_id = svc_rpc_gss_next_clientid++;
555 	list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
556 	sx_xlock(&svc_rpc_gss_lock);
557 	TAILQ_INSERT_HEAD(list, client, cl_link);
558 	TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client, cl_alllink);
559 	svc_rpc_gss_client_count++;
560 	sx_xunlock(&svc_rpc_gss_lock);
561 
562 	/*
563 	 * Start the client off with a short expiration time. We will
564 	 * try to get a saner value from the client creds later.
565 	 */
566 	client->cl_state = CLIENT_NEW;
567 	client->cl_locked = FALSE;
568 	client->cl_expiration = time_uptime + 5*60;
569 
570 	return (client);
571 }
572 
573 static void
574 svc_rpc_gss_destroy_client(struct svc_rpc_gss_client *client)
575 {
576 	OM_uint32 min_stat;
577 
578 	rpc_gss_log_debug("in svc_rpc_gss_destroy_client()");
579 
580 	if (client->cl_ctx)
581 		gss_delete_sec_context(&min_stat,
582 		    &client->cl_ctx, GSS_C_NO_BUFFER);
583 
584 	if (client->cl_cname)
585 		gss_release_name(&min_stat, &client->cl_cname);
586 
587 	if (client->cl_rawcred.client_principal)
588 		mem_free(client->cl_rawcred.client_principal,
589 		    sizeof(*client->cl_rawcred.client_principal)
590 		    + client->cl_rawcred.client_principal->len);
591 
592 	if (client->cl_cred)
593 		crfree(client->cl_cred);
594 
595 	sx_destroy(&client->cl_lock);
596 	mem_free(client, sizeof(*client));
597 }
598 
599 /*
600  * Drop a reference to a client and free it if that was the last reference.
601  */
602 static void
603 svc_rpc_gss_release_client(struct svc_rpc_gss_client *client)
604 {
605 
606 	if (!refcount_release(&client->cl_refs))
607 		return;
608 	svc_rpc_gss_destroy_client(client);
609 }
610 
611 /*
612  * Remove a client from our global lists and free it if we can.
613  */
614 static void
615 svc_rpc_gss_forget_client(struct svc_rpc_gss_client *client)
616 {
617 	struct svc_rpc_gss_client_list *list;
618 
619 	list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
620 	sx_xlock(&svc_rpc_gss_lock);
621 	TAILQ_REMOVE(list, client, cl_link);
622 	TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
623 	svc_rpc_gss_client_count--;
624 	sx_xunlock(&svc_rpc_gss_lock);
625 	svc_rpc_gss_release_client(client);
626 }
627 
628 static void
629 svc_rpc_gss_timeout_clients(void)
630 {
631 	struct svc_rpc_gss_client *client;
632 	struct svc_rpc_gss_client *nclient;
633 	time_t now = time_uptime;
634 
635 	rpc_gss_log_debug("in svc_rpc_gss_timeout_clients()");
636 
637 	/*
638 	 * First enforce the max client limit. We keep
639 	 * svc_rpc_gss_clients in LRU order.
640 	 */
641 	while (svc_rpc_gss_client_count > CLIENT_MAX)
642 		svc_rpc_gss_forget_client(TAILQ_LAST(&svc_rpc_gss_clients,
643 			    svc_rpc_gss_client_list));
644 	TAILQ_FOREACH_SAFE(client, &svc_rpc_gss_clients, cl_alllink, nclient) {
645 		if (client->cl_state == CLIENT_STALE
646 		    || now > client->cl_expiration) {
647 			rpc_gss_log_debug("expiring client %p", client);
648 			svc_rpc_gss_forget_client(client);
649 		}
650 	}
651 }
652 
653 #ifdef DEBUG
654 /*
655  * OID<->string routines.  These are uuuuugly.
656  */
657 static OM_uint32
658 gss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
659 {
660 	char		numstr[128];
661 	unsigned long	number;
662 	int		numshift;
663 	size_t		string_length;
664 	size_t		i;
665 	unsigned char	*cp;
666 	char		*bp;
667 
668 	/* Decoded according to krb5/gssapi_krb5.c */
669 
670 	/* First determine the size of the string */
671 	string_length = 0;
672 	number = 0;
673 	numshift = 0;
674 	cp = (unsigned char *) oid->elements;
675 	number = (unsigned long) cp[0];
676 	sprintf(numstr, "%ld ", number/40);
677 	string_length += strlen(numstr);
678 	sprintf(numstr, "%ld ", number%40);
679 	string_length += strlen(numstr);
680 	for (i=1; i<oid->length; i++) {
681 		if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
682 			number = (number << 7) | (cp[i] & 0x7f);
683 			numshift += 7;
684 		}
685 		else {
686 			*minor_status = 0;
687 			return(GSS_S_FAILURE);
688 		}
689 		if ((cp[i] & 0x80) == 0) {
690 			sprintf(numstr, "%ld ", number);
691 			string_length += strlen(numstr);
692 			number = 0;
693 			numshift = 0;
694 		}
695 	}
696 	/*
697 	 * If we get here, we've calculated the length of "n n n ... n ".  Add 4
698 	 * here for "{ " and "}\0".
699 	 */
700 	string_length += 4;
701 	if ((bp = (char *) mem_alloc(string_length))) {
702 		strcpy(bp, "{ ");
703 		number = (unsigned long) cp[0];
704 		sprintf(numstr, "%ld ", number/40);
705 		strcat(bp, numstr);
706 		sprintf(numstr, "%ld ", number%40);
707 		strcat(bp, numstr);
708 		number = 0;
709 		cp = (unsigned char *) oid->elements;
710 		for (i=1; i<oid->length; i++) {
711 			number = (number << 7) | (cp[i] & 0x7f);
712 			if ((cp[i] & 0x80) == 0) {
713 				sprintf(numstr, "%ld ", number);
714 				strcat(bp, numstr);
715 				number = 0;
716 			}
717 		}
718 		strcat(bp, "}");
719 		oid_str->length = strlen(bp)+1;
720 		oid_str->value = (void *) bp;
721 		*minor_status = 0;
722 		return(GSS_S_COMPLETE);
723 	}
724 	*minor_status = 0;
725 	return(GSS_S_FAILURE);
726 }
727 #endif
728 
729 static void
730 svc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
731     const gss_name_t name)
732 {
733 	OM_uint32		maj_stat, min_stat;
734 	rpc_gss_ucred_t		*uc = &client->cl_ucred;
735 	int			numgroups;
736 
737 	uc->uid = 65534;
738 	uc->gid = 65534;
739 	uc->gidlist = client->cl_gid_storage;
740 
741 	numgroups = RPCAUTH_UNIXGIDS;
742 	maj_stat = gss_pname_to_unix_cred(&min_stat, name, client->cl_mech,
743 	    &uc->uid, &uc->gid, &numgroups, &uc->gidlist[0]);
744 	if (GSS_ERROR(maj_stat))
745 		uc->gidlen = 0;
746 	else
747 		uc->gidlen = numgroups;
748 }
749 
750 static void
751 svc_rpc_gss_set_flavor(struct svc_rpc_gss_client *client)
752 {
753 	static gss_OID_desc krb5_mech_oid =
754 		{9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
755 
756 	/*
757 	 * Attempt to translate mech type and service into a
758 	 * 'pseudo flavor'. Hardwire in krb5 support for now.
759 	 */
760 	if (kgss_oid_equal(client->cl_mech, &krb5_mech_oid)) {
761 		switch (client->cl_rawcred.service) {
762 		case rpc_gss_svc_default:
763 		case rpc_gss_svc_none:
764 			client->cl_rpcflavor = RPCSEC_GSS_KRB5;
765 			break;
766 		case rpc_gss_svc_integrity:
767 			client->cl_rpcflavor = RPCSEC_GSS_KRB5I;
768 			break;
769 		case rpc_gss_svc_privacy:
770 			client->cl_rpcflavor = RPCSEC_GSS_KRB5P;
771 			break;
772 		}
773 	} else {
774 		client->cl_rpcflavor = RPCSEC_GSS;
775 	}
776 }
777 
778 static bool_t
779 svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
780 			       struct svc_req *rqst,
781 			       struct rpc_gss_init_res *gr,
782 			       struct rpc_gss_cred *gc)
783 {
784 	gss_buffer_desc		recv_tok;
785 	gss_OID			mech;
786 	OM_uint32		maj_stat = 0, min_stat = 0, ret_flags;
787 	OM_uint32		cred_lifetime;
788 	struct svc_rpc_gss_svc_name *sname;
789 
790 	rpc_gss_log_debug("in svc_rpc_gss_accept_context()");
791 
792 	/* Deserialize arguments. */
793 	memset(&recv_tok, 0, sizeof(recv_tok));
794 
795 	if (!svc_getargs(rqst,
796 		(xdrproc_t) xdr_gss_buffer_desc,
797 		(caddr_t) &recv_tok)) {
798 		client->cl_state = CLIENT_STALE;
799 		return (FALSE);
800 	}
801 
802 	/*
803 	 * First time round, try all the server names we have until
804 	 * one matches. Afterwards, stick with that one.
805 	 */
806 	sx_xlock(&svc_rpc_gss_lock);
807 	if (!client->cl_sname) {
808 		SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
809 			if (sname->sn_program == rqst->rq_prog
810 			    && sname->sn_version == rqst->rq_vers) {
811 			retry:
812 				gr->gr_major = gss_accept_sec_context(
813 					&gr->gr_minor,
814 					&client->cl_ctx,
815 					sname->sn_cred,
816 					&recv_tok,
817 					GSS_C_NO_CHANNEL_BINDINGS,
818 					&client->cl_cname,
819 					&mech,
820 					&gr->gr_token,
821 					&ret_flags,
822 					&cred_lifetime,
823 					&client->cl_creds);
824 				if (gr->gr_major ==
825 				    GSS_S_CREDENTIALS_EXPIRED) {
826 					/*
827 					 * Either our creds really did
828 					 * expire or gssd was
829 					 * restarted.
830 					 */
831 					if (rpc_gss_acquire_svc_cred(sname))
832 						goto retry;
833 				}
834 				client->cl_sname = sname;
835 				break;
836 			}
837 		}
838 		if (!sname) {
839 			xdr_free((xdrproc_t) xdr_gss_buffer_desc,
840 			    (char *) &recv_tok);
841 			sx_xunlock(&svc_rpc_gss_lock);
842 			return (FALSE);
843 		}
844 	} else {
845 		gr->gr_major = gss_accept_sec_context(
846 			&gr->gr_minor,
847 			&client->cl_ctx,
848 			client->cl_sname->sn_cred,
849 			&recv_tok,
850 			GSS_C_NO_CHANNEL_BINDINGS,
851 			&client->cl_cname,
852 			&mech,
853 			&gr->gr_token,
854 			&ret_flags,
855 			&cred_lifetime,
856 			NULL);
857 	}
858 	sx_xunlock(&svc_rpc_gss_lock);
859 
860 	xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
861 
862 	/*
863 	 * If we get an error from gss_accept_sec_context, send the
864 	 * reply anyway so that the client gets a chance to see what
865 	 * is wrong.
866 	 */
867 	if (gr->gr_major != GSS_S_COMPLETE &&
868 	    gr->gr_major != GSS_S_CONTINUE_NEEDED) {
869 		rpc_gss_log_status("accept_sec_context", client->cl_mech,
870 		    gr->gr_major, gr->gr_minor);
871 		client->cl_state = CLIENT_STALE;
872 		return (TRUE);
873 	}
874 
875 	gr->gr_handle.value = &client->cl_id;
876 	gr->gr_handle.length = sizeof(client->cl_id);
877 	gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
878 
879 	/* Save client info. */
880 	client->cl_mech = mech;
881 	client->cl_qop = GSS_C_QOP_DEFAULT;
882 	client->cl_done_callback = FALSE;
883 
884 	if (gr->gr_major == GSS_S_COMPLETE) {
885 		gss_buffer_desc	export_name;
886 
887 		/*
888 		 * Change client expiration time to be near when the
889 		 * client creds expire (or 24 hours if we can't figure
890 		 * that out).
891 		 */
892 		if (cred_lifetime == GSS_C_INDEFINITE)
893 			cred_lifetime = time_uptime + 24*60*60;
894 
895 		client->cl_expiration = time_uptime + cred_lifetime;
896 
897 		/*
898 		 * Fill in cred details in the rawcred structure.
899 		 */
900 		client->cl_rawcred.version = RPCSEC_GSS_VERSION;
901 		rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
902 		maj_stat = gss_export_name(&min_stat, client->cl_cname,
903 		    &export_name);
904 		if (maj_stat != GSS_S_COMPLETE) {
905 			rpc_gss_log_status("gss_export_name", client->cl_mech,
906 			    maj_stat, min_stat);
907 			return (FALSE);
908 		}
909 		client->cl_rawcred.client_principal =
910 			mem_alloc(sizeof(*client->cl_rawcred.client_principal)
911 			    + export_name.length);
912 		client->cl_rawcred.client_principal->len = export_name.length;
913 		memcpy(client->cl_rawcred.client_principal->name,
914 		    export_name.value, export_name.length);
915 		gss_release_buffer(&min_stat, &export_name);
916 		client->cl_rawcred.svc_principal =
917 			client->cl_sname->sn_principal;
918 		client->cl_rawcred.service = gc->gc_svc;
919 
920 		/*
921 		 * Use gss_pname_to_uid to map to unix creds. For
922 		 * kerberos5, this uses krb5_aname_to_localname.
923 		 */
924 		svc_rpc_gss_build_ucred(client, client->cl_cname);
925 		svc_rpc_gss_set_flavor(client);
926 		gss_release_name(&min_stat, &client->cl_cname);
927 
928 #ifdef DEBUG
929 		{
930 			gss_buffer_desc mechname;
931 
932 			gss_oid_to_str(&min_stat, mech, &mechname);
933 
934 			rpc_gss_log_debug("accepted context for %s with "
935 			    "<mech %.*s, qop %d, svc %d>",
936 			    client->cl_rawcred.client_principal->name,
937 			    mechname.length, (char *)mechname.value,
938 			    client->cl_qop, client->cl_rawcred.service);
939 
940 			gss_release_buffer(&min_stat, &mechname);
941 		}
942 #endif /* DEBUG */
943 	}
944 	return (TRUE);
945 }
946 
947 static bool_t
948 svc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
949     gss_qop_t *qop)
950 {
951 	struct opaque_auth	*oa;
952 	gss_buffer_desc		 rpcbuf, checksum;
953 	OM_uint32		 maj_stat, min_stat;
954 	gss_qop_t		 qop_state;
955 	int32_t			 rpchdr[128 / sizeof(int32_t)];
956 	int32_t			*buf;
957 
958 	rpc_gss_log_debug("in svc_rpc_gss_validate()");
959 
960 	memset(rpchdr, 0, sizeof(rpchdr));
961 
962 	/* Reconstruct RPC header for signing (from xdr_callmsg). */
963 	buf = rpchdr;
964 	IXDR_PUT_LONG(buf, msg->rm_xid);
965 	IXDR_PUT_ENUM(buf, msg->rm_direction);
966 	IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
967 	IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
968 	IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
969 	IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
970 	oa = &msg->rm_call.cb_cred;
971 	IXDR_PUT_ENUM(buf, oa->oa_flavor);
972 	IXDR_PUT_LONG(buf, oa->oa_length);
973 	if (oa->oa_length) {
974 		memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
975 		buf += RNDUP(oa->oa_length) / sizeof(int32_t);
976 	}
977 	rpcbuf.value = rpchdr;
978 	rpcbuf.length = (u_char *)buf - (u_char *)rpchdr;
979 
980 	checksum.value = msg->rm_call.cb_verf.oa_base;
981 	checksum.length = msg->rm_call.cb_verf.oa_length;
982 
983 	maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
984 				  &qop_state);
985 
986 	if (maj_stat != GSS_S_COMPLETE) {
987 		rpc_gss_log_status("gss_verify_mic", client->cl_mech,
988 		    maj_stat, min_stat);
989 		client->cl_state = CLIENT_STALE;
990 		return (FALSE);
991 	}
992 
993 	*qop = qop_state;
994 	return (TRUE);
995 }
996 
997 static bool_t
998 svc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
999     struct svc_req *rqst, u_int seq)
1000 {
1001 	gss_buffer_desc		signbuf;
1002 	gss_buffer_desc		mic;
1003 	OM_uint32		maj_stat, min_stat;
1004 	uint32_t		nseq;
1005 
1006 	rpc_gss_log_debug("in svc_rpc_gss_nextverf()");
1007 
1008 	nseq = htonl(seq);
1009 	signbuf.value = &nseq;
1010 	signbuf.length = sizeof(nseq);
1011 
1012 	maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
1013 	    &signbuf, &mic);
1014 
1015 	if (maj_stat != GSS_S_COMPLETE) {
1016 		rpc_gss_log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
1017 		client->cl_state = CLIENT_STALE;
1018 		return (FALSE);
1019 	}
1020 
1021 	KASSERT(mic.length <= MAX_AUTH_BYTES,
1022 	    ("MIC too large for RPCSEC_GSS"));
1023 
1024 	rqst->rq_verf.oa_flavor = RPCSEC_GSS;
1025 	rqst->rq_verf.oa_length = mic.length;
1026 	bcopy(mic.value, rqst->rq_verf.oa_base, mic.length);
1027 
1028 	gss_release_buffer(&min_stat, &mic);
1029 
1030 	return (TRUE);
1031 }
1032 
1033 static bool_t
1034 svc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
1035 {
1036 	struct svc_rpc_gss_callback *scb;
1037 	rpc_gss_lock_t	lock;
1038 	void		*cookie;
1039 	bool_t		cb_res;
1040 	bool_t		result;
1041 
1042 	/*
1043 	 * See if we have a callback for this guy.
1044 	 */
1045 	result = TRUE;
1046 	SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
1047 		if (scb->cb_callback.program == rqst->rq_prog
1048 		    && scb->cb_callback.version == rqst->rq_vers) {
1049 			/*
1050 			 * This one matches. Call the callback and see
1051 			 * if it wants to veto or something.
1052 			 */
1053 			lock.locked = FALSE;
1054 			lock.raw_cred = &client->cl_rawcred;
1055 			cb_res = scb->cb_callback.callback(rqst,
1056 			    client->cl_creds,
1057 			    client->cl_ctx,
1058 			    &lock,
1059 			    &cookie);
1060 
1061 			if (!cb_res) {
1062 				client->cl_state = CLIENT_STALE;
1063 				result = FALSE;
1064 				break;
1065 			}
1066 
1067 			/*
1068 			 * The callback accepted the connection - it
1069 			 * is responsible for freeing client->cl_creds
1070 			 * now.
1071 			 */
1072 			client->cl_creds = GSS_C_NO_CREDENTIAL;
1073 			client->cl_locked = lock.locked;
1074 			client->cl_cookie = cookie;
1075 			return (TRUE);
1076 		}
1077 	}
1078 
1079 	/*
1080 	 * Either no callback exists for this program/version or one
1081 	 * of the callbacks rejected the connection. We just need to
1082 	 * clean up the delegated client creds, if any.
1083 	 */
1084 	if (client->cl_creds) {
1085 		OM_uint32 min_ver;
1086 		gss_release_cred(&min_ver, &client->cl_creds);
1087 	}
1088 	return (result);
1089 }
1090 
1091 static bool_t
1092 svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
1093 {
1094 	u_int32_t offset;
1095 	int word, bit;
1096 	bool_t result;
1097 
1098 	sx_xlock(&client->cl_lock);
1099 	if (seq <= client->cl_seqlast) {
1100 		/*
1101 		 * The request sequence number is less than
1102 		 * the largest we have seen so far. If it is
1103 		 * outside the window or if we have seen a
1104 		 * request with this sequence before, silently
1105 		 * discard it.
1106 		 */
1107 		offset = client->cl_seqlast - seq;
1108 		if (offset >= SVC_RPC_GSS_SEQWINDOW) {
1109 			result = FALSE;
1110 			goto out;
1111 		}
1112 		word = offset / 32;
1113 		bit = offset % 32;
1114 		if (client->cl_seqmask[word] & (1 << bit)) {
1115 			result = FALSE;
1116 			goto out;
1117 		}
1118 	}
1119 
1120 	result = TRUE;
1121 out:
1122 	sx_xunlock(&client->cl_lock);
1123 	return (result);
1124 }
1125 
1126 static void
1127 svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
1128 {
1129 	int offset, i, word, bit;
1130 	uint32_t carry, newcarry;
1131 
1132 	sx_xlock(&client->cl_lock);
1133 	if (seq > client->cl_seqlast) {
1134 		/*
1135 		 * This request has a sequence number greater
1136 		 * than any we have seen so far. Advance the
1137 		 * seq window and set bit zero of the window
1138 		 * (which corresponds to the new sequence
1139 		 * number)
1140 		 */
1141 		offset = seq - client->cl_seqlast;
1142 		while (offset > 32) {
1143 			for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
1144 			     i > 0; i--) {
1145 				client->cl_seqmask[i] = client->cl_seqmask[i-1];
1146 			}
1147 			client->cl_seqmask[0] = 0;
1148 			offset -= 32;
1149 		}
1150 		carry = 0;
1151 		for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
1152 			newcarry = client->cl_seqmask[i] >> (32 - offset);
1153 			client->cl_seqmask[i] =
1154 				(client->cl_seqmask[i] << offset) | carry;
1155 			carry = newcarry;
1156 		}
1157 		client->cl_seqmask[0] |= 1;
1158 		client->cl_seqlast = seq;
1159 	} else {
1160 		offset = client->cl_seqlast - seq;
1161 		word = offset / 32;
1162 		bit = offset % 32;
1163 		client->cl_seqmask[word] |= (1 << bit);
1164 	}
1165 	sx_xunlock(&client->cl_lock);
1166 }
1167 
1168 enum auth_stat
1169 svc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
1170 
1171 {
1172 	OM_uint32		 min_stat;
1173 	XDR	 		 xdrs;
1174 	struct svc_rpc_gss_cookedcred *cc;
1175 	struct svc_rpc_gss_client *client;
1176 	struct rpc_gss_cred	 gc;
1177 	struct rpc_gss_init_res	 gr;
1178 	gss_qop_t		 qop;
1179 	int			 call_stat;
1180 	enum auth_stat		 result;
1181 
1182 	rpc_gss_log_debug("in svc_rpc_gss()");
1183 
1184 	/* Garbage collect old clients. */
1185 	svc_rpc_gss_timeout_clients();
1186 
1187 	/* Initialize reply. */
1188 	rqst->rq_verf = _null_auth;
1189 
1190 	/* Deserialize client credentials. */
1191 	if (rqst->rq_cred.oa_length <= 0)
1192 		return (AUTH_BADCRED);
1193 
1194 	memset(&gc, 0, sizeof(gc));
1195 
1196 	xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
1197 	    rqst->rq_cred.oa_length, XDR_DECODE);
1198 
1199 	if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
1200 		XDR_DESTROY(&xdrs);
1201 		return (AUTH_BADCRED);
1202 	}
1203 	XDR_DESTROY(&xdrs);
1204 
1205 	client = NULL;
1206 
1207 	/* Check version. */
1208 	if (gc.gc_version != RPCSEC_GSS_VERSION) {
1209 		result = AUTH_BADCRED;
1210 		goto out;
1211 	}
1212 
1213 	/* Check the proc and find the client (or create it) */
1214 	if (gc.gc_proc == RPCSEC_GSS_INIT) {
1215 		if (gc.gc_handle.length != 0) {
1216 			result = AUTH_BADCRED;
1217 			goto out;
1218 		}
1219 		client = svc_rpc_gss_create_client();
1220 		refcount_acquire(&client->cl_refs);
1221 	} else {
1222 		struct svc_rpc_gss_clientid *p;
1223 		if (gc.gc_handle.length != sizeof(*p)) {
1224 			result = AUTH_BADCRED;
1225 			goto out;
1226 		}
1227 		p = gc.gc_handle.value;
1228 		client = svc_rpc_gss_find_client(p);
1229 		if (!client) {
1230 			/*
1231 			 * Can't find the client - we may have
1232 			 * destroyed it - tell the other side to
1233 			 * re-authenticate.
1234 			 */
1235 			result = RPCSEC_GSS_CREDPROBLEM;
1236 			goto out;
1237 		}
1238 	}
1239 	cc = rqst->rq_clntcred;
1240 	cc->cc_client = client;
1241 	cc->cc_service = gc.gc_svc;
1242 	cc->cc_seq = gc.gc_seq;
1243 
1244 	/*
1245 	 * The service and sequence number must be ignored for
1246 	 * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
1247 	 */
1248 	if (gc.gc_proc != RPCSEC_GSS_INIT
1249 	    && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
1250 		/*
1251 		 * Check for sequence number overflow.
1252 		 */
1253 		if (gc.gc_seq >= MAXSEQ) {
1254 			result = RPCSEC_GSS_CTXPROBLEM;
1255 			goto out;
1256 		}
1257 
1258 		/*
1259 		 * Check for valid service.
1260 		 */
1261 		if (gc.gc_svc != rpc_gss_svc_none &&
1262 		    gc.gc_svc != rpc_gss_svc_integrity &&
1263 		    gc.gc_svc != rpc_gss_svc_privacy) {
1264 			result = AUTH_BADCRED;
1265 			goto out;
1266 		}
1267 	}
1268 
1269 	/* Handle RPCSEC_GSS control procedure. */
1270 	switch (gc.gc_proc) {
1271 
1272 	case RPCSEC_GSS_INIT:
1273 	case RPCSEC_GSS_CONTINUE_INIT:
1274 		if (rqst->rq_proc != NULLPROC) {
1275 			result = AUTH_REJECTEDCRED;
1276 			break;
1277 		}
1278 
1279 		memset(&gr, 0, sizeof(gr));
1280 		if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
1281 			result = AUTH_REJECTEDCRED;
1282 			break;
1283 		}
1284 
1285 		if (gr.gr_major == GSS_S_COMPLETE) {
1286 			/*
1287 			 * We borrow the space for the call verf to
1288 			 * pack our reply verf.
1289 			 */
1290 			rqst->rq_verf = msg->rm_call.cb_verf;
1291 			if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
1292 				result = AUTH_REJECTEDCRED;
1293 				break;
1294 			}
1295 		} else {
1296 			rqst->rq_verf = _null_auth;
1297 		}
1298 
1299 		call_stat = svc_sendreply(rqst,
1300 		    (xdrproc_t) xdr_rpc_gss_init_res,
1301 		    (caddr_t) &gr);
1302 
1303 		gss_release_buffer(&min_stat, &gr.gr_token);
1304 
1305 		if (!call_stat) {
1306 			result = AUTH_FAILED;
1307 			break;
1308 		}
1309 
1310 		if (gr.gr_major == GSS_S_COMPLETE)
1311 			client->cl_state = CLIENT_ESTABLISHED;
1312 
1313 		result = RPCSEC_GSS_NODISPATCH;
1314 		break;
1315 
1316 	case RPCSEC_GSS_DATA:
1317 	case RPCSEC_GSS_DESTROY:
1318 		if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
1319 			result = RPCSEC_GSS_NODISPATCH;
1320 			break;
1321 		}
1322 
1323 		if (!svc_rpc_gss_validate(client, msg, &qop)) {
1324 			result = RPCSEC_GSS_CREDPROBLEM;
1325 			break;
1326 		}
1327 
1328 		/*
1329 		 * We borrow the space for the call verf to pack our
1330 		 * reply verf.
1331 		 */
1332 		rqst->rq_verf = msg->rm_call.cb_verf;
1333 		if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
1334 			result = RPCSEC_GSS_CTXPROBLEM;
1335 			break;
1336 		}
1337 
1338 		svc_rpc_gss_update_seq(client, gc.gc_seq);
1339 
1340 		/*
1341 		 * Change the SVCAUTH ops on the request to point at
1342 		 * our own code so that we can unwrap the arguments
1343 		 * and wrap the result. The caller will re-set this on
1344 		 * every request to point to a set of null wrap/unwrap
1345 		 * methods. Acquire an extra reference to the client
1346 		 * which will be released by svc_rpc_gss_release()
1347 		 * after the request has finished processing.
1348 		 */
1349 		refcount_acquire(&client->cl_refs);
1350 		rqst->rq_auth.svc_ah_ops = &svc_auth_gss_ops;
1351 		rqst->rq_auth.svc_ah_private = cc;
1352 
1353 		if (gc.gc_proc == RPCSEC_GSS_DATA) {
1354 			/*
1355 			 * We might be ready to do a callback to the server to
1356 			 * see if it wants to accept/reject the connection.
1357 			 */
1358 			sx_xlock(&client->cl_lock);
1359 			if (!client->cl_done_callback) {
1360 				client->cl_done_callback = TRUE;
1361 				client->cl_qop = qop;
1362 				client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1363 					client->cl_rawcred.mechanism, qop);
1364 				if (!svc_rpc_gss_callback(client, rqst)) {
1365 					result = AUTH_REJECTEDCRED;
1366 					sx_xunlock(&client->cl_lock);
1367 					break;
1368 				}
1369 			}
1370 			sx_xunlock(&client->cl_lock);
1371 
1372 			/*
1373 			 * If the server has locked this client to a
1374 			 * particular service+qop pair, enforce that
1375 			 * restriction now.
1376 			 */
1377 			if (client->cl_locked) {
1378 				if (client->cl_rawcred.service != gc.gc_svc) {
1379 					result = AUTH_FAILED;
1380 					break;
1381 				} else if (client->cl_qop != qop) {
1382 					result = AUTH_BADVERF;
1383 					break;
1384 				}
1385 			}
1386 
1387 			/*
1388 			 * If the qop changed, look up the new qop
1389 			 * name for rawcred.
1390 			 */
1391 			if (client->cl_qop != qop) {
1392 				client->cl_qop = qop;
1393 				client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1394 					client->cl_rawcred.mechanism, qop);
1395 			}
1396 
1397 			/*
1398 			 * Make sure we use the right service value
1399 			 * for unwrap/wrap.
1400 			 */
1401 			if (client->cl_rawcred.service != gc.gc_svc) {
1402 				client->cl_rawcred.service = gc.gc_svc;
1403 				svc_rpc_gss_set_flavor(client);
1404 			}
1405 
1406 			result = AUTH_OK;
1407 		} else {
1408 			if (rqst->rq_proc != NULLPROC) {
1409 				result = AUTH_REJECTEDCRED;
1410 				break;
1411 			}
1412 
1413 			call_stat = svc_sendreply(rqst,
1414 			    (xdrproc_t) xdr_void, (caddr_t) NULL);
1415 
1416 			if (!call_stat) {
1417 				result = AUTH_FAILED;
1418 				break;
1419 			}
1420 
1421 			svc_rpc_gss_forget_client(client);
1422 
1423 			result = RPCSEC_GSS_NODISPATCH;
1424 			break;
1425 		}
1426 		break;
1427 
1428 	default:
1429 		result = AUTH_BADCRED;
1430 		break;
1431 	}
1432 out:
1433 	if (client)
1434 		svc_rpc_gss_release_client(client);
1435 
1436 	xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
1437 	return (result);
1438 }
1439 
1440 static bool_t
1441 svc_rpc_gss_wrap(SVCAUTH *auth, struct mbuf **mp)
1442 {
1443 	struct svc_rpc_gss_cookedcred *cc;
1444 	struct svc_rpc_gss_client *client;
1445 
1446 	rpc_gss_log_debug("in svc_rpc_gss_wrap()");
1447 
1448 	cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1449 	client = cc->cc_client;
1450 	if (client->cl_state != CLIENT_ESTABLISHED
1451 	    || cc->cc_service == rpc_gss_svc_none || *mp == NULL) {
1452 		return (TRUE);
1453 	}
1454 
1455 	return (xdr_rpc_gss_wrap_data(mp,
1456 		client->cl_ctx, client->cl_qop,
1457 		cc->cc_service, cc->cc_seq));
1458 }
1459 
1460 static bool_t
1461 svc_rpc_gss_unwrap(SVCAUTH *auth, struct mbuf **mp)
1462 {
1463 	struct svc_rpc_gss_cookedcred *cc;
1464 	struct svc_rpc_gss_client *client;
1465 
1466 	rpc_gss_log_debug("in svc_rpc_gss_unwrap()");
1467 
1468 	cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1469 	client = cc->cc_client;
1470 	if (client->cl_state != CLIENT_ESTABLISHED
1471 	    || cc->cc_service == rpc_gss_svc_none) {
1472 		return (TRUE);
1473 	}
1474 
1475 	return (xdr_rpc_gss_unwrap_data(mp,
1476 		client->cl_ctx, client->cl_qop,
1477 		cc->cc_service, cc->cc_seq));
1478 }
1479 
1480 static void
1481 svc_rpc_gss_release(SVCAUTH *auth)
1482 {
1483 	struct svc_rpc_gss_cookedcred *cc;
1484 	struct svc_rpc_gss_client *client;
1485 
1486 	rpc_gss_log_debug("in svc_rpc_gss_release()");
1487 
1488 	cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1489 	client = cc->cc_client;
1490 	svc_rpc_gss_release_client(client);
1491 }
1492