svc_rpcsec_gss.c revision 194239
1178825Sdfr/*-
2233294Sstas * Copyright (c) 2008 Doug Rabson
3178825Sdfr * All rights reserved.
4178825Sdfr *
5233294Sstas * Redistribution and use in source and binary forms, with or without
6178825Sdfr * modification, are permitted provided that the following conditions
7178825Sdfr * are met:
8178825Sdfr * 1. Redistributions of source code must retain the above copyright
9233294Sstas *    notice, this list of conditions and the following disclaimer.
10178825Sdfr * 2. Redistributions in binary form must reproduce the above copyright
11178825Sdfr *    notice, this list of conditions and the following disclaimer in the
12233294Sstas *    documentation and/or other materials provided with the distribution.
13178825Sdfr *
14178825Sdfr * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15178825Sdfr * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16233294Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17178825Sdfr * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18178825Sdfr * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19178825Sdfr * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20233294Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21178825Sdfr * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22178825Sdfr * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23178825Sdfr * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24178825Sdfr * SUCH DAMAGE.
25178825Sdfr */
26178825Sdfr/*
27178825Sdfr  svc_rpcsec_gss.c
28178825Sdfr
29178825Sdfr  Copyright (c) 2000 The Regents of the University of Michigan.
30178825Sdfr  All rights reserved.
31178825Sdfr
32178825Sdfr  Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
33178825Sdfr  All rights reserved, all wrongs reversed.
34178825Sdfr
35178825Sdfr  Redistribution and use in source and binary forms, with or without
36178825Sdfr  modification, are permitted provided that the following conditions
37178825Sdfr  are met:
38178825Sdfr
39178825Sdfr  1. Redistributions of source code must retain the above copyright
40178825Sdfr     notice, this list of conditions and the following disclaimer.
41178825Sdfr  2. Redistributions in binary form must reproduce the above copyright
42233294Sstas     notice, this list of conditions and the following disclaimer in the
43178825Sdfr     documentation and/or other materials provided with the distribution.
44178825Sdfr  3. Neither the name of the University nor the names of its
45178825Sdfr     contributors may be used to endorse or promote products derived
46178825Sdfr     from this software without specific prior written permission.
47178825Sdfr
48178825Sdfr  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
49178825Sdfr  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
50178825Sdfr  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
51178825Sdfr  DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
52178825Sdfr  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
53233294Sstas  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
54233294Sstas  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
55233294Sstas  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
56233294Sstas  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
57233294Sstas  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
58233294Sstas  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
59233294Sstas
60233294Sstas  $Id: svc_auth_gss.c,v 1.27 2002/01/15 15:43:00 andros Exp $
61233294Sstas */
62233294Sstas
63233294Sstas#include <sys/cdefs.h>
64233294Sstas__FBSDID("$FreeBSD: head/sys/rpc/rpcsec_gss/svc_rpcsec_gss.c 194239 2009-06-15 14:44:55Z rmacklem $");
65233294Sstas
66233294Sstas#include <sys/param.h>
67233294Sstas#include <sys/systm.h>
68233294Sstas#include <sys/jail.h>
69233294Sstas#include <sys/kernel.h>
70233294Sstas#include <sys/kobj.h>
71233294Sstas#include <sys/lock.h>
72233294Sstas#include <sys/malloc.h>
73233294Sstas#include <sys/mbuf.h>
74233294Sstas#include <sys/mutex.h>
75233294Sstas#include <sys/proc.h>
76233294Sstas#include <sys/sx.h>
77233294Sstas#include <sys/ucred.h>
78233294Sstas
79233294Sstas#include <rpc/rpc.h>
80233294Sstas#include <rpc/rpcsec_gss.h>
81233294Sstas
82233294Sstas#include "rpcsec_gss_int.h"
83233294Sstas
84233294Sstasstatic bool_t   svc_rpc_gss_wrap(SVCAUTH *, struct mbuf **);
85233294Sstasstatic bool_t   svc_rpc_gss_unwrap(SVCAUTH *, struct mbuf **);
86233294Sstasstatic void     svc_rpc_gss_release(SVCAUTH *);
87233294Sstasstatic enum auth_stat svc_rpc_gss(struct svc_req *, struct rpc_msg *);
88233294Sstasstatic int rpc_gss_svc_getcred(struct svc_req *, struct ucred **, int *);
89233294Sstas
90233294Sstasstatic struct svc_auth_ops svc_auth_gss_ops = {
91233294Sstas	svc_rpc_gss_wrap,
92233294Sstas	svc_rpc_gss_unwrap,
93233294Sstas	svc_rpc_gss_release,
94233294Sstas};
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
171struct svc_rpc_gss_client_list svc_rpc_gss_client_hash[CLIENT_HASH_SIZE];
172struct svc_rpc_gss_client_list svc_rpc_gss_clients;
173static size_t svc_rpc_gss_client_count;
174static uint32_t svc_rpc_gss_next_clientid = 1;
175
176static void
177svc_rpc_gss_init(void *arg)
178{
179	int i;
180
181	for (i = 0; i < CLIENT_HASH_SIZE; i++)
182		TAILQ_INIT(&svc_rpc_gss_client_hash[i]);
183	TAILQ_INIT(&svc_rpc_gss_clients);
184	svc_auth_reg(RPCSEC_GSS, svc_rpc_gss, rpc_gss_svc_getcred);
185	sx_init(&svc_rpc_gss_lock, "gsslock");
186}
187SYSINIT(svc_rpc_gss_init, SI_SUB_KMEM, SI_ORDER_ANY, svc_rpc_gss_init, NULL);
188
189bool_t
190rpc_gss_set_callback(rpc_gss_callback_t *cb)
191{
192	struct svc_rpc_gss_callback *scb;
193
194	scb = mem_alloc(sizeof(struct svc_rpc_gss_callback));
195	if (!scb) {
196		_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
197		return (FALSE);
198	}
199	scb->cb_callback = *cb;
200	sx_xlock(&svc_rpc_gss_lock);
201	SLIST_INSERT_HEAD(&svc_rpc_gss_callbacks, scb, cb_link);
202	sx_xunlock(&svc_rpc_gss_lock);
203
204	return (TRUE);
205}
206
207void
208rpc_gss_clear_callback(rpc_gss_callback_t *cb)
209{
210	struct svc_rpc_gss_callback *scb;
211
212	sx_xlock(&svc_rpc_gss_lock);
213	SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
214		if (scb->cb_callback.program == cb->program
215		    && scb->cb_callback.version == cb->version
216		    && scb->cb_callback.callback == cb->callback) {
217			SLIST_REMOVE(&svc_rpc_gss_callbacks, scb,
218			    svc_rpc_gss_callback, cb_link);
219			sx_xunlock(&svc_rpc_gss_lock);
220			mem_free(scb, sizeof(*scb));
221			return;
222		}
223	}
224	sx_xunlock(&svc_rpc_gss_lock);
225}
226
227static bool_t
228rpc_gss_acquire_svc_cred(struct svc_rpc_gss_svc_name *sname)
229{
230	OM_uint32		maj_stat, min_stat;
231	gss_buffer_desc		namebuf;
232	gss_name_t		name;
233	gss_OID_set_desc	oid_set;
234
235	oid_set.count = 1;
236	oid_set.elements = sname->sn_mech;
237
238	namebuf.value = (void *) sname->sn_principal;
239	namebuf.length = strlen(sname->sn_principal);
240
241	maj_stat = gss_import_name(&min_stat, &namebuf,
242				   GSS_C_NT_HOSTBASED_SERVICE, &name);
243	if (maj_stat != GSS_S_COMPLETE)
244		return (FALSE);
245
246	if (sname->sn_cred != GSS_C_NO_CREDENTIAL)
247		gss_release_cred(&min_stat, &sname->sn_cred);
248
249	maj_stat = gss_acquire_cred(&min_stat, name,
250	    sname->sn_req_time, &oid_set, GSS_C_ACCEPT, &sname->sn_cred,
251	    NULL, NULL);
252	if (maj_stat != GSS_S_COMPLETE) {
253		gss_release_name(&min_stat, &name);
254		return (FALSE);
255	}
256	gss_release_name(&min_stat, &name);
257
258	return (TRUE);
259}
260
261bool_t
262rpc_gss_set_svc_name(const char *principal, const char *mechanism,
263    u_int req_time, u_int program, u_int version)
264{
265	struct svc_rpc_gss_svc_name *sname;
266	gss_OID			mech_oid;
267
268	if (!rpc_gss_mech_to_oid(mechanism, &mech_oid))
269		return (FALSE);
270
271	sname = mem_alloc(sizeof(*sname));
272	if (!sname)
273		return (FALSE);
274	sname->sn_principal = strdup(principal, M_RPC);
275	sname->sn_mech = mech_oid;
276	sname->sn_req_time = req_time;
277	sname->sn_cred = GSS_C_NO_CREDENTIAL;
278	sname->sn_program = program;
279	sname->sn_version = version;
280
281	if (!rpc_gss_acquire_svc_cred(sname)) {
282		free(sname->sn_principal, M_RPC);
283		mem_free(sname, sizeof(*sname));
284		return (FALSE);
285	}
286
287	sx_xlock(&svc_rpc_gss_lock);
288	SLIST_INSERT_HEAD(&svc_rpc_gss_svc_names, sname, sn_link);
289	sx_xunlock(&svc_rpc_gss_lock);
290
291	return (TRUE);
292}
293
294void
295rpc_gss_clear_svc_name(u_int program, u_int version)
296{
297	OM_uint32		min_stat;
298	struct svc_rpc_gss_svc_name *sname;
299
300	sx_xlock(&svc_rpc_gss_lock);
301	SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
302		if (sname->sn_program == program
303		    && sname->sn_version == version) {
304			SLIST_REMOVE(&svc_rpc_gss_svc_names, sname,
305			    svc_rpc_gss_svc_name, sn_link);
306			sx_xunlock(&svc_rpc_gss_lock);
307			gss_release_cred(&min_stat, &sname->sn_cred);
308			free(sname->sn_principal, M_RPC);
309			mem_free(sname, sizeof(*sname));
310			return;
311		}
312	}
313	sx_xunlock(&svc_rpc_gss_lock);
314}
315
316bool_t
317rpc_gss_get_principal_name(rpc_gss_principal_t *principal,
318    const char *mech, const char *name, const char *node, const char *domain)
319{
320	OM_uint32		maj_stat, min_stat;
321	gss_OID			mech_oid;
322	size_t			namelen;
323	gss_buffer_desc		buf;
324	gss_name_t		gss_name, gss_mech_name;
325	rpc_gss_principal_t	result;
326
327	if (!rpc_gss_mech_to_oid(mech, &mech_oid))
328		return (FALSE);
329
330	/*
331	 * Construct a gss_buffer containing the full name formatted
332	 * as "name/node@domain" where node and domain are optional.
333	 */
334	namelen = strlen(name);
335	if (node) {
336		namelen += strlen(node) + 1;
337	}
338	if (domain) {
339		namelen += strlen(domain) + 1;
340	}
341
342	buf.value = mem_alloc(namelen);
343	buf.length = namelen;
344	strcpy((char *) buf.value, name);
345	if (node) {
346		strcat((char *) buf.value, "/");
347		strcat((char *) buf.value, node);
348	}
349	if (domain) {
350		strcat((char *) buf.value, "@");
351		strcat((char *) buf.value, domain);
352	}
353
354	/*
355	 * Convert that to a gss_name_t and then convert that to a
356	 * mechanism name in the selected mechanism.
357	 */
358	maj_stat = gss_import_name(&min_stat, &buf,
359	    GSS_C_NT_USER_NAME, &gss_name);
360	mem_free(buf.value, buf.length);
361	if (maj_stat != GSS_S_COMPLETE) {
362		rpc_gss_log_status("gss_import_name", mech_oid, maj_stat, min_stat);
363		return (FALSE);
364	}
365	maj_stat = gss_canonicalize_name(&min_stat, gss_name, mech_oid,
366	    &gss_mech_name);
367	if (maj_stat != GSS_S_COMPLETE) {
368		rpc_gss_log_status("gss_canonicalize_name", mech_oid, maj_stat,
369		    min_stat);
370		gss_release_name(&min_stat, &gss_name);
371		return (FALSE);
372	}
373	gss_release_name(&min_stat, &gss_name);
374
375	/*
376	 * Export the mechanism name and use that to construct the
377	 * rpc_gss_principal_t result.
378	 */
379	maj_stat = gss_export_name(&min_stat, gss_mech_name, &buf);
380	if (maj_stat != GSS_S_COMPLETE) {
381		rpc_gss_log_status("gss_export_name", mech_oid, maj_stat, min_stat);
382		gss_release_name(&min_stat, &gss_mech_name);
383		return (FALSE);
384	}
385	gss_release_name(&min_stat, &gss_mech_name);
386
387	result = mem_alloc(sizeof(int) + buf.length);
388	if (!result) {
389		gss_release_buffer(&min_stat, &buf);
390		return (FALSE);
391	}
392	result->len = buf.length;
393	memcpy(result->name, buf.value, buf.length);
394	gss_release_buffer(&min_stat, &buf);
395
396	*principal = result;
397	return (TRUE);
398}
399
400bool_t
401rpc_gss_getcred(struct svc_req *req, rpc_gss_rawcred_t **rcred,
402    rpc_gss_ucred_t **ucred, void **cookie)
403{
404	struct svc_rpc_gss_cookedcred *cc;
405	struct svc_rpc_gss_client *client;
406
407	if (req->rq_cred.oa_flavor != RPCSEC_GSS)
408		return (FALSE);
409
410	cc = req->rq_clntcred;
411	client = cc->cc_client;
412	if (rcred)
413		*rcred = &client->cl_rawcred;
414	if (ucred)
415		*ucred = &client->cl_ucred;
416	if (cookie)
417		*cookie = client->cl_cookie;
418	return (TRUE);
419}
420
421/*
422 * This simpler interface is used by svc_getcred to copy the cred data
423 * into a kernel cred structure.
424 */
425static int
426rpc_gss_svc_getcred(struct svc_req *req, struct ucred **crp, int *flavorp)
427{
428	struct ucred *cr;
429	struct svc_rpc_gss_cookedcred *cc;
430	struct svc_rpc_gss_client *client;
431	rpc_gss_ucred_t *uc;
432	int i;
433
434	if (req->rq_cred.oa_flavor != RPCSEC_GSS)
435		return (FALSE);
436
437	cc = req->rq_clntcred;
438	client = cc->cc_client;
439
440	if (flavorp)
441		*flavorp = client->cl_rpcflavor;
442
443	if (client->cl_cred) {
444		*crp = crhold(client->cl_cred);
445		return (TRUE);
446	}
447
448	uc = &client->cl_ucred;
449	cr = client->cl_cred = crget();
450	cr->cr_uid = cr->cr_ruid = cr->cr_svuid = uc->uid;
451	cr->cr_rgid = cr->cr_svgid = uc->gid;
452	cr->cr_ngroups = uc->gidlen;
453	if (cr->cr_ngroups > NGROUPS)
454		cr->cr_ngroups = NGROUPS;
455	for (i = 0; i < cr->cr_ngroups; i++)
456		cr->cr_groups[i] = uc->gidlist[i];
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 and free it if we can.
613 */
614static void
615svc_rpc_gss_forget_client(struct svc_rpc_gss_client *client)
616{
617	struct svc_rpc_gss_client_list *list;
618
619	list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
620	sx_xlock(&svc_rpc_gss_lock);
621	TAILQ_REMOVE(list, client, cl_link);
622	TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
623	svc_rpc_gss_client_count--;
624	sx_xunlock(&svc_rpc_gss_lock);
625	svc_rpc_gss_release_client(client);
626}
627
628static void
629svc_rpc_gss_timeout_clients(void)
630{
631	struct svc_rpc_gss_client *client;
632	struct svc_rpc_gss_client *nclient;
633	time_t now = time_uptime;
634
635	rpc_gss_log_debug("in svc_rpc_gss_timeout_clients()");
636
637	/*
638	 * First enforce the max client limit. We keep
639	 * svc_rpc_gss_clients in LRU order.
640	 */
641	while (svc_rpc_gss_client_count > CLIENT_MAX)
642		svc_rpc_gss_forget_client(TAILQ_LAST(&svc_rpc_gss_clients,
643			    svc_rpc_gss_client_list));
644	TAILQ_FOREACH_SAFE(client, &svc_rpc_gss_clients, cl_alllink, nclient) {
645		if (client->cl_state == CLIENT_STALE
646		    || now > client->cl_expiration) {
647			rpc_gss_log_debug("expiring client %p", client);
648			svc_rpc_gss_forget_client(client);
649		}
650	}
651}
652
653#ifdef DEBUG
654/*
655 * OID<->string routines.  These are uuuuugly.
656 */
657static OM_uint32
658gss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
659{
660	char		numstr[128];
661	unsigned long	number;
662	int		numshift;
663	size_t		string_length;
664	size_t		i;
665	unsigned char	*cp;
666	char		*bp;
667
668	/* Decoded according to krb5/gssapi_krb5.c */
669
670	/* First determine the size of the string */
671	string_length = 0;
672	number = 0;
673	numshift = 0;
674	cp = (unsigned char *) oid->elements;
675	number = (unsigned long) cp[0];
676	sprintf(numstr, "%ld ", number/40);
677	string_length += strlen(numstr);
678	sprintf(numstr, "%ld ", number%40);
679	string_length += strlen(numstr);
680	for (i=1; i<oid->length; i++) {
681		if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
682			number = (number << 7) | (cp[i] & 0x7f);
683			numshift += 7;
684		}
685		else {
686			*minor_status = 0;
687			return(GSS_S_FAILURE);
688		}
689		if ((cp[i] & 0x80) == 0) {
690			sprintf(numstr, "%ld ", number);
691			string_length += strlen(numstr);
692			number = 0;
693			numshift = 0;
694		}
695	}
696	/*
697	 * If we get here, we've calculated the length of "n n n ... n ".  Add 4
698	 * here for "{ " and "}\0".
699	 */
700	string_length += 4;
701	if ((bp = (char *) mem_alloc(string_length))) {
702		strcpy(bp, "{ ");
703		number = (unsigned long) cp[0];
704		sprintf(numstr, "%ld ", number/40);
705		strcat(bp, numstr);
706		sprintf(numstr, "%ld ", number%40);
707		strcat(bp, numstr);
708		number = 0;
709		cp = (unsigned char *) oid->elements;
710		for (i=1; i<oid->length; i++) {
711			number = (number << 7) | (cp[i] & 0x7f);
712			if ((cp[i] & 0x80) == 0) {
713				sprintf(numstr, "%ld ", number);
714				strcat(bp, numstr);
715				number = 0;
716			}
717		}
718		strcat(bp, "}");
719		oid_str->length = strlen(bp)+1;
720		oid_str->value = (void *) bp;
721		*minor_status = 0;
722		return(GSS_S_COMPLETE);
723	}
724	*minor_status = 0;
725	return(GSS_S_FAILURE);
726}
727#endif
728
729static void
730svc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
731    const gss_name_t name)
732{
733	OM_uint32		maj_stat, min_stat;
734	rpc_gss_ucred_t		*uc = &client->cl_ucred;
735	int			numgroups;
736
737	uc->uid = 65534;
738	uc->gid = 65534;
739	uc->gidlist = client->cl_gid_storage;
740
741	numgroups = NGROUPS;
742	maj_stat = gss_pname_to_unix_cred(&min_stat, name, client->cl_mech,
743	    &uc->uid, &uc->gid, &numgroups, &uc->gidlist[0]);
744	if (GSS_ERROR(maj_stat))
745		uc->gidlen = 0;
746	else
747		uc->gidlen = numgroups;
748}
749
750static void
751svc_rpc_gss_set_flavor(struct svc_rpc_gss_client *client)
752{
753	static gss_OID_desc krb5_mech_oid =
754		{9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
755
756	/*
757	 * Attempt to translate mech type and service into a
758	 * 'pseudo flavor'. Hardwire in krb5 support for now.
759	 */
760	if (kgss_oid_equal(client->cl_mech, &krb5_mech_oid)) {
761		switch (client->cl_rawcred.service) {
762		case rpc_gss_svc_default:
763		case rpc_gss_svc_none:
764			client->cl_rpcflavor = RPCSEC_GSS_KRB5;
765			break;
766		case rpc_gss_svc_integrity:
767			client->cl_rpcflavor = RPCSEC_GSS_KRB5I;
768			break;
769		case rpc_gss_svc_privacy:
770			client->cl_rpcflavor = RPCSEC_GSS_KRB5P;
771			break;
772		}
773	} else {
774		client->cl_rpcflavor = RPCSEC_GSS;
775	}
776}
777
778static bool_t
779svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
780			       struct svc_req *rqst,
781			       struct rpc_gss_init_res *gr,
782			       struct rpc_gss_cred *gc)
783{
784	gss_buffer_desc		recv_tok;
785	gss_OID			mech;
786	OM_uint32		maj_stat = 0, min_stat = 0, ret_flags;
787	OM_uint32		cred_lifetime;
788	struct svc_rpc_gss_svc_name *sname;
789
790	rpc_gss_log_debug("in svc_rpc_gss_accept_context()");
791
792	/* Deserialize arguments. */
793	memset(&recv_tok, 0, sizeof(recv_tok));
794
795	if (!svc_getargs(rqst,
796		(xdrproc_t) xdr_gss_buffer_desc,
797		(caddr_t) &recv_tok)) {
798		client->cl_state = CLIENT_STALE;
799		return (FALSE);
800	}
801
802	/*
803	 * First time round, try all the server names we have until
804	 * one matches. Afterwards, stick with that one.
805	 */
806	sx_xlock(&svc_rpc_gss_lock);
807	if (!client->cl_sname) {
808		SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
809			if (sname->sn_program == rqst->rq_prog
810			    && sname->sn_version == rqst->rq_vers) {
811			retry:
812				gr->gr_major = gss_accept_sec_context(
813					&gr->gr_minor,
814					&client->cl_ctx,
815					sname->sn_cred,
816					&recv_tok,
817					GSS_C_NO_CHANNEL_BINDINGS,
818					&client->cl_cname,
819					&mech,
820					&gr->gr_token,
821					&ret_flags,
822					&cred_lifetime,
823					&client->cl_creds);
824				if (gr->gr_major ==
825				    GSS_S_CREDENTIALS_EXPIRED) {
826					/*
827					 * Either our creds really did
828					 * expire or gssd was
829					 * restarted.
830					 */
831					if (rpc_gss_acquire_svc_cred(sname))
832						goto retry;
833				}
834				client->cl_sname = sname;
835				break;
836			}
837		}
838		if (!sname) {
839			xdr_free((xdrproc_t) xdr_gss_buffer_desc,
840			    (char *) &recv_tok);
841			sx_xunlock(&svc_rpc_gss_lock);
842			return (FALSE);
843		}
844	} else {
845		gr->gr_major = gss_accept_sec_context(
846			&gr->gr_minor,
847			&client->cl_ctx,
848			client->cl_sname->sn_cred,
849			&recv_tok,
850			GSS_C_NO_CHANNEL_BINDINGS,
851			&client->cl_cname,
852			&mech,
853			&gr->gr_token,
854			&ret_flags,
855			&cred_lifetime,
856			NULL);
857	}
858	sx_xunlock(&svc_rpc_gss_lock);
859
860	xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
861
862	/*
863	 * If we get an error from gss_accept_sec_context, send the
864	 * reply anyway so that the client gets a chance to see what
865	 * is wrong.
866	 */
867	if (gr->gr_major != GSS_S_COMPLETE &&
868	    gr->gr_major != GSS_S_CONTINUE_NEEDED) {
869		rpc_gss_log_status("accept_sec_context", client->cl_mech,
870		    gr->gr_major, gr->gr_minor);
871		client->cl_state = CLIENT_STALE;
872		return (TRUE);
873	}
874
875	gr->gr_handle.value = &client->cl_id;
876	gr->gr_handle.length = sizeof(client->cl_id);
877	gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
878
879	/* Save client info. */
880	client->cl_mech = mech;
881	client->cl_qop = GSS_C_QOP_DEFAULT;
882	client->cl_done_callback = FALSE;
883
884	if (gr->gr_major == GSS_S_COMPLETE) {
885		gss_buffer_desc	export_name;
886
887		/*
888		 * Change client expiration time to be near when the
889		 * client creds expire (or 24 hours if we can't figure
890		 * that out).
891		 */
892		if (cred_lifetime == GSS_C_INDEFINITE)
893			cred_lifetime = time_uptime + 24*60*60;
894
895		client->cl_expiration = time_uptime + cred_lifetime;
896
897		/*
898		 * Fill in cred details in the rawcred structure.
899		 */
900		client->cl_rawcred.version = RPCSEC_GSS_VERSION;
901		rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
902		maj_stat = gss_export_name(&min_stat, client->cl_cname,
903		    &export_name);
904		if (maj_stat != GSS_S_COMPLETE) {
905			rpc_gss_log_status("gss_export_name", client->cl_mech,
906			    maj_stat, min_stat);
907			return (FALSE);
908		}
909		client->cl_rawcred.client_principal =
910			mem_alloc(sizeof(*client->cl_rawcred.client_principal)
911			    + export_name.length);
912		client->cl_rawcred.client_principal->len = export_name.length;
913		memcpy(client->cl_rawcred.client_principal->name,
914		    export_name.value, export_name.length);
915		gss_release_buffer(&min_stat, &export_name);
916		client->cl_rawcred.svc_principal =
917			client->cl_sname->sn_principal;
918		client->cl_rawcred.service = gc->gc_svc;
919
920		/*
921		 * Use gss_pname_to_uid to map to unix creds. For
922		 * kerberos5, this uses krb5_aname_to_localname.
923		 */
924		svc_rpc_gss_build_ucred(client, client->cl_cname);
925		svc_rpc_gss_set_flavor(client);
926		gss_release_name(&min_stat, &client->cl_cname);
927
928#ifdef DEBUG
929		{
930			gss_buffer_desc mechname;
931
932			gss_oid_to_str(&min_stat, mech, &mechname);
933
934			rpc_gss_log_debug("accepted context for %s with "
935			    "<mech %.*s, qop %d, svc %d>",
936			    client->cl_rawcred.client_principal->name,
937			    mechname.length, (char *)mechname.value,
938			    client->cl_qop, client->rawcred.service);
939
940			gss_release_buffer(&min_stat, &mechname);
941		}
942#endif /* DEBUG */
943	}
944	return (TRUE);
945}
946
947static bool_t
948svc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
949    gss_qop_t *qop)
950{
951	struct opaque_auth	*oa;
952	gss_buffer_desc		 rpcbuf, checksum;
953	OM_uint32		 maj_stat, min_stat;
954	gss_qop_t		 qop_state;
955	int32_t			 rpchdr[128 / sizeof(int32_t)];
956	int32_t			*buf;
957
958	rpc_gss_log_debug("in svc_rpc_gss_validate()");
959
960	memset(rpchdr, 0, sizeof(rpchdr));
961
962	/* Reconstruct RPC header for signing (from xdr_callmsg). */
963	buf = rpchdr;
964	IXDR_PUT_LONG(buf, msg->rm_xid);
965	IXDR_PUT_ENUM(buf, msg->rm_direction);
966	IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
967	IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
968	IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
969	IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
970	oa = &msg->rm_call.cb_cred;
971	IXDR_PUT_ENUM(buf, oa->oa_flavor);
972	IXDR_PUT_LONG(buf, oa->oa_length);
973	if (oa->oa_length) {
974		memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
975		buf += RNDUP(oa->oa_length) / sizeof(int32_t);
976	}
977	rpcbuf.value = rpchdr;
978	rpcbuf.length = (u_char *)buf - (u_char *)rpchdr;
979
980	checksum.value = msg->rm_call.cb_verf.oa_base;
981	checksum.length = msg->rm_call.cb_verf.oa_length;
982
983	maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
984				  &qop_state);
985
986	if (maj_stat != GSS_S_COMPLETE) {
987		rpc_gss_log_status("gss_verify_mic", client->cl_mech,
988		    maj_stat, min_stat);
989		client->cl_state = CLIENT_STALE;
990		return (FALSE);
991	}
992
993	*qop = qop_state;
994	return (TRUE);
995}
996
997static bool_t
998svc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
999    struct svc_req *rqst, u_int seq)
1000{
1001	gss_buffer_desc		signbuf;
1002	gss_buffer_desc		mic;
1003	OM_uint32		maj_stat, min_stat;
1004	uint32_t		nseq;
1005
1006	rpc_gss_log_debug("in svc_rpc_gss_nextverf()");
1007
1008	nseq = htonl(seq);
1009	signbuf.value = &nseq;
1010	signbuf.length = sizeof(nseq);
1011
1012	maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
1013	    &signbuf, &mic);
1014
1015	if (maj_stat != GSS_S_COMPLETE) {
1016		rpc_gss_log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
1017		client->cl_state = CLIENT_STALE;
1018		return (FALSE);
1019	}
1020
1021	KASSERT(mic.length <= MAX_AUTH_BYTES,
1022	    ("MIC too large for RPCSEC_GSS"));
1023
1024	rqst->rq_verf.oa_flavor = RPCSEC_GSS;
1025	rqst->rq_verf.oa_length = mic.length;
1026	bcopy(mic.value, rqst->rq_verf.oa_base, mic.length);
1027
1028	gss_release_buffer(&min_stat, &mic);
1029
1030	return (TRUE);
1031}
1032
1033static bool_t
1034svc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
1035{
1036	struct svc_rpc_gss_callback *scb;
1037	rpc_gss_lock_t	lock;
1038	void		*cookie;
1039	bool_t		cb_res;
1040	bool_t		result;
1041
1042	/*
1043	 * See if we have a callback for this guy.
1044	 */
1045	result = TRUE;
1046	SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
1047		if (scb->cb_callback.program == rqst->rq_prog
1048		    && scb->cb_callback.version == rqst->rq_vers) {
1049			/*
1050			 * This one matches. Call the callback and see
1051			 * if it wants to veto or something.
1052			 */
1053			lock.locked = FALSE;
1054			lock.raw_cred = &client->cl_rawcred;
1055			cb_res = scb->cb_callback.callback(rqst,
1056			    client->cl_creds,
1057			    client->cl_ctx,
1058			    &lock,
1059			    &cookie);
1060
1061			if (!cb_res) {
1062				client->cl_state = CLIENT_STALE;
1063				result = FALSE;
1064				break;
1065			}
1066
1067			/*
1068			 * The callback accepted the connection - it
1069			 * is responsible for freeing client->cl_creds
1070			 * now.
1071			 */
1072			client->cl_creds = GSS_C_NO_CREDENTIAL;
1073			client->cl_locked = lock.locked;
1074			client->cl_cookie = cookie;
1075			return (TRUE);
1076		}
1077	}
1078
1079	/*
1080	 * Either no callback exists for this program/version or one
1081	 * of the callbacks rejected the connection. We just need to
1082	 * clean up the delegated client creds, if any.
1083	 */
1084	if (client->cl_creds) {
1085		OM_uint32 min_ver;
1086		gss_release_cred(&min_ver, &client->cl_creds);
1087	}
1088	return (result);
1089}
1090
1091static bool_t
1092svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
1093{
1094	u_int32_t offset;
1095	int word, bit;
1096	bool_t result;
1097
1098	sx_xlock(&client->cl_lock);
1099	if (seq <= client->cl_seqlast) {
1100		/*
1101		 * The request sequence number is less than
1102		 * the largest we have seen so far. If it is
1103		 * outside the window or if we have seen a
1104		 * request with this sequence before, silently
1105		 * discard it.
1106		 */
1107		offset = client->cl_seqlast - seq;
1108		if (offset >= SVC_RPC_GSS_SEQWINDOW) {
1109			result = FALSE;
1110			goto out;
1111		}
1112		word = offset / 32;
1113		bit = offset % 32;
1114		if (client->cl_seqmask[word] & (1 << bit)) {
1115			result = FALSE;
1116			goto out;
1117		}
1118	}
1119
1120	result = TRUE;
1121out:
1122	sx_xunlock(&client->cl_lock);
1123	return (result);
1124}
1125
1126static void
1127svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
1128{
1129	int offset, i, word, bit;
1130	uint32_t carry, newcarry;
1131
1132	sx_xlock(&client->cl_lock);
1133	if (seq > client->cl_seqlast) {
1134		/*
1135		 * This request has a sequence number greater
1136		 * than any we have seen so far. Advance the
1137		 * seq window and set bit zero of the window
1138		 * (which corresponds to the new sequence
1139		 * number)
1140		 */
1141		offset = seq - client->cl_seqlast;
1142		while (offset > 32) {
1143			for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
1144			     i > 0; i--) {
1145				client->cl_seqmask[i] = client->cl_seqmask[i-1];
1146			}
1147			client->cl_seqmask[0] = 0;
1148			offset -= 32;
1149		}
1150		carry = 0;
1151		for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
1152			newcarry = client->cl_seqmask[i] >> (32 - offset);
1153			client->cl_seqmask[i] =
1154				(client->cl_seqmask[i] << offset) | carry;
1155			carry = newcarry;
1156		}
1157		client->cl_seqmask[0] |= 1;
1158		client->cl_seqlast = seq;
1159	} else {
1160		offset = client->cl_seqlast - seq;
1161		word = offset / 32;
1162		bit = offset % 32;
1163		client->cl_seqmask[word] |= (1 << bit);
1164	}
1165	sx_xunlock(&client->cl_lock);
1166}
1167
1168enum auth_stat
1169svc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
1170
1171{
1172	OM_uint32		 min_stat;
1173	XDR	 		 xdrs;
1174	struct svc_rpc_gss_cookedcred *cc;
1175	struct svc_rpc_gss_client *client;
1176	struct rpc_gss_cred	 gc;
1177	struct rpc_gss_init_res	 gr;
1178	gss_qop_t		 qop;
1179	int			 call_stat;
1180	enum auth_stat		 result;
1181
1182	rpc_gss_log_debug("in svc_rpc_gss()");
1183
1184	/* Garbage collect old clients. */
1185	svc_rpc_gss_timeout_clients();
1186
1187	/* Initialize reply. */
1188	rqst->rq_verf = _null_auth;
1189
1190	/* Deserialize client credentials. */
1191	if (rqst->rq_cred.oa_length <= 0)
1192		return (AUTH_BADCRED);
1193
1194	memset(&gc, 0, sizeof(gc));
1195
1196	xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
1197	    rqst->rq_cred.oa_length, XDR_DECODE);
1198
1199	if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
1200		XDR_DESTROY(&xdrs);
1201		return (AUTH_BADCRED);
1202	}
1203	XDR_DESTROY(&xdrs);
1204
1205	client = NULL;
1206
1207	/* Check version. */
1208	if (gc.gc_version != RPCSEC_GSS_VERSION) {
1209		result = AUTH_BADCRED;
1210		goto out;
1211	}
1212
1213	/* Check the proc and find the client (or create it) */
1214	if (gc.gc_proc == RPCSEC_GSS_INIT) {
1215		if (gc.gc_handle.length != 0) {
1216			result = AUTH_BADCRED;
1217			goto out;
1218		}
1219		client = svc_rpc_gss_create_client();
1220		refcount_acquire(&client->cl_refs);
1221	} else {
1222		struct svc_rpc_gss_clientid *p;
1223		if (gc.gc_handle.length != sizeof(*p)) {
1224			result = AUTH_BADCRED;
1225			goto out;
1226		}
1227		p = gc.gc_handle.value;
1228		client = svc_rpc_gss_find_client(p);
1229		if (!client) {
1230			/*
1231			 * Can't find the client - we may have
1232			 * destroyed it - tell the other side to
1233			 * re-authenticate.
1234			 */
1235			result = RPCSEC_GSS_CREDPROBLEM;
1236			goto out;
1237		}
1238	}
1239	cc = rqst->rq_clntcred;
1240	cc->cc_client = client;
1241	cc->cc_service = gc.gc_svc;
1242	cc->cc_seq = gc.gc_seq;
1243
1244	/*
1245	 * The service and sequence number must be ignored for
1246	 * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
1247	 */
1248	if (gc.gc_proc != RPCSEC_GSS_INIT
1249	    && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
1250		/*
1251		 * Check for sequence number overflow.
1252		 */
1253		if (gc.gc_seq >= MAXSEQ) {
1254			result = RPCSEC_GSS_CTXPROBLEM;
1255			goto out;
1256		}
1257
1258		/*
1259		 * Check for valid service.
1260		 */
1261		if (gc.gc_svc != rpc_gss_svc_none &&
1262		    gc.gc_svc != rpc_gss_svc_integrity &&
1263		    gc.gc_svc != rpc_gss_svc_privacy) {
1264			result = AUTH_BADCRED;
1265			goto out;
1266		}
1267	}
1268
1269	/* Handle RPCSEC_GSS control procedure. */
1270	switch (gc.gc_proc) {
1271
1272	case RPCSEC_GSS_INIT:
1273	case RPCSEC_GSS_CONTINUE_INIT:
1274		if (rqst->rq_proc != NULLPROC) {
1275			result = AUTH_REJECTEDCRED;
1276			break;
1277		}
1278
1279		memset(&gr, 0, sizeof(gr));
1280		if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
1281			result = AUTH_REJECTEDCRED;
1282			break;
1283		}
1284
1285		if (gr.gr_major == GSS_S_COMPLETE) {
1286			/*
1287			 * We borrow the space for the call verf to
1288			 * pack our reply verf.
1289			 */
1290			rqst->rq_verf = msg->rm_call.cb_verf;
1291			if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
1292				result = AUTH_REJECTEDCRED;
1293				break;
1294			}
1295		} else {
1296			rqst->rq_verf = _null_auth;
1297		}
1298
1299		call_stat = svc_sendreply(rqst,
1300		    (xdrproc_t) xdr_rpc_gss_init_res,
1301		    (caddr_t) &gr);
1302
1303		gss_release_buffer(&min_stat, &gr.gr_token);
1304
1305		if (!call_stat) {
1306			result = AUTH_FAILED;
1307			break;
1308		}
1309
1310		if (gr.gr_major == GSS_S_COMPLETE)
1311			client->cl_state = CLIENT_ESTABLISHED;
1312
1313		result = RPCSEC_GSS_NODISPATCH;
1314		break;
1315
1316	case RPCSEC_GSS_DATA:
1317	case RPCSEC_GSS_DESTROY:
1318		if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
1319			result = RPCSEC_GSS_NODISPATCH;
1320			break;
1321		}
1322
1323		if (!svc_rpc_gss_validate(client, msg, &qop)) {
1324			result = RPCSEC_GSS_CREDPROBLEM;
1325			break;
1326		}
1327
1328		/*
1329		 * We borrow the space for the call verf to pack our
1330		 * reply verf.
1331		 */
1332		rqst->rq_verf = msg->rm_call.cb_verf;
1333		if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
1334			result = RPCSEC_GSS_CTXPROBLEM;
1335			break;
1336		}
1337
1338		svc_rpc_gss_update_seq(client, gc.gc_seq);
1339
1340		/*
1341		 * Change the SVCAUTH ops on the request to point at
1342		 * our own code so that we can unwrap the arguments
1343		 * and wrap the result. The caller will re-set this on
1344		 * every request to point to a set of null wrap/unwrap
1345		 * methods. Acquire an extra reference to the client
1346		 * which will be released by svc_rpc_gss_release()
1347		 * after the request has finished processing.
1348		 */
1349		refcount_acquire(&client->cl_refs);
1350		rqst->rq_auth.svc_ah_ops = &svc_auth_gss_ops;
1351		rqst->rq_auth.svc_ah_private = cc;
1352
1353		if (gc.gc_proc == RPCSEC_GSS_DATA) {
1354			/*
1355			 * We might be ready to do a callback to the server to
1356			 * see if it wants to accept/reject the connection.
1357			 */
1358			sx_xlock(&client->cl_lock);
1359			if (!client->cl_done_callback) {
1360				client->cl_done_callback = TRUE;
1361				client->cl_qop = qop;
1362				client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1363					client->cl_rawcred.mechanism, qop);
1364				if (!svc_rpc_gss_callback(client, rqst)) {
1365					result = AUTH_REJECTEDCRED;
1366					sx_xunlock(&client->cl_lock);
1367					break;
1368				}
1369			}
1370			sx_xunlock(&client->cl_lock);
1371
1372			/*
1373			 * If the server has locked this client to a
1374			 * particular service+qop pair, enforce that
1375			 * restriction now.
1376			 */
1377			if (client->cl_locked) {
1378				if (client->cl_rawcred.service != gc.gc_svc) {
1379					result = AUTH_FAILED;
1380					break;
1381				} else if (client->cl_qop != qop) {
1382					result = AUTH_BADVERF;
1383					break;
1384				}
1385			}
1386
1387			/*
1388			 * If the qop changed, look up the new qop
1389			 * name for rawcred.
1390			 */
1391			if (client->cl_qop != qop) {
1392				client->cl_qop = qop;
1393				client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1394					client->cl_rawcred.mechanism, qop);
1395			}
1396
1397			/*
1398			 * Make sure we use the right service value
1399			 * for unwrap/wrap.
1400			 */
1401			if (client->cl_rawcred.service != gc.gc_svc) {
1402				client->cl_rawcred.service = gc.gc_svc;
1403				svc_rpc_gss_set_flavor(client);
1404			}
1405
1406			result = AUTH_OK;
1407		} else {
1408			if (rqst->rq_proc != NULLPROC) {
1409				result = AUTH_REJECTEDCRED;
1410				break;
1411			}
1412
1413			call_stat = svc_sendreply(rqst,
1414			    (xdrproc_t) xdr_void, (caddr_t) NULL);
1415
1416			if (!call_stat) {
1417				result = AUTH_FAILED;
1418				break;
1419			}
1420
1421			svc_rpc_gss_forget_client(client);
1422
1423			result = RPCSEC_GSS_NODISPATCH;
1424			break;
1425		}
1426		break;
1427
1428	default:
1429		result = AUTH_BADCRED;
1430		break;
1431	}
1432out:
1433	if (client)
1434		svc_rpc_gss_release_client(client);
1435
1436	xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
1437	return (result);
1438}
1439
1440static bool_t
1441svc_rpc_gss_wrap(SVCAUTH *auth, struct mbuf **mp)
1442{
1443	struct svc_rpc_gss_cookedcred *cc;
1444	struct svc_rpc_gss_client *client;
1445
1446	rpc_gss_log_debug("in svc_rpc_gss_wrap()");
1447
1448	cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1449	client = cc->cc_client;
1450	if (client->cl_state != CLIENT_ESTABLISHED
1451	    || cc->cc_service == rpc_gss_svc_none || *mp == NULL) {
1452		return (TRUE);
1453	}
1454
1455	return (xdr_rpc_gss_wrap_data(mp,
1456		client->cl_ctx, client->cl_qop,
1457		cc->cc_service, cc->cc_seq));
1458}
1459
1460static bool_t
1461svc_rpc_gss_unwrap(SVCAUTH *auth, struct mbuf **mp)
1462{
1463	struct svc_rpc_gss_cookedcred *cc;
1464	struct svc_rpc_gss_client *client;
1465
1466	rpc_gss_log_debug("in svc_rpc_gss_unwrap()");
1467
1468	cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1469	client = cc->cc_client;
1470	if (client->cl_state != CLIENT_ESTABLISHED
1471	    || cc->cc_service == rpc_gss_svc_none) {
1472		return (TRUE);
1473	}
1474
1475	return (xdr_rpc_gss_unwrap_data(mp,
1476		client->cl_ctx, client->cl_qop,
1477		cc->cc_service, cc->cc_seq));
1478}
1479
1480static void
1481svc_rpc_gss_release(SVCAUTH *auth)
1482{
1483	struct svc_rpc_gss_cookedcred *cc;
1484	struct svc_rpc_gss_client *client;
1485
1486	rpc_gss_log_debug("in svc_rpc_gss_release()");
1487
1488	cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1489	client = cc->cc_client;
1490	svc_rpc_gss_release_client(client);
1491}
1492