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