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