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