1191005Sdelphij/*-
2204977Simp * Copyright (c) 2008 Doug Rabson
331899Ssef * All rights reserved.
431899Ssef *
531899Ssef * Redistribution and use in source and binary forms, with or without
631899Ssef * modification, are permitted provided that the following conditions
731899Ssef * are met:
831899Ssef * 1. Redistributions of source code must retain the above copyright
931899Ssef *    notice, this list of conditions and the following disclaimer.
1031899Ssef * 2. Redistributions in binary form must reproduce the above copyright
1131899Ssef *    notice, this list of conditions and the following disclaimer in the
1231899Ssef *    documentation and/or other materials provided with the distribution.
1331899Ssef *
1431899Ssef * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1531899Ssef * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1631899Ssef * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1731899Ssef * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1831899Ssef * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1931899Ssef * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2031899Ssef * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2131899Ssef * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2231899Ssef * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2331899Ssef * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2431899Ssef * SUCH DAMAGE.
2531899Ssef */
2631899Ssef/*
2731899Ssef  svc_rpcsec_gss.c
2831899Ssef
2931899Ssef  Copyright (c) 2000 The Regents of the University of Michigan.
3031899Ssef  All rights reserved.
3131899Ssef
32119852Scharnier  Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
33119852Scharnier  All rights reserved, all wrongs reversed.
3432275Scharnier
3531899Ssef  Redistribution and use in source and binary forms, with or without
3631567Ssef  modification, are permitted provided that the following conditions
3731567Ssef  are met:
3831567Ssef
3931567Ssef  1. Redistributions of source code must retain the above copyright
4031567Ssef     notice, this list of conditions and the following disclaimer.
4185301Sdes  2. Redistributions in binary form must reproduce the above copyright
42123916Scracauer     notice, this list of conditions and the following disclaimer in the
43104581Smike     documentation and/or other materials provided with the distribution.
44123916Scracauer  3. Neither the name of the University nor the names of its
45168569Sdelphij     contributors may be used to endorse or promote products derived
46191005Sdelphij     from this software without specific prior written permission.
4785301Sdes
48132306Salfred  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
4932275Scharnier  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
50200462Sdelphij  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
51200462Sdelphij  DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
5232275Scharnier  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
5331567Ssef  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
5431567Ssef  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
5531567Ssef  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
56101423Smdodd  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
5731579Speter  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
5831567Ssef  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
59101282Smdodd
6087703Smarkm  $Id: svc_auth_gss.c,v 1.27 2002/01/15 15:43:00 andros Exp $
61192025Sdds */
6231567Ssef
63171645Smarcel#include <sys/cdefs.h>
6431567Ssef__FBSDID("$FreeBSD: stable/11/sys/rpc/rpcsec_gss/svc_rpcsec_gss.c 355967 2019-12-20 23:08:10Z rmacklem $");
65144177Salfred
6632275Scharnier#include <sys/param.h>
6732275Scharnier#include <sys/systm.h>
68144177Salfred#include <sys/jail.h>
69192025Sdds#include <sys/kernel.h>
70192025Sdds#include <sys/kobj.h>
71144177Salfred#include <sys/lock.h>
7231567Ssef#include <sys/malloc.h>
7331567Ssef#include <sys/mbuf.h>
7438897Ssef#include <sys/mutex.h>
7538897Ssef#include <sys/proc.h>
7638897Ssef#include <sys/sx.h>
7738897Ssef#include <sys/ucred.h>
7831567Ssef
79144177Salfred#include <rpc/rpc.h>
80144177Salfred#include <rpc/rpcsec_gss.h>
81144177Salfred
8231567Ssef#include "rpcsec_gss_int.h"
83130394Sdwmalone
84144177Salfredstatic bool_t   svc_rpc_gss_wrap(SVCAUTH *, struct mbuf **);
85179051Sjhbstatic bool_t   svc_rpc_gss_unwrap(SVCAUTH *, struct mbuf **);
86179051Sjhbstatic void     svc_rpc_gss_release(SVCAUTH *);
87130394Sdwmalonestatic enum auth_stat svc_rpc_gss(struct svc_req *, struct rpc_msg *);
8839908Ssefstatic int rpc_gss_svc_getcred(struct svc_req *, struct ucred **, int *);
89144177Salfred
90144177Salfredstatic struct svc_auth_ops svc_auth_gss_ops = {
91144177Salfred	svc_rpc_gss_wrap,
92144177Salfred	svc_rpc_gss_unwrap,
9339908Ssef	svc_rpc_gss_release,
94106716Smarcel};
95144177Salfred
96106716Smarcelstruct sx svc_rpc_gss_lock;
97154047Sgrehan
98154047Sgrehanstruct svc_rpc_gss_callback {
99154047Sgrehan	SLIST_ENTRY(svc_rpc_gss_callback) cb_link;
100154047Sgrehan	rpc_gss_callback_t	cb_callback;
101101320Sjake};
102144177Salfredstatic SLIST_HEAD(svc_rpc_gss_callback_list, svc_rpc_gss_callback)
103101320Sjake	svc_rpc_gss_callbacks = SLIST_HEAD_INITIALIZER(svc_rpc_gss_callbacks);
104188628Simp
105188628Simpstruct svc_rpc_gss_svc_name {
106188628Simp	SLIST_ENTRY(svc_rpc_gss_svc_name) sn_link;
107188628Simp	char			*sn_principal;
108188628Simp	gss_OID			sn_mech;
109144177Salfred	u_int			sn_req_time;
11031567Ssef	gss_cred_id_t		sn_cred;
11131567Ssef	u_int			sn_program;
11231567Ssef	u_int			sn_version;
11331567Ssef};
114168569Sdelphijstatic SLIST_HEAD(svc_rpc_gss_svc_name_list, svc_rpc_gss_svc_name)
11531567Ssef	svc_rpc_gss_svc_names = SLIST_HEAD_INITIALIZER(svc_rpc_gss_svc_names);
11631567Ssef
11731567Ssefenum svc_rpc_gss_client_state {
118144177Salfred	CLIENT_NEW,				/* still authenticating */
119144177Salfred	CLIENT_ESTABLISHED,			/* context established */
120144177Salfred	CLIENT_STALE				/* garbage to collect */
121144177Salfred};
122168569Sdelphij
123168569Sdelphij#define SVC_RPC_GSS_SEQWINDOW	128
124168569Sdelphij
125168569Sdelphijstruct svc_rpc_gss_clientid {
12631567Ssef	unsigned long		ci_hostid;
127168569Sdelphij	uint32_t		ci_boottime;
128168569Sdelphij	uint32_t		ci_id;
129168569Sdelphij};
130168569Sdelphij
131168569Sdelphijstruct svc_rpc_gss_client {
132168569Sdelphij	TAILQ_ENTRY(svc_rpc_gss_client) cl_link;
133168569Sdelphij	TAILQ_ENTRY(svc_rpc_gss_client) cl_alllink;
13431567Ssef	volatile u_int		cl_refs;
135144177Salfred	struct sx		cl_lock;
136144177Salfred	struct svc_rpc_gss_clientid cl_id;
137144177Salfred	time_t			cl_expiration;	/* when to gc */
13831567Ssef	enum svc_rpc_gss_client_state cl_state;	/* client state */
139144177Salfred	bool_t			cl_locked;	/* fixed service+qop */
140144177Salfred	gss_ctx_id_t		cl_ctx;		/* context id */
141144177Salfred	gss_cred_id_t		cl_creds;	/* delegated creds */
142144177Salfred	gss_name_t		cl_cname;	/* client name */
143144177Salfred	struct svc_rpc_gss_svc_name *cl_sname;	/* server name used */
144144177Salfred	rpc_gss_rawcred_t	cl_rawcred;	/* raw credentials */
14531567Ssef	rpc_gss_ucred_t		cl_ucred;	/* unix-style credentials */
14631567Ssef	struct ucred		*cl_cred;	/* kernel-style credentials */
147132306Salfred	int			cl_rpcflavor;	/* RPC pseudo sec flavor */
148132306Salfred	bool_t			cl_done_callback; /* TRUE after call */
149132306Salfred	void			*cl_cookie;	/* user cookie from callback */
150132306Salfred	gid_t			cl_gid_storage[NGROUPS];
151132306Salfred	gss_OID			cl_mech;	/* mechanism */
152132306Salfred	gss_qop_t		cl_qop;		/* quality of protection */
153132306Salfred	uint32_t		cl_seqlast;	/* sequence window origin */
154132306Salfred	uint32_t		cl_seqmask[SVC_RPC_GSS_SEQWINDOW/32]; /* bitmask of seqnums */
155132306Salfred};
156132306SalfredTAILQ_HEAD(svc_rpc_gss_client_list, svc_rpc_gss_client);
157132306Salfred
158132306Salfred/*
159132306Salfred * This structure holds enough information to unwrap arguments or wrap
160132306Salfred * results for a given request. We use the rq_clntcred area for this
161132306Salfred * (which is a per-request buffer).
162132306Salfred */
163132306Salfredstruct svc_rpc_gss_cookedcred {
16432275Scharnier	struct svc_rpc_gss_client *cc_client;
165144177Salfred	rpc_gss_service_t	cc_service;
166144177Salfred	uint32_t		cc_seq;
167144177Salfred};
168144177Salfred
169191005Sdelphij#define CLIENT_HASH_SIZE	256
170191005Sdelphij#define CLIENT_MAX		128
171144177Salfredu_int svc_rpc_gss_client_max = CLIENT_MAX;
172144177Salfred
173171055SdelphijSYSCTL_NODE(_kern, OID_AUTO, rpc, CTLFLAG_RW, 0, "RPC");
174144178SalfredSYSCTL_NODE(_kern_rpc, OID_AUTO, gss, CTLFLAG_RW, 0, "GSS");
175144177Salfred
176144177SalfredSYSCTL_UINT(_kern_rpc_gss, OID_AUTO, client_max, CTLFLAG_RW,
17731567Ssef    &svc_rpc_gss_client_max, 0,
178144178Salfred    "Max number of rpc-gss clients");
179144178Salfred
180144178Salfredstatic u_int svc_rpc_gss_lifetime_max = 0;
181144177SalfredSYSCTL_UINT(_kern_rpc_gss, OID_AUTO, lifetime_max, CTLFLAG_RW,
182192153Sdelphij    &svc_rpc_gss_lifetime_max, 0,
183144177Salfred    "Maximum lifetime (seconds) of rpc-gss clients");
184192153Sdelphij
185192153Sdelphijstatic u_int svc_rpc_gss_client_count;
186144177SalfredSYSCTL_UINT(_kern_rpc_gss, OID_AUTO, client_count, CTLFLAG_RD,
187153963Sbrian    &svc_rpc_gss_client_count, 0,
188168569Sdelphij    "Number of rpc-gss clients");
189168569Sdelphij
190168569Sdelphijstruct svc_rpc_gss_client_list svc_rpc_gss_client_hash[CLIENT_HASH_SIZE];
191192025Sddsstruct svc_rpc_gss_client_list svc_rpc_gss_clients;
192144177Salfredstatic uint32_t svc_rpc_gss_next_clientid = 1;
193144177Salfred
194144177Salfredstatic void
195171055Sdelphijsvc_rpc_gss_init(void *arg)
196171055Sdelphij{
197171055Sdelphij	int i;
198171055Sdelphij
199171055Sdelphij	for (i = 0; i < CLIENT_HASH_SIZE; i++)
200144177Salfred		TAILQ_INIT(&svc_rpc_gss_client_hash[i]);
201144177Salfred	TAILQ_INIT(&svc_rpc_gss_clients);
202144177Salfred	svc_auth_reg(RPCSEC_GSS, svc_rpc_gss, rpc_gss_svc_getcred);
203144177Salfred	sx_init(&svc_rpc_gss_lock, "gsslock");
204144177Salfred}
205144177SalfredSYSINIT(svc_rpc_gss_init, SI_SUB_KMEM, SI_ORDER_ANY, svc_rpc_gss_init, NULL);
206144177Salfred
207192025Sddsbool_t
208192025Sddsrpc_gss_set_callback(rpc_gss_callback_t *cb)
209192025Sdds{
210144177Salfred	struct svc_rpc_gss_callback *scb;
211144177Salfred
212144177Salfred	scb = mem_alloc(sizeof(struct svc_rpc_gss_callback));
213144177Salfred	if (!scb) {
214144177Salfred		_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
215144177Salfred		return (FALSE);
216144177Salfred	}
217144177Salfred	scb->cb_callback = *cb;
218144177Salfred	sx_xlock(&svc_rpc_gss_lock);
219144177Salfred	SLIST_INSERT_HEAD(&svc_rpc_gss_callbacks, scb, cb_link);
220144177Salfred	sx_xunlock(&svc_rpc_gss_lock);
221144177Salfred
222153963Sbrian	return (TRUE);
223153963Sbrian}
224153963Sbrian
225144177Salfredvoid
226144177Salfredrpc_gss_clear_callback(rpc_gss_callback_t *cb)
227144177Salfred{
228144177Salfred	struct svc_rpc_gss_callback *scb;
229144177Salfred
230144177Salfred	sx_xlock(&svc_rpc_gss_lock);
231144177Salfred	SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
23231567Ssef		if (scb->cb_callback.program == cb->program
233144177Salfred		    && scb->cb_callback.version == cb->version
234144177Salfred		    && scb->cb_callback.callback == cb->callback) {
235144177Salfred			SLIST_REMOVE(&svc_rpc_gss_callbacks, scb,
236144177Salfred			    svc_rpc_gss_callback, cb_link);
23731567Ssef			sx_xunlock(&svc_rpc_gss_lock);
238144177Salfred			mem_free(scb, sizeof(*scb));
239144177Salfred			return;
240144177Salfred		}
241144177Salfred	}
242200752Sjh	sx_xunlock(&svc_rpc_gss_lock);
243200752Sjh}
244200752Sjh
245200752Sjhstatic bool_t
246200752Sjhrpc_gss_acquire_svc_cred(struct svc_rpc_gss_svc_name *sname)
247200752Sjh{
24832275Scharnier	OM_uint32		maj_stat, min_stat;
249144177Salfred	gss_buffer_desc		namebuf;
250144177Salfred	gss_name_t		name;
251144177Salfred	gss_OID_set_desc	oid_set;
252144177Salfred
253144177Salfred	oid_set.count = 1;
254144177Salfred	oid_set.elements = sname->sn_mech;
25531567Ssef
256144177Salfred	namebuf.value = (void *) sname->sn_principal;
257144177Salfred	namebuf.length = strlen(sname->sn_principal);
258144177Salfred
259144177Salfred	maj_stat = gss_import_name(&min_stat, &namebuf,
260144177Salfred				   GSS_C_NT_HOSTBASED_SERVICE, &name);
261144177Salfred	if (maj_stat != GSS_S_COMPLETE)
262144177Salfred		return (FALSE);
263168569Sdelphij
264144177Salfred	if (sname->sn_cred != GSS_C_NO_CREDENTIAL)
265144177Salfred		gss_release_cred(&min_stat, &sname->sn_cred);
266144177Salfred
267144177Salfred	maj_stat = gss_acquire_cred(&min_stat, name,
26831567Ssef	    sname->sn_req_time, &oid_set, GSS_C_ACCEPT, &sname->sn_cred,
26931567Ssef	    NULL, NULL);
270144177Salfred	if (maj_stat != GSS_S_COMPLETE) {
271144177Salfred		gss_release_name(&min_stat, &name);
272144177Salfred		return (FALSE);
273144177Salfred	}
27431567Ssef	gss_release_name(&min_stat, &name);
275101283Smdodd
276168569Sdelphij	return (TRUE);
277168569Sdelphij}
278144178Salfred
279144177Salfredbool_t
280144177Salfredrpc_gss_set_svc_name(const char *principal, const char *mechanism,
281144177Salfred    u_int req_time, u_int program, u_int version)
282144177Salfred{
283144177Salfred	struct svc_rpc_gss_svc_name *sname;
28431567Ssef	gss_OID			mech_oid;
285144177Salfred
286101285Smdodd	if (!rpc_gss_mech_to_oid(mechanism, &mech_oid))
287144177Salfred		return (FALSE);
288158630Spav
289168569Sdelphij	sname = mem_alloc(sizeof(*sname));
29031567Ssef	if (!sname)
291168569Sdelphij		return (FALSE);
292168569Sdelphij	sname->sn_principal = strdup(principal, M_RPC);
293168569Sdelphij	sname->sn_mech = mech_oid;
294168569Sdelphij	sname->sn_req_time = req_time;
295168569Sdelphij	sname->sn_cred = GSS_C_NO_CREDENTIAL;
296168569Sdelphij	sname->sn_program = program;
297168569Sdelphij	sname->sn_version = version;
298168569Sdelphij
299168569Sdelphij	if (!rpc_gss_acquire_svc_cred(sname)) {
30031567Ssef		free(sname->sn_principal, M_RPC);
301168569Sdelphij		mem_free(sname, sizeof(*sname));
302168569Sdelphij		return (FALSE);
303168569Sdelphij	}
304168569Sdelphij
305168569Sdelphij	sx_xlock(&svc_rpc_gss_lock);
306168569Sdelphij	SLIST_INSERT_HEAD(&svc_rpc_gss_svc_names, sname, sn_link);
307101283Smdodd	sx_xunlock(&svc_rpc_gss_lock);
308168569Sdelphij
309168569Sdelphij	return (TRUE);
310168569Sdelphij}
311168569Sdelphij
312168569Sdelphijvoid
313168569Sdelphijrpc_gss_clear_svc_name(u_int program, u_int version)
314168569Sdelphij{
315168569Sdelphij	OM_uint32		min_stat;
316168569Sdelphij	struct svc_rpc_gss_svc_name *sname;
317144177Salfred
318144177Salfred	sx_xlock(&svc_rpc_gss_lock);
319168569Sdelphij	SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
320168569Sdelphij		if (sname->sn_program == program
321168569Sdelphij		    && sname->sn_version == version) {
322168569Sdelphij			SLIST_REMOVE(&svc_rpc_gss_svc_names, sname,
323168569Sdelphij			    svc_rpc_gss_svc_name, sn_link);
324144177Salfred			sx_xunlock(&svc_rpc_gss_lock);
325168569Sdelphij			gss_release_cred(&min_stat, &sname->sn_cred);
326168569Sdelphij			free(sname->sn_principal, M_RPC);
327168569Sdelphij			mem_free(sname, sizeof(*sname));
328168569Sdelphij			return;
329168569Sdelphij		}
330168569Sdelphij	}
331168569Sdelphij	sx_xunlock(&svc_rpc_gss_lock);
332168569Sdelphij}
333168569Sdelphij
334144177Salfredbool_t
335168569Sdelphijrpc_gss_get_principal_name(rpc_gss_principal_t *principal,
336168569Sdelphij    const char *mech, const char *name, const char *node, const char *domain)
337168569Sdelphij{
338168569Sdelphij	OM_uint32		maj_stat, min_stat;
339168569Sdelphij	gss_OID			mech_oid;
340168569Sdelphij	size_t			namelen;
341168569Sdelphij	gss_buffer_desc		buf;
342168569Sdelphij	gss_name_t		gss_name, gss_mech_name;
343168569Sdelphij	rpc_gss_principal_t	result;
344168569Sdelphij
345168569Sdelphij	if (!rpc_gss_mech_to_oid(mech, &mech_oid))
346168569Sdelphij		return (FALSE);
347168569Sdelphij
348168569Sdelphij	/*
349192025Sdds	 * Construct a gss_buffer containing the full name formatted
350192025Sdds	 * as "name/node@domain" where node and domain are optional.
351168569Sdelphij	 */
352168569Sdelphij	namelen = strlen(name) + 1;
353168569Sdelphij	if (node) {
354168569Sdelphij		namelen += strlen(node) + 1;
355168569Sdelphij	}
356168569Sdelphij	if (domain) {
357168569Sdelphij		namelen += strlen(domain) + 1;
358168569Sdelphij	}
359168569Sdelphij
360168569Sdelphij	buf.value = mem_alloc(namelen);
361168569Sdelphij	buf.length = namelen;
362168569Sdelphij	strcpy((char *) buf.value, name);
363168569Sdelphij	if (node) {
364168569Sdelphij		strcat((char *) buf.value, "/");
365168569Sdelphij		strcat((char *) buf.value, node);
366168569Sdelphij	}
367168569Sdelphij	if (domain) {
368168569Sdelphij		strcat((char *) buf.value, "@");
369168569Sdelphij		strcat((char *) buf.value, domain);
370168569Sdelphij	}
371168569Sdelphij
372144177Salfred	/*
373168569Sdelphij	 * Convert that to a gss_name_t and then convert that to a
374191005Sdelphij	 * mechanism name in the selected mechanism.
375191005Sdelphij	 */
376191005Sdelphij	maj_stat = gss_import_name(&min_stat, &buf,
377191005Sdelphij	    GSS_C_NT_USER_NAME, &gss_name);
378191005Sdelphij	mem_free(buf.value, buf.length);
379191005Sdelphij	if (maj_stat != GSS_S_COMPLETE) {
380192025Sdds		rpc_gss_log_status("gss_import_name", mech_oid, maj_stat, min_stat);
381192025Sdds		return (FALSE);
382192025Sdds	}
383192025Sdds	maj_stat = gss_canonicalize_name(&min_stat, gss_name, mech_oid,
384192025Sdds	    &gss_mech_name);
385144177Salfred	if (maj_stat != GSS_S_COMPLETE) {
38631567Ssef		rpc_gss_log_status("gss_canonicalize_name", mech_oid, maj_stat,
387		    min_stat);
388		gss_release_name(&min_stat, &gss_name);
389		return (FALSE);
390	}
391	gss_release_name(&min_stat, &gss_name);
392
393	/*
394	 * Export the mechanism name and use that to construct the
395	 * rpc_gss_principal_t result.
396	 */
397	maj_stat = gss_export_name(&min_stat, gss_mech_name, &buf);
398	if (maj_stat != GSS_S_COMPLETE) {
399		rpc_gss_log_status("gss_export_name", mech_oid, maj_stat, min_stat);
400		gss_release_name(&min_stat, &gss_mech_name);
401		return (FALSE);
402	}
403	gss_release_name(&min_stat, &gss_mech_name);
404
405	result = mem_alloc(sizeof(int) + buf.length);
406	if (!result) {
407		gss_release_buffer(&min_stat, &buf);
408		return (FALSE);
409	}
410	result->len = buf.length;
411	memcpy(result->name, buf.value, buf.length);
412	gss_release_buffer(&min_stat, &buf);
413
414	*principal = result;
415	return (TRUE);
416}
417
418bool_t
419rpc_gss_getcred(struct svc_req *req, rpc_gss_rawcred_t **rcred,
420    rpc_gss_ucred_t **ucred, void **cookie)
421{
422	struct svc_rpc_gss_cookedcred *cc;
423	struct svc_rpc_gss_client *client;
424
425	if (req->rq_cred.oa_flavor != RPCSEC_GSS)
426		return (FALSE);
427
428	cc = req->rq_clntcred;
429	client = cc->cc_client;
430	if (rcred)
431		*rcred = &client->cl_rawcred;
432	if (ucred)
433		*ucred = &client->cl_ucred;
434	if (cookie)
435		*cookie = client->cl_cookie;
436	return (TRUE);
437}
438
439/*
440 * This simpler interface is used by svc_getcred to copy the cred data
441 * into a kernel cred structure.
442 */
443static int
444rpc_gss_svc_getcred(struct svc_req *req, struct ucred **crp, int *flavorp)
445{
446	struct ucred *cr;
447	struct svc_rpc_gss_cookedcred *cc;
448	struct svc_rpc_gss_client *client;
449	rpc_gss_ucred_t *uc;
450
451	if (req->rq_cred.oa_flavor != RPCSEC_GSS)
452		return (FALSE);
453
454	cc = req->rq_clntcred;
455	client = cc->cc_client;
456
457	if (flavorp)
458		*flavorp = client->cl_rpcflavor;
459
460	if (client->cl_cred) {
461		*crp = crhold(client->cl_cred);
462		return (TRUE);
463	}
464
465	uc = &client->cl_ucred;
466	cr = client->cl_cred = crget();
467	cr->cr_uid = cr->cr_ruid = cr->cr_svuid = uc->uid;
468	cr->cr_rgid = cr->cr_svgid = uc->gid;
469	crsetgroups(cr, uc->gidlen, uc->gidlist);
470	cr->cr_prison = &prison0;
471	prison_hold(cr->cr_prison);
472	*crp = crhold(cr);
473
474	return (TRUE);
475}
476
477int
478rpc_gss_svc_max_data_length(struct svc_req *req, int max_tp_unit_len)
479{
480	struct svc_rpc_gss_cookedcred *cc = req->rq_clntcred;
481	struct svc_rpc_gss_client *client = cc->cc_client;
482	int			want_conf;
483	OM_uint32		max;
484	OM_uint32		maj_stat, min_stat;
485	int			result;
486
487	switch (client->cl_rawcred.service) {
488	case rpc_gss_svc_none:
489		return (max_tp_unit_len);
490		break;
491
492	case rpc_gss_svc_default:
493	case rpc_gss_svc_integrity:
494		want_conf = FALSE;
495		break;
496
497	case rpc_gss_svc_privacy:
498		want_conf = TRUE;
499		break;
500
501	default:
502		return (0);
503	}
504
505	maj_stat = gss_wrap_size_limit(&min_stat, client->cl_ctx, want_conf,
506	    client->cl_qop, max_tp_unit_len, &max);
507
508	if (maj_stat == GSS_S_COMPLETE) {
509		result = (int) max;
510		if (result < 0)
511			result = 0;
512		return (result);
513	} else {
514		rpc_gss_log_status("gss_wrap_size_limit", client->cl_mech,
515		    maj_stat, min_stat);
516		return (0);
517	}
518}
519
520static struct svc_rpc_gss_client *
521svc_rpc_gss_find_client(struct svc_rpc_gss_clientid *id)
522{
523	struct svc_rpc_gss_client *client;
524	struct svc_rpc_gss_client_list *list;
525	struct timeval boottime;
526	unsigned long hostid;
527
528	rpc_gss_log_debug("in svc_rpc_gss_find_client(%d)", id->ci_id);
529
530	getcredhostid(curthread->td_ucred, &hostid);
531	getboottime(&boottime);
532	if (id->ci_hostid != hostid || id->ci_boottime != boottime.tv_sec)
533		return (NULL);
534
535	list = &svc_rpc_gss_client_hash[id->ci_id % CLIENT_HASH_SIZE];
536	sx_xlock(&svc_rpc_gss_lock);
537	TAILQ_FOREACH(client, list, cl_link) {
538		if (client->cl_id.ci_id == id->ci_id) {
539			/*
540			 * Move this client to the front of the LRU
541			 * list.
542			 */
543			TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
544			TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client,
545			    cl_alllink);
546			refcount_acquire(&client->cl_refs);
547			break;
548		}
549	}
550	sx_xunlock(&svc_rpc_gss_lock);
551
552	return (client);
553}
554
555static struct svc_rpc_gss_client *
556svc_rpc_gss_create_client(void)
557{
558	struct svc_rpc_gss_client *client;
559	struct svc_rpc_gss_client_list *list;
560	struct timeval boottime;
561	unsigned long hostid;
562
563	rpc_gss_log_debug("in svc_rpc_gss_create_client()");
564
565	client = mem_alloc(sizeof(struct svc_rpc_gss_client));
566	memset(client, 0, sizeof(struct svc_rpc_gss_client));
567
568	/*
569	 * Set the initial value of cl_refs to two.  One for the caller
570	 * and the other to hold onto the client structure until it expires.
571	 */
572	refcount_init(&client->cl_refs, 2);
573	sx_init(&client->cl_lock, "GSS-client");
574	getcredhostid(curthread->td_ucred, &hostid);
575	client->cl_id.ci_hostid = hostid;
576	getboottime(&boottime);
577	client->cl_id.ci_boottime = boottime.tv_sec;
578	client->cl_id.ci_id = svc_rpc_gss_next_clientid++;
579
580	/*
581	 * Start the client off with a short expiration time. We will
582	 * try to get a saner value from the client creds later.
583	 */
584	client->cl_state = CLIENT_NEW;
585	client->cl_locked = FALSE;
586	client->cl_expiration = time_uptime + 5*60;
587
588	list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
589	sx_xlock(&svc_rpc_gss_lock);
590	TAILQ_INSERT_HEAD(list, client, cl_link);
591	TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client, cl_alllink);
592	svc_rpc_gss_client_count++;
593	sx_xunlock(&svc_rpc_gss_lock);
594	return (client);
595}
596
597static void
598svc_rpc_gss_destroy_client(struct svc_rpc_gss_client *client)
599{
600	OM_uint32 min_stat;
601
602	rpc_gss_log_debug("in svc_rpc_gss_destroy_client()");
603
604	if (client->cl_ctx)
605		gss_delete_sec_context(&min_stat,
606		    &client->cl_ctx, GSS_C_NO_BUFFER);
607
608	if (client->cl_cname)
609		gss_release_name(&min_stat, &client->cl_cname);
610
611	if (client->cl_rawcred.client_principal)
612		mem_free(client->cl_rawcred.client_principal,
613		    sizeof(*client->cl_rawcred.client_principal)
614		    + client->cl_rawcred.client_principal->len);
615
616	if (client->cl_cred)
617		crfree(client->cl_cred);
618
619	sx_destroy(&client->cl_lock);
620	mem_free(client, sizeof(*client));
621}
622
623/*
624 * Drop a reference to a client and free it if that was the last reference.
625 */
626static void
627svc_rpc_gss_release_client(struct svc_rpc_gss_client *client)
628{
629
630	if (!refcount_release(&client->cl_refs))
631		return;
632	svc_rpc_gss_destroy_client(client);
633}
634
635/*
636 * Remove a client from our global lists.
637 * Must be called with svc_rpc_gss_lock held.
638 */
639static void
640svc_rpc_gss_forget_client_locked(struct svc_rpc_gss_client *client)
641{
642	struct svc_rpc_gss_client_list *list;
643
644	sx_assert(&svc_rpc_gss_lock, SX_XLOCKED);
645	list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
646	TAILQ_REMOVE(list, client, cl_link);
647	TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
648	svc_rpc_gss_client_count--;
649}
650
651/*
652 * Remove a client from our global lists and free it if we can.
653 */
654static void
655svc_rpc_gss_forget_client(struct svc_rpc_gss_client *client)
656{
657	struct svc_rpc_gss_client_list *list;
658	struct svc_rpc_gss_client *tclient;
659
660	list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % CLIENT_HASH_SIZE];
661	sx_xlock(&svc_rpc_gss_lock);
662	TAILQ_FOREACH(tclient, list, cl_link) {
663		/*
664		 * Make sure this client has not already been removed
665		 * from the lists by svc_rpc_gss_forget_client() or
666		 * svc_rpc_gss_forget_client_locked().
667		 */
668		if (client == tclient) {
669			svc_rpc_gss_forget_client_locked(client);
670			sx_xunlock(&svc_rpc_gss_lock);
671			svc_rpc_gss_release_client(client);
672			return;
673		}
674	}
675	sx_xunlock(&svc_rpc_gss_lock);
676}
677
678static void
679svc_rpc_gss_timeout_clients(void)
680{
681	struct svc_rpc_gss_client *client;
682	time_t now = time_uptime;
683
684	rpc_gss_log_debug("in svc_rpc_gss_timeout_clients()");
685
686	/*
687	 * First enforce the max client limit. We keep
688	 * svc_rpc_gss_clients in LRU order.
689	 */
690	sx_xlock(&svc_rpc_gss_lock);
691	client = TAILQ_LAST(&svc_rpc_gss_clients, svc_rpc_gss_client_list);
692	while (svc_rpc_gss_client_count > svc_rpc_gss_client_max && client != NULL) {
693		svc_rpc_gss_forget_client_locked(client);
694		sx_xunlock(&svc_rpc_gss_lock);
695		svc_rpc_gss_release_client(client);
696		sx_xlock(&svc_rpc_gss_lock);
697		client = TAILQ_LAST(&svc_rpc_gss_clients,
698		    svc_rpc_gss_client_list);
699	}
700again:
701	TAILQ_FOREACH(client, &svc_rpc_gss_clients, cl_alllink) {
702		if (client->cl_state == CLIENT_STALE
703		    || now > client->cl_expiration) {
704			svc_rpc_gss_forget_client_locked(client);
705			sx_xunlock(&svc_rpc_gss_lock);
706			rpc_gss_log_debug("expiring client %p", client);
707			svc_rpc_gss_release_client(client);
708			sx_xlock(&svc_rpc_gss_lock);
709			goto again;
710		}
711	}
712	sx_xunlock(&svc_rpc_gss_lock);
713}
714
715#ifdef DEBUG
716/*
717 * OID<->string routines.  These are uuuuugly.
718 */
719static OM_uint32
720gss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
721{
722	char		numstr[128];
723	unsigned long	number;
724	int		numshift;
725	size_t		string_length;
726	size_t		i;
727	unsigned char	*cp;
728	char		*bp;
729
730	/* Decoded according to krb5/gssapi_krb5.c */
731
732	/* First determine the size of the string */
733	string_length = 0;
734	number = 0;
735	numshift = 0;
736	cp = (unsigned char *) oid->elements;
737	number = (unsigned long) cp[0];
738	sprintf(numstr, "%ld ", number/40);
739	string_length += strlen(numstr);
740	sprintf(numstr, "%ld ", number%40);
741	string_length += strlen(numstr);
742	for (i=1; i<oid->length; i++) {
743		if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
744			number = (number << 7) | (cp[i] & 0x7f);
745			numshift += 7;
746		}
747		else {
748			*minor_status = 0;
749			return(GSS_S_FAILURE);
750		}
751		if ((cp[i] & 0x80) == 0) {
752			sprintf(numstr, "%ld ", number);
753			string_length += strlen(numstr);
754			number = 0;
755			numshift = 0;
756		}
757	}
758	/*
759	 * If we get here, we've calculated the length of "n n n ... n ".  Add 4
760	 * here for "{ " and "}\0".
761	 */
762	string_length += 4;
763	if ((bp = malloc(string_length, M_GSSAPI, M_WAITOK | M_ZERO))) {
764		strcpy(bp, "{ ");
765		number = (unsigned long) cp[0];
766		sprintf(numstr, "%ld ", number/40);
767		strcat(bp, numstr);
768		sprintf(numstr, "%ld ", number%40);
769		strcat(bp, numstr);
770		number = 0;
771		cp = (unsigned char *) oid->elements;
772		for (i=1; i<oid->length; i++) {
773			number = (number << 7) | (cp[i] & 0x7f);
774			if ((cp[i] & 0x80) == 0) {
775				sprintf(numstr, "%ld ", number);
776				strcat(bp, numstr);
777				number = 0;
778			}
779		}
780		strcat(bp, "}");
781		oid_str->length = strlen(bp)+1;
782		oid_str->value = (void *) bp;
783		*minor_status = 0;
784		return(GSS_S_COMPLETE);
785	}
786	*minor_status = 0;
787	return(GSS_S_FAILURE);
788}
789#endif
790
791static void
792svc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
793    const gss_name_t name)
794{
795	OM_uint32		maj_stat, min_stat;
796	rpc_gss_ucred_t		*uc = &client->cl_ucred;
797	int			numgroups;
798
799	uc->uid = 65534;
800	uc->gid = 65534;
801	uc->gidlist = client->cl_gid_storage;
802
803	numgroups = NGROUPS;
804	maj_stat = gss_pname_to_unix_cred(&min_stat, name, client->cl_mech,
805	    &uc->uid, &uc->gid, &numgroups, &uc->gidlist[0]);
806	if (GSS_ERROR(maj_stat))
807		uc->gidlen = 0;
808	else
809		uc->gidlen = numgroups;
810}
811
812static void
813svc_rpc_gss_set_flavor(struct svc_rpc_gss_client *client)
814{
815	static gss_OID_desc krb5_mech_oid =
816		{9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
817
818	/*
819	 * Attempt to translate mech type and service into a
820	 * 'pseudo flavor'. Hardwire in krb5 support for now.
821	 */
822	if (kgss_oid_equal(client->cl_mech, &krb5_mech_oid)) {
823		switch (client->cl_rawcred.service) {
824		case rpc_gss_svc_default:
825		case rpc_gss_svc_none:
826			client->cl_rpcflavor = RPCSEC_GSS_KRB5;
827			break;
828		case rpc_gss_svc_integrity:
829			client->cl_rpcflavor = RPCSEC_GSS_KRB5I;
830			break;
831		case rpc_gss_svc_privacy:
832			client->cl_rpcflavor = RPCSEC_GSS_KRB5P;
833			break;
834		}
835	} else {
836		client->cl_rpcflavor = RPCSEC_GSS;
837	}
838}
839
840static bool_t
841svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
842			       struct svc_req *rqst,
843			       struct rpc_gss_init_res *gr,
844			       struct rpc_gss_cred *gc)
845{
846	gss_buffer_desc		recv_tok;
847	gss_OID			mech;
848	OM_uint32		maj_stat = 0, min_stat = 0, ret_flags;
849	OM_uint32		cred_lifetime;
850	struct svc_rpc_gss_svc_name *sname;
851
852	rpc_gss_log_debug("in svc_rpc_gss_accept_context()");
853
854	/* Deserialize arguments. */
855	memset(&recv_tok, 0, sizeof(recv_tok));
856
857	if (!svc_getargs(rqst,
858		(xdrproc_t) xdr_gss_buffer_desc,
859		(caddr_t) &recv_tok)) {
860		client->cl_state = CLIENT_STALE;
861		return (FALSE);
862	}
863
864	/*
865	 * First time round, try all the server names we have until
866	 * one matches. Afterwards, stick with that one.
867	 */
868	sx_xlock(&svc_rpc_gss_lock);
869	if (!client->cl_sname) {
870		SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
871			if (sname->sn_program == rqst->rq_prog
872			    && sname->sn_version == rqst->rq_vers) {
873			retry:
874				gr->gr_major = gss_accept_sec_context(
875					&gr->gr_minor,
876					&client->cl_ctx,
877					sname->sn_cred,
878					&recv_tok,
879					GSS_C_NO_CHANNEL_BINDINGS,
880					&client->cl_cname,
881					&mech,
882					&gr->gr_token,
883					&ret_flags,
884					&cred_lifetime,
885					&client->cl_creds);
886				if (gr->gr_major ==
887				    GSS_S_CREDENTIALS_EXPIRED) {
888					/*
889					 * Either our creds really did
890					 * expire or gssd was
891					 * restarted.
892					 */
893					if (rpc_gss_acquire_svc_cred(sname))
894						goto retry;
895				}
896				client->cl_sname = sname;
897				break;
898			}
899		}
900		if (!sname) {
901			xdr_free((xdrproc_t) xdr_gss_buffer_desc,
902			    (char *) &recv_tok);
903			sx_xunlock(&svc_rpc_gss_lock);
904			return (FALSE);
905		}
906	} else {
907		gr->gr_major = gss_accept_sec_context(
908			&gr->gr_minor,
909			&client->cl_ctx,
910			client->cl_sname->sn_cred,
911			&recv_tok,
912			GSS_C_NO_CHANNEL_BINDINGS,
913			&client->cl_cname,
914			&mech,
915			&gr->gr_token,
916			&ret_flags,
917			&cred_lifetime,
918			NULL);
919	}
920	sx_xunlock(&svc_rpc_gss_lock);
921
922	xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
923
924	/*
925	 * If we get an error from gss_accept_sec_context, send the
926	 * reply anyway so that the client gets a chance to see what
927	 * is wrong.
928	 */
929	if (gr->gr_major != GSS_S_COMPLETE &&
930	    gr->gr_major != GSS_S_CONTINUE_NEEDED) {
931		rpc_gss_log_status("accept_sec_context", client->cl_mech,
932		    gr->gr_major, gr->gr_minor);
933		client->cl_state = CLIENT_STALE;
934		return (TRUE);
935	}
936
937	gr->gr_handle.value = &client->cl_id;
938	gr->gr_handle.length = sizeof(client->cl_id);
939	gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
940
941	/* Save client info. */
942	client->cl_mech = mech;
943	client->cl_qop = GSS_C_QOP_DEFAULT;
944	client->cl_done_callback = FALSE;
945
946	if (gr->gr_major == GSS_S_COMPLETE) {
947		gss_buffer_desc	export_name;
948
949		/*
950		 * Change client expiration time to be near when the
951		 * client creds expire (or 24 hours if we can't figure
952		 * that out).
953		 */
954		if (cred_lifetime == GSS_C_INDEFINITE)
955			cred_lifetime = 24*60*60;
956
957		/*
958		 * Cap cred_lifetime if sysctl kern.rpc.gss.lifetime_max is set.
959		 */
960		if (svc_rpc_gss_lifetime_max > 0 && cred_lifetime >
961		    svc_rpc_gss_lifetime_max)
962			cred_lifetime = svc_rpc_gss_lifetime_max;
963
964		client->cl_expiration = time_uptime + cred_lifetime;
965
966		/*
967		 * Fill in cred details in the rawcred structure.
968		 */
969		client->cl_rawcred.version = RPCSEC_GSS_VERSION;
970		rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
971		maj_stat = gss_export_name(&min_stat, client->cl_cname,
972		    &export_name);
973		if (maj_stat != GSS_S_COMPLETE) {
974			rpc_gss_log_status("gss_export_name", client->cl_mech,
975			    maj_stat, min_stat);
976			return (FALSE);
977		}
978		client->cl_rawcred.client_principal =
979			mem_alloc(sizeof(*client->cl_rawcred.client_principal)
980			    + export_name.length);
981		client->cl_rawcred.client_principal->len = export_name.length;
982		memcpy(client->cl_rawcred.client_principal->name,
983		    export_name.value, export_name.length);
984		gss_release_buffer(&min_stat, &export_name);
985		client->cl_rawcred.svc_principal =
986			client->cl_sname->sn_principal;
987		client->cl_rawcred.service = gc->gc_svc;
988
989		/*
990		 * Use gss_pname_to_uid to map to unix creds. For
991		 * kerberos5, this uses krb5_aname_to_localname.
992		 */
993		svc_rpc_gss_build_ucred(client, client->cl_cname);
994		svc_rpc_gss_set_flavor(client);
995		gss_release_name(&min_stat, &client->cl_cname);
996
997#ifdef DEBUG
998		{
999			gss_buffer_desc mechname;
1000
1001			gss_oid_to_str(&min_stat, mech, &mechname);
1002
1003			rpc_gss_log_debug("accepted context for %s with "
1004			    "<mech %.*s, qop %d, svc %d>",
1005			    client->cl_rawcred.client_principal->name,
1006			    mechname.length, (char *)mechname.value,
1007			    client->cl_qop, client->cl_rawcred.service);
1008
1009			gss_release_buffer(&min_stat, &mechname);
1010		}
1011#endif /* DEBUG */
1012	}
1013	return (TRUE);
1014}
1015
1016static bool_t
1017svc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
1018    gss_qop_t *qop, rpc_gss_proc_t gcproc)
1019{
1020	struct opaque_auth	*oa;
1021	gss_buffer_desc		 rpcbuf, checksum;
1022	OM_uint32		 maj_stat, min_stat;
1023	gss_qop_t		 qop_state;
1024	int32_t			 rpchdr[128 / sizeof(int32_t)];
1025	int32_t			*buf;
1026
1027	rpc_gss_log_debug("in svc_rpc_gss_validate()");
1028
1029	memset(rpchdr, 0, sizeof(rpchdr));
1030
1031	/* Reconstruct RPC header for signing (from xdr_callmsg). */
1032	buf = rpchdr;
1033	IXDR_PUT_LONG(buf, msg->rm_xid);
1034	IXDR_PUT_ENUM(buf, msg->rm_direction);
1035	IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
1036	IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
1037	IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
1038	IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
1039	oa = &msg->rm_call.cb_cred;
1040	IXDR_PUT_ENUM(buf, oa->oa_flavor);
1041	IXDR_PUT_LONG(buf, oa->oa_length);
1042	if (oa->oa_length) {
1043		memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
1044		buf += RNDUP(oa->oa_length) / sizeof(int32_t);
1045	}
1046	rpcbuf.value = rpchdr;
1047	rpcbuf.length = (u_char *)buf - (u_char *)rpchdr;
1048
1049	checksum.value = msg->rm_call.cb_verf.oa_base;
1050	checksum.length = msg->rm_call.cb_verf.oa_length;
1051
1052	maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
1053				  &qop_state);
1054
1055	if (maj_stat != GSS_S_COMPLETE) {
1056		rpc_gss_log_status("gss_verify_mic", client->cl_mech,
1057		    maj_stat, min_stat);
1058		/*
1059		 * A bug in some versions of the Linux client generates a
1060		 * Destroy operation with a bogus encrypted checksum. Deleting
1061		 * the credential handle for that case causes the mount to fail.
1062		 * Since the checksum is bogus (gss_verify_mic() failed), it
1063		 * doesn't make sense to destroy the handle and not doing so
1064		 * fixes the Linux mount.
1065		 */
1066		if (gcproc != RPCSEC_GSS_DESTROY)
1067			client->cl_state = CLIENT_STALE;
1068		return (FALSE);
1069	}
1070
1071	*qop = qop_state;
1072	return (TRUE);
1073}
1074
1075static bool_t
1076svc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
1077    struct svc_req *rqst, u_int seq)
1078{
1079	gss_buffer_desc		signbuf;
1080	gss_buffer_desc		mic;
1081	OM_uint32		maj_stat, min_stat;
1082	uint32_t		nseq;
1083
1084	rpc_gss_log_debug("in svc_rpc_gss_nextverf()");
1085
1086	nseq = htonl(seq);
1087	signbuf.value = &nseq;
1088	signbuf.length = sizeof(nseq);
1089
1090	maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
1091	    &signbuf, &mic);
1092
1093	if (maj_stat != GSS_S_COMPLETE) {
1094		rpc_gss_log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
1095		client->cl_state = CLIENT_STALE;
1096		return (FALSE);
1097	}
1098
1099	KASSERT(mic.length <= MAX_AUTH_BYTES,
1100	    ("MIC too large for RPCSEC_GSS"));
1101
1102	rqst->rq_verf.oa_flavor = RPCSEC_GSS;
1103	rqst->rq_verf.oa_length = mic.length;
1104	bcopy(mic.value, rqst->rq_verf.oa_base, mic.length);
1105
1106	gss_release_buffer(&min_stat, &mic);
1107
1108	return (TRUE);
1109}
1110
1111static bool_t
1112svc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
1113{
1114	struct svc_rpc_gss_callback *scb;
1115	rpc_gss_lock_t	lock;
1116	void		*cookie;
1117	bool_t		cb_res;
1118	bool_t		result;
1119
1120	/*
1121	 * See if we have a callback for this guy.
1122	 */
1123	result = TRUE;
1124	SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
1125		if (scb->cb_callback.program == rqst->rq_prog
1126		    && scb->cb_callback.version == rqst->rq_vers) {
1127			/*
1128			 * This one matches. Call the callback and see
1129			 * if it wants to veto or something.
1130			 */
1131			lock.locked = FALSE;
1132			lock.raw_cred = &client->cl_rawcred;
1133			cb_res = scb->cb_callback.callback(rqst,
1134			    client->cl_creds,
1135			    client->cl_ctx,
1136			    &lock,
1137			    &cookie);
1138
1139			if (!cb_res) {
1140				client->cl_state = CLIENT_STALE;
1141				result = FALSE;
1142				break;
1143			}
1144
1145			/*
1146			 * The callback accepted the connection - it
1147			 * is responsible for freeing client->cl_creds
1148			 * now.
1149			 */
1150			client->cl_creds = GSS_C_NO_CREDENTIAL;
1151			client->cl_locked = lock.locked;
1152			client->cl_cookie = cookie;
1153			return (TRUE);
1154		}
1155	}
1156
1157	/*
1158	 * Either no callback exists for this program/version or one
1159	 * of the callbacks rejected the connection. We just need to
1160	 * clean up the delegated client creds, if any.
1161	 */
1162	if (client->cl_creds) {
1163		OM_uint32 min_ver;
1164		gss_release_cred(&min_ver, &client->cl_creds);
1165	}
1166	return (result);
1167}
1168
1169static bool_t
1170svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
1171{
1172	u_int32_t offset;
1173	int word, bit;
1174	bool_t result;
1175
1176	sx_xlock(&client->cl_lock);
1177	if (seq <= client->cl_seqlast) {
1178		/*
1179		 * The request sequence number is less than
1180		 * the largest we have seen so far. If it is
1181		 * outside the window or if we have seen a
1182		 * request with this sequence before, silently
1183		 * discard it.
1184		 */
1185		offset = client->cl_seqlast - seq;
1186		if (offset >= SVC_RPC_GSS_SEQWINDOW) {
1187			result = FALSE;
1188			goto out;
1189		}
1190		word = offset / 32;
1191		bit = offset % 32;
1192		if (client->cl_seqmask[word] & (1 << bit)) {
1193			result = FALSE;
1194			goto out;
1195		}
1196	}
1197
1198	result = TRUE;
1199out:
1200	sx_xunlock(&client->cl_lock);
1201	return (result);
1202}
1203
1204static void
1205svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
1206{
1207	int offset, i, word, bit;
1208	uint32_t carry, newcarry;
1209
1210	sx_xlock(&client->cl_lock);
1211	if (seq > client->cl_seqlast) {
1212		/*
1213		 * This request has a sequence number greater
1214		 * than any we have seen so far. Advance the
1215		 * seq window and set bit zero of the window
1216		 * (which corresponds to the new sequence
1217		 * number)
1218		 */
1219		offset = seq - client->cl_seqlast;
1220		while (offset > 32) {
1221			for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
1222			     i > 0; i--) {
1223				client->cl_seqmask[i] = client->cl_seqmask[i-1];
1224			}
1225			client->cl_seqmask[0] = 0;
1226			offset -= 32;
1227		}
1228		carry = 0;
1229		for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
1230			newcarry = client->cl_seqmask[i] >> (32 - offset);
1231			client->cl_seqmask[i] =
1232				(client->cl_seqmask[i] << offset) | carry;
1233			carry = newcarry;
1234		}
1235		client->cl_seqmask[0] |= 1;
1236		client->cl_seqlast = seq;
1237	} else {
1238		offset = client->cl_seqlast - seq;
1239		word = offset / 32;
1240		bit = offset % 32;
1241		client->cl_seqmask[word] |= (1 << bit);
1242	}
1243	sx_xunlock(&client->cl_lock);
1244}
1245
1246enum auth_stat
1247svc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
1248
1249{
1250	OM_uint32		 min_stat;
1251	XDR	 		 xdrs;
1252	struct svc_rpc_gss_cookedcred *cc;
1253	struct svc_rpc_gss_client *client;
1254	struct rpc_gss_cred	 gc;
1255	struct rpc_gss_init_res	 gr;
1256	gss_qop_t		 qop;
1257	int			 call_stat;
1258	enum auth_stat		 result;
1259
1260	rpc_gss_log_debug("in svc_rpc_gss()");
1261
1262	/* Garbage collect old clients. */
1263	svc_rpc_gss_timeout_clients();
1264
1265	/* Initialize reply. */
1266	rqst->rq_verf = _null_auth;
1267
1268	/* Deserialize client credentials. */
1269	if (rqst->rq_cred.oa_length <= 0)
1270		return (AUTH_BADCRED);
1271
1272	memset(&gc, 0, sizeof(gc));
1273
1274	xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
1275	    rqst->rq_cred.oa_length, XDR_DECODE);
1276
1277	if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
1278		XDR_DESTROY(&xdrs);
1279		return (AUTH_BADCRED);
1280	}
1281	XDR_DESTROY(&xdrs);
1282
1283	client = NULL;
1284
1285	/* Check version. */
1286	if (gc.gc_version != RPCSEC_GSS_VERSION) {
1287		result = AUTH_BADCRED;
1288		goto out;
1289	}
1290
1291	/* Check the proc and find the client (or create it) */
1292	if (gc.gc_proc == RPCSEC_GSS_INIT) {
1293		if (gc.gc_handle.length != 0) {
1294			result = AUTH_BADCRED;
1295			goto out;
1296		}
1297		client = svc_rpc_gss_create_client();
1298	} else {
1299		struct svc_rpc_gss_clientid *p;
1300		if (gc.gc_handle.length != sizeof(*p)) {
1301			result = AUTH_BADCRED;
1302			goto out;
1303		}
1304		p = gc.gc_handle.value;
1305		client = svc_rpc_gss_find_client(p);
1306		if (!client) {
1307			/*
1308			 * Can't find the client - we may have
1309			 * destroyed it - tell the other side to
1310			 * re-authenticate.
1311			 */
1312			result = RPCSEC_GSS_CREDPROBLEM;
1313			goto out;
1314		}
1315	}
1316	cc = rqst->rq_clntcred;
1317	cc->cc_client = client;
1318	cc->cc_service = gc.gc_svc;
1319	cc->cc_seq = gc.gc_seq;
1320
1321	/*
1322	 * The service and sequence number must be ignored for
1323	 * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
1324	 */
1325	if (gc.gc_proc != RPCSEC_GSS_INIT
1326	    && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
1327		/*
1328		 * Check for sequence number overflow.
1329		 */
1330		if (gc.gc_seq >= MAXSEQ) {
1331			result = RPCSEC_GSS_CTXPROBLEM;
1332			goto out;
1333		}
1334
1335		/*
1336		 * Check for valid service.
1337		 */
1338		if (gc.gc_svc != rpc_gss_svc_none &&
1339		    gc.gc_svc != rpc_gss_svc_integrity &&
1340		    gc.gc_svc != rpc_gss_svc_privacy) {
1341			result = AUTH_BADCRED;
1342			goto out;
1343		}
1344	}
1345
1346	/* Handle RPCSEC_GSS control procedure. */
1347	switch (gc.gc_proc) {
1348
1349	case RPCSEC_GSS_INIT:
1350	case RPCSEC_GSS_CONTINUE_INIT:
1351		if (rqst->rq_proc != NULLPROC) {
1352			result = AUTH_REJECTEDCRED;
1353			break;
1354		}
1355
1356		memset(&gr, 0, sizeof(gr));
1357		if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
1358			result = AUTH_REJECTEDCRED;
1359			break;
1360		}
1361
1362		if (gr.gr_major == GSS_S_COMPLETE) {
1363			/*
1364			 * We borrow the space for the call verf to
1365			 * pack our reply verf.
1366			 */
1367			rqst->rq_verf = msg->rm_call.cb_verf;
1368			if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
1369				result = AUTH_REJECTEDCRED;
1370				break;
1371			}
1372		} else {
1373			rqst->rq_verf = _null_auth;
1374		}
1375
1376		call_stat = svc_sendreply(rqst,
1377		    (xdrproc_t) xdr_rpc_gss_init_res,
1378		    (caddr_t) &gr);
1379
1380		gss_release_buffer(&min_stat, &gr.gr_token);
1381
1382		if (!call_stat) {
1383			result = AUTH_FAILED;
1384			break;
1385		}
1386
1387		if (gr.gr_major == GSS_S_COMPLETE)
1388			client->cl_state = CLIENT_ESTABLISHED;
1389
1390		result = RPCSEC_GSS_NODISPATCH;
1391		break;
1392
1393	case RPCSEC_GSS_DATA:
1394	case RPCSEC_GSS_DESTROY:
1395		if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
1396			result = RPCSEC_GSS_NODISPATCH;
1397			break;
1398		}
1399
1400		if (!svc_rpc_gss_validate(client, msg, &qop, gc.gc_proc)) {
1401			result = RPCSEC_GSS_CREDPROBLEM;
1402			break;
1403		}
1404
1405		/*
1406		 * We borrow the space for the call verf to pack our
1407		 * reply verf.
1408		 */
1409		rqst->rq_verf = msg->rm_call.cb_verf;
1410		if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
1411			result = RPCSEC_GSS_CTXPROBLEM;
1412			break;
1413		}
1414
1415		svc_rpc_gss_update_seq(client, gc.gc_seq);
1416
1417		/*
1418		 * Change the SVCAUTH ops on the request to point at
1419		 * our own code so that we can unwrap the arguments
1420		 * and wrap the result. The caller will re-set this on
1421		 * every request to point to a set of null wrap/unwrap
1422		 * methods. Acquire an extra reference to the client
1423		 * which will be released by svc_rpc_gss_release()
1424		 * after the request has finished processing.
1425		 */
1426		refcount_acquire(&client->cl_refs);
1427		rqst->rq_auth.svc_ah_ops = &svc_auth_gss_ops;
1428		rqst->rq_auth.svc_ah_private = cc;
1429
1430		if (gc.gc_proc == RPCSEC_GSS_DATA) {
1431			/*
1432			 * We might be ready to do a callback to the server to
1433			 * see if it wants to accept/reject the connection.
1434			 */
1435			sx_xlock(&client->cl_lock);
1436			if (!client->cl_done_callback) {
1437				client->cl_done_callback = TRUE;
1438				client->cl_qop = qop;
1439				client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1440					client->cl_rawcred.mechanism, qop);
1441				if (!svc_rpc_gss_callback(client, rqst)) {
1442					result = AUTH_REJECTEDCRED;
1443					sx_xunlock(&client->cl_lock);
1444					break;
1445				}
1446			}
1447			sx_xunlock(&client->cl_lock);
1448
1449			/*
1450			 * If the server has locked this client to a
1451			 * particular service+qop pair, enforce that
1452			 * restriction now.
1453			 */
1454			if (client->cl_locked) {
1455				if (client->cl_rawcred.service != gc.gc_svc) {
1456					result = AUTH_FAILED;
1457					break;
1458				} else if (client->cl_qop != qop) {
1459					result = AUTH_BADVERF;
1460					break;
1461				}
1462			}
1463
1464			/*
1465			 * If the qop changed, look up the new qop
1466			 * name for rawcred.
1467			 */
1468			if (client->cl_qop != qop) {
1469				client->cl_qop = qop;
1470				client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1471					client->cl_rawcred.mechanism, qop);
1472			}
1473
1474			/*
1475			 * Make sure we use the right service value
1476			 * for unwrap/wrap.
1477			 */
1478			if (client->cl_rawcred.service != gc.gc_svc) {
1479				client->cl_rawcred.service = gc.gc_svc;
1480				svc_rpc_gss_set_flavor(client);
1481			}
1482
1483			result = AUTH_OK;
1484		} else {
1485			if (rqst->rq_proc != NULLPROC) {
1486				result = AUTH_REJECTEDCRED;
1487				break;
1488			}
1489
1490			call_stat = svc_sendreply(rqst,
1491			    (xdrproc_t) xdr_void, (caddr_t) NULL);
1492
1493			if (!call_stat) {
1494				result = AUTH_FAILED;
1495				break;
1496			}
1497
1498			svc_rpc_gss_forget_client(client);
1499
1500			result = RPCSEC_GSS_NODISPATCH;
1501			break;
1502		}
1503		break;
1504
1505	default:
1506		result = AUTH_BADCRED;
1507		break;
1508	}
1509out:
1510	if (client)
1511		svc_rpc_gss_release_client(client);
1512
1513	xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
1514	return (result);
1515}
1516
1517static bool_t
1518svc_rpc_gss_wrap(SVCAUTH *auth, struct mbuf **mp)
1519{
1520	struct svc_rpc_gss_cookedcred *cc;
1521	struct svc_rpc_gss_client *client;
1522
1523	rpc_gss_log_debug("in svc_rpc_gss_wrap()");
1524
1525	cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1526	client = cc->cc_client;
1527	if (client->cl_state != CLIENT_ESTABLISHED
1528	    || cc->cc_service == rpc_gss_svc_none || *mp == NULL) {
1529		return (TRUE);
1530	}
1531
1532	return (xdr_rpc_gss_wrap_data(mp,
1533		client->cl_ctx, client->cl_qop,
1534		cc->cc_service, cc->cc_seq));
1535}
1536
1537static bool_t
1538svc_rpc_gss_unwrap(SVCAUTH *auth, struct mbuf **mp)
1539{
1540	struct svc_rpc_gss_cookedcred *cc;
1541	struct svc_rpc_gss_client *client;
1542
1543	rpc_gss_log_debug("in svc_rpc_gss_unwrap()");
1544
1545	cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1546	client = cc->cc_client;
1547	if (client->cl_state != CLIENT_ESTABLISHED
1548	    || cc->cc_service == rpc_gss_svc_none) {
1549		return (TRUE);
1550	}
1551
1552	return (xdr_rpc_gss_unwrap_data(mp,
1553		client->cl_ctx, client->cl_qop,
1554		cc->cc_service, cc->cc_seq));
1555}
1556
1557static void
1558svc_rpc_gss_release(SVCAUTH *auth)
1559{
1560	struct svc_rpc_gss_cookedcred *cc;
1561	struct svc_rpc_gss_client *client;
1562
1563	rpc_gss_log_debug("in svc_rpc_gss_release()");
1564
1565	cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1566	client = cc->cc_client;
1567	svc_rpc_gss_release_client(client);
1568}
1569