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