194380Sdfr/*-
294380Sdfr * Copyright (c) 2008 Doug Rabson
394380Sdfr * All rights reserved.
494380Sdfr *
594380Sdfr * Redistribution and use in source and binary forms, with or without
694380Sdfr * modification, are permitted provided that the following conditions
794380Sdfr * are met:
8119332Speter * 1. Redistributions of source code must retain the above copyright
9119332Speter *    notice, this list of conditions and the following disclaimer.
1094380Sdfr * 2. Redistributions in binary form must reproduce the above copyright
1194380Sdfr *    notice, this list of conditions and the following disclaimer in the
1294380Sdfr *    documentation and/or other materials provided with the distribution.
13177613Sjhb *
14227776Slstewart * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15164199Sru * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16113989Sjhb * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17255658Sjilles * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18113989Sjhb * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19161330Sjhb * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20161330Sjhb * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2194380Sdfr * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2294380Sdfr * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2394380Sdfr * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2494380Sdfr * SUCH DAMAGE.
2594380Sdfr *
2694380Sdfr *	$FreeBSD$
2794380Sdfr */
2894380Sdfr/*
2994380Sdfr  svc_rpcsec_gss.c
3094380Sdfr
3194380Sdfr  Copyright (c) 2000 The Regents of the University of Michigan.
3294380Sdfr  All rights reserved.
3394380Sdfr
3494380Sdfr  Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
3594380Sdfr  All rights reserved, all wrongs reversed.
36232449Sjmallett
37205014Snwhitehorn  Redistribution and use in source and binary forms, with or without
38205014Snwhitehorn  modification, are permitted provided that the following conditions
39119332Speter  are met:
4094380Sdfr
4194380Sdfr  1. Redistributions of source code must retain the above copyright
4294380Sdfr     notice, this list of conditions and the following disclaimer.
43100385Speter  2. Redistributions in binary form must reproduce the above copyright
4494380Sdfr     notice, this list of conditions and the following disclaimer in the
45151360Sps     documentation and/or other materials provided with the distribution.
46151360Sps  3. Neither the name of the University nor the names of its
47151360Sps     contributors may be used to endorse or promote products derived
48151360Sps     from this software without specific prior written permission.
49151360Sps
50151360Sps  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
51151360Sps  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
52151360Sps  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
53151360Sps  DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
54151360Sps  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
55151360Sps  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
56151360Sps  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
57236027Sed  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
58236027Sed  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
59151360Sps  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
60236027Sed  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
61236027Sed
62151360Sps  $Id: svc_auth_gss.c,v 1.27 2002/01/15 15:43:00 andros Exp $
63151721Speter */
64151583Sps
65151583Sps#include <stdio.h>
66119332Speter#include <stdlib.h>
67100385Speter#include <string.h>
68100385Speter#include <pwd.h>
6994380Sdfr#include <grp.h>
70183271Sobrien#include <errno.h>
71183271Sobrien#include <unistd.h>
72183271Sobrien#include <sys/queue.h>
73183271Sobrien#include <rpc/rpc.h>
74183271Sobrien#include <rpc/rpcsec_gss.h>
75119332Speter#include "rpcsec_gss_int.h"
7694380Sdfr
77236027Sedstatic bool_t	svc_rpc_gss_initialised = FALSE;
78236027Sed
7994380Sdfrstatic bool_t   svc_rpc_gss_wrap(SVCAUTH *, XDR *, xdrproc_t, caddr_t);
80226349Smarcelstatic bool_t   svc_rpc_gss_unwrap(SVCAUTH *, XDR *, xdrproc_t, caddr_t);
81226349Smarcelstatic enum auth_stat svc_rpc_gss(struct svc_req *, struct rpc_msg *);
82226349Smarcel
83226349Smarcelstatic struct svc_auth_ops svc_auth_gss_ops = {
84226349Smarcel	svc_rpc_gss_wrap,
85119332Speter	svc_rpc_gss_unwrap,
8694380Sdfr};
87100385Speter
88100385Speterstruct svc_rpc_gss_callback {
8994380Sdfr	SLIST_ENTRY(svc_rpc_gss_callback) cb_link;
90125171Speter	rpc_gss_callback_t	cb_callback;
91125171Speter};
92125171Speterstatic SLIST_HEAD(svc_rpc_gss_callback_list, svc_rpc_gss_callback)
93125171Speter	svc_rpc_gss_callbacks = SLIST_HEAD_INITIALIZER(svc_rpc_gss_callbacks);
94271011Skib
95271011Skibstruct svc_rpc_gss_svc_name {
96271011Skib	SLIST_ENTRY(svc_rpc_gss_svc_name) sn_link;
97271011Skib	char			*sn_principal;
98271011Skib	gss_OID			sn_mech;
99119332Speter	u_int			sn_req_time;
10094380Sdfr	gss_cred_id_t		sn_cred;
10194380Sdfr	u_int			sn_program;
10294380Sdfr	u_int			sn_version;
10394380Sdfr};
104100385Speterstatic SLIST_HEAD(svc_rpc_gss_svc_name_list, svc_rpc_gss_svc_name)
10594380Sdfr	svc_rpc_gss_svc_names = SLIST_HEAD_INITIALIZER(svc_rpc_gss_svc_names);
106119332Speter
107100385Speterenum svc_rpc_gss_client_state {
10894380Sdfr	CLIENT_NEW,				/* still authenticating */
10994380Sdfr	CLIENT_ESTABLISHED,			/* context established */
110119332Speter	CLIENT_STALE				/* garbage to collect */
11194380Sdfr};
112100385Speter
11394380Sdfr#define SVC_RPC_GSS_SEQWINDOW	128
114119332Speter
11594380Sdfrstruct svc_rpc_gss_client {
116100385Speter	TAILQ_ENTRY(svc_rpc_gss_client) cl_link;
11794380Sdfr	TAILQ_ENTRY(svc_rpc_gss_client) cl_alllink;
11894380Sdfr	uint32_t		cl_id;
119119332Speter	time_t			cl_expiration;	/* when to gc */
12094380Sdfr	enum svc_rpc_gss_client_state cl_state;	/* client state */
121100385Speter	bool_t			cl_locked;	/* fixed service+qop */
12294380Sdfr	gss_ctx_id_t		cl_ctx;		/* context id */
12394380Sdfr	gss_cred_id_t		cl_creds;	/* delegated creds */
124119332Speter	gss_name_t		cl_cname;	/* client name */
125100385Speter	struct svc_rpc_gss_svc_name *cl_sname;	/* server name used */
12694380Sdfr	rpc_gss_rawcred_t	cl_rawcred;	/* raw credentials */
12794380Sdfr	rpc_gss_ucred_t		cl_ucred;	/* unix-style credentials */
128119332Speter	bool_t			cl_done_callback; /* TRUE after call */
12994380Sdfr	void			*cl_cookie;	/* user cookie from callback */
130100385Speter	gid_t			cl_gid_storage[NGRPS];
13194380Sdfr	gss_OID			cl_mech;	/* mechanism */
132119332Speter	gss_qop_t		cl_qop;		/* quality of protection */
133100385Speter	u_int			cl_seq;		/* current sequence number */
134100385Speter	u_int			cl_win;		/* sequence window size */
13594380Sdfr	u_int			cl_seqlast;	/* sequence window origin */
136190622Skib	uint32_t		cl_seqmask[SVC_RPC_GSS_SEQWINDOW/32]; /* bitmask of seqnums */
137190622Skib	gss_buffer_desc		cl_verf;	/* buffer for verf checksum */
138190622Skib};
139190622SkibTAILQ_HEAD(svc_rpc_gss_client_list, svc_rpc_gss_client);
140119332Speter
14194380Sdfr#define CLIENT_HASH_SIZE	256
14294380Sdfr#define CLIENT_MAX		128
14394380Sdfrstatic struct svc_rpc_gss_client_list svc_rpc_gss_client_hash[CLIENT_HASH_SIZE];
14494380Sdfrstatic struct svc_rpc_gss_client_list svc_rpc_gss_clients;
14594380Sdfrstatic size_t svc_rpc_gss_client_count;
14694380Sdfrstatic uint32_t svc_rpc_gss_next_clientid = 1;
147119332Speter
14894380Sdfr#ifdef __GNUC__
14994380Sdfrstatic void svc_rpc_gss_init(void) __attribute__ ((constructor));
15094380Sdfr#endif
15194380Sdfr
15294380Sdfrstatic void
15394380Sdfrsvc_rpc_gss_init(void)
15494380Sdfr{
155119332Speter	int i;
156157286Sps
157157286Sps	if (!svc_rpc_gss_initialised) {
158157286Sps		for (i = 0; i < CLIENT_HASH_SIZE; i++)
159157286Sps			TAILQ_INIT(&svc_rpc_gss_client_hash[i]);
16094380Sdfr		TAILQ_INIT(&svc_rpc_gss_clients);
161119332Speter		svc_auth_reg(RPCSEC_GSS, svc_rpc_gss);
16294380Sdfr		svc_rpc_gss_initialised = TRUE;
163100385Speter	}
16494380Sdfr}
165119332Speter
16694380Sdfrbool_t
167100385Speterrpc_gss_set_callback(rpc_gss_callback_t *cb)
16894380Sdfr{
169119332Speter	struct svc_rpc_gss_callback *scb;
17094380Sdfr
171100385Speter	scb = mem_alloc(sizeof(struct svc_rpc_gss_callback));
17294380Sdfr	if (!scb) {
173184184Sjhb		_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
174184184Sjhb		return (FALSE);
175184184Sjhb	}
176184184Sjhb	scb->cb_callback = *cb;
177184184Sjhb	SLIST_INSERT_HEAD(&svc_rpc_gss_callbacks, scb, cb_link);
178184184Sjhb
179119332Speter	return (TRUE);
18094380Sdfr}
18194380Sdfr
18294380Sdfrbool_t
183236027Sedrpc_gss_set_svc_name(const char *principal, const char *mechanism,
18494380Sdfr    u_int req_time, u_int program, u_int version)
185236027Sed{
18694380Sdfr	OM_uint32		maj_stat, min_stat;
187154596Sambrisko	struct svc_rpc_gss_svc_name *sname;
188154596Sambrisko	gss_buffer_desc		namebuf;
189154596Sambrisko	gss_name_t		name;
190154596Sambrisko	gss_OID			mech_oid;
191165406Sjkim	gss_OID_set_desc	oid_set;
192165406Sjkim	gss_cred_id_t		cred;
193165406Sjkim
194165406Sjkim	svc_rpc_gss_init();
195165406Sjkim
196165406Sjkim	if (!rpc_gss_mech_to_oid(mechanism, &mech_oid))
197165406Sjkim		return (FALSE);
198165406Sjkim	oid_set.count = 1;
199165406Sjkim	oid_set.elements = mech_oid;
200165406Sjkim
201165406Sjkim	namebuf.value = (void *)(intptr_t) principal;
202165406Sjkim	namebuf.length = strlen(principal);
203165406Sjkim
204151358Sps	maj_stat = gss_import_name(&min_stat, &namebuf,
205151358Sps				   GSS_C_NT_HOSTBASED_SERVICE, &name);
206151358Sps	if (maj_stat != GSS_S_COMPLETE)
207151358Sps		return (FALSE);
208151358Sps
209151358Sps	maj_stat = gss_acquire_cred(&min_stat, name,
210151358Sps	    req_time, &oid_set, GSS_C_ACCEPT, &cred, NULL, NULL);
211151358Sps	if (maj_stat != GSS_S_COMPLETE)
212151358Sps		return (FALSE);
213151358Sps
214151358Sps	gss_release_name(&min_stat, &name);
215151358Sps
216253531Skib	sname = malloc(sizeof(struct svc_rpc_gss_svc_name));
217253531Skib	if (!sname)
218253531Skib		return (FALSE);
219253531Skib	sname->sn_principal = strdup(principal);
220253531Skib	sname->sn_mech = mech_oid;
221253531Skib	sname->sn_req_time = req_time;
222253531Skib	sname->sn_cred = cred;
223253531Skib	sname->sn_program = program;
224253531Skib	sname->sn_version = version;
225253531Skib	SLIST_INSERT_HEAD(&svc_rpc_gss_svc_names, sname, sn_link);
226253531Skib
227253531Skib	return (TRUE);
228253531Skib}
229253531Skib
230253531Skibbool_t
231140481Spsrpc_gss_get_principal_name(rpc_gss_principal_t *principal,
232151356Sps    const char *mech, const char *name, const char *node, const char *domain)
233151356Sps{
234140481Sps	OM_uint32		maj_stat, min_stat;
235253495Skib	gss_OID			mech_oid;
236253495Skib	size_t			namelen;
237253495Skib	gss_buffer_desc		buf;
238253495Skib	gss_name_t		gss_name, gss_mech_name;
239253495Skib	rpc_gss_principal_t	result;
240253495Skib
241185879Sjhb	svc_rpc_gss_init();
242185879Sjhb
243185879Sjhb	if (!rpc_gss_mech_to_oid(mech, &mech_oid))
244185879Sjhb		return (FALSE);
245185879Sjhb
246185879Sjhb	/*
247185879Sjhb	 * Construct a gss_buffer containing the full name formatted
248185879Sjhb	 * as "name/node@domain" where node and domain are optional.
249185879Sjhb	 */
250185879Sjhb	namelen = strlen(name);
251253531Skib	if (node) {
252185879Sjhb		namelen += strlen(node) + 1;
253154587Sambrisko	}
254154587Sambrisko	if (domain) {
255154587Sambrisko		namelen += strlen(domain) + 1;
256154587Sambrisko	}
257147814Sjhb
258147814Sjhb	buf.value = mem_alloc(namelen);
259147814Sjhb	buf.length = namelen;
260147814Sjhb	strcpy((char *) buf.value, name);
261236027Sed	if (node) {
262236027Sed		strcat((char *) buf.value, "/");
263147814Sjhb		strcat((char *) buf.value, node);
264147814Sjhb	}
265147814Sjhb	if (domain) {
266147814Sjhb		strcat((char *) buf.value, "@");
267147814Sjhb		strcat((char *) buf.value, domain);
268236027Sed	}
269236027Sed
270147814Sjhb	/*
271140482Sps	 * Convert that to a gss_name_t and then convert that to a
272140482Sps	 * mechanism name in the selected mechanism.
273140482Sps	 */
274140482Sps	maj_stat = gss_import_name(&min_stat, &buf,
275220159Skib	    GSS_C_NT_USER_NAME, &gss_name);
276220159Skib	mem_free(buf.value, buf.length);
277220159Skib	if (maj_stat != GSS_S_COMPLETE) {
278220159Skib		log_status("gss_import_name", mech_oid, maj_stat, min_stat);
279185879Sjhb		return (FALSE);
280185879Sjhb	}
281185879Sjhb	maj_stat = gss_canonicalize_name(&min_stat, gss_name, mech_oid,
282185879Sjhb	    &gss_mech_name);
283185879Sjhb	if (maj_stat != GSS_S_COMPLETE) {
284185879Sjhb		log_status("gss_canonicalize_name", mech_oid, maj_stat,
285185879Sjhb		    min_stat);
286185879Sjhb		gss_release_name(&min_stat, &gss_name);
287185879Sjhb		return (FALSE);
288185879Sjhb	}
289185879Sjhb	gss_release_name(&min_stat, &gss_name);
290185879Sjhb
291185879Sjhb	/*
292185879Sjhb	 * Export the mechanism name and use that to construct the
293185879Sjhb	 * rpc_gss_principal_t result.
294185879Sjhb	 */
295185879Sjhb	maj_stat = gss_export_name(&min_stat, gss_mech_name, &buf);
296185879Sjhb	if (maj_stat != GSS_S_COMPLETE) {
297185879Sjhb		log_status("gss_export_name", mech_oid, maj_stat, min_stat);
298185879Sjhb		gss_release_name(&min_stat, &gss_mech_name);
299185879Sjhb		return (FALSE);
300185879Sjhb	}
301185879Sjhb	gss_release_name(&min_stat, &gss_mech_name);
302185879Sjhb
303185879Sjhb	result = mem_alloc(sizeof(int) + buf.length);
304185879Sjhb	if (!result) {
305185879Sjhb		gss_release_buffer(&min_stat, &buf);
306185436Sbz		return (FALSE);
307185436Sbz	}
308185436Sbz	result->len = buf.length;
309163020Sdavidxu	memcpy(result->name, buf.value, buf.length);
310163020Sdavidxu	gss_release_buffer(&min_stat, &buf);
311163020Sdavidxu
312163020Sdavidxu	*principal = result;
313163020Sdavidxu	return (TRUE);
314163020Sdavidxu}
315163020Sdavidxu
316163020Sdavidxubool_t
317163020Sdavidxurpc_gss_getcred(struct svc_req *req, rpc_gss_rawcred_t **rcred,
318185879Sjhb    rpc_gss_ucred_t **ucred, void **cookie)
319185879Sjhb{
320185879Sjhb	struct svc_rpc_gss_client *client;
321185879Sjhb
322119332Speter	if (req->rq_cred.oa_flavor != RPCSEC_GSS)
323114988Speter		return (FALSE);
324142874Sps
325114988Speter	client = req->rq_clntcred;
326142874Sps	if (rcred)
327114988Speter		*rcred = &client->cl_rawcred;
328142874Sps	if (ucred)
329104739Speter		*ucred = &client->cl_ucred;
330183189Sobrien	if (cookie)
331183189Sobrien		*cookie = client->cl_cookie;
332183189Sobrien	return (TRUE);
333183189Sobrien}
334183189Sobrien
335119332Speterint
33694380Sdfrrpc_gss_svc_max_data_length(struct svc_req *req, int max_tp_unit_len)
33794380Sdfr{
338236027Sed	struct svc_rpc_gss_client *client = req->rq_clntcred;
339236027Sed	int			want_conf;
34094380Sdfr	OM_uint32		max;
341156115Sps	OM_uint32		maj_stat, min_stat;
34294380Sdfr	int			result;
34394380Sdfr
34494380Sdfr	switch (client->cl_rawcred.service) {
345205328Skib	case rpc_gss_svc_none:
346205328Skib		return (max_tp_unit_len);
347205328Skib		break;
348205328Skib
349205328Skib	case rpc_gss_svc_default:
350205328Skib	case rpc_gss_svc_integrity:
351205328Skib		want_conf = FALSE;
352205328Skib		break;
353205328Skib
354205328Skib	case rpc_gss_svc_privacy:
355205328Skib		want_conf = TRUE;
356119332Speter		break;
357114988Speter
358114988Speter	default:
359114988Speter		return (0);
360114988Speter	}
361119332Speter
362119332Speter	maj_stat = gss_wrap_size_limit(&min_stat, client->cl_ctx, want_conf,
363119194Speter	    client->cl_qop, max_tp_unit_len, &max);
364150632Speter
365150632Speter	if (maj_stat == GSS_S_COMPLETE) {
366150632Speter		result = (int) max;
367150632Speter		if (result < 0)
368150632Speter			result = 0;
369150632Speter		return (result);
370150632Speter	} else {
371150632Speter		log_status("gss_wrap_size_limit", client->cl_mech,
372150632Speter		    maj_stat, min_stat);
373150632Speter		return (0);
374163047Sdavidxu	}
375163047Sdavidxu}
376163047Sdavidxu
377163047Sdavidxustatic struct svc_rpc_gss_client *
378163047Sdavidxusvc_rpc_gss_find_client(uint32_t clientid)
379163047Sdavidxu{
380205328Skib	struct svc_rpc_gss_client *client;
381205328Skib	struct svc_rpc_gss_client_list *list;
382205328Skib
383205328Skib
384162552Sdavidxu	log_debug("in svc_rpc_gss_find_client(%d)", clientid);
385162552Sdavidxu
386162552Sdavidxu	list = &svc_rpc_gss_client_hash[clientid % CLIENT_HASH_SIZE];
387162537Sdavidxu	TAILQ_FOREACH(client, list, cl_link) {
388162537Sdavidxu		if (client->cl_id == clientid) {
389162537Sdavidxu			/*
390163451Sdavidxu			 * Move this client to the front of the LRU
391162537Sdavidxu			 * list.
392162537Sdavidxu			 */
393162537Sdavidxu			TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
394162552Sdavidxu			TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client,
395162552Sdavidxu			    cl_alllink);
396162552Sdavidxu			return client;
397162552Sdavidxu		}
398318323Sbrooks	}
399318323Sbrooks
400318323Sbrooks	return (NULL);
401318323Sbrooks}
402318323Sbrooks
403205328Skibstatic struct svc_rpc_gss_client *
404205328Skibsvc_rpc_gss_create_client(void)
405205328Skib{
406205328Skib	struct svc_rpc_gss_client *client;
407205328Skib	struct svc_rpc_gss_client_list *list;
408205328Skib
409205328Skib	log_debug("in svc_rpc_gss_create_client()");
410205328Skib
411205328Skib	client = mem_alloc(sizeof(struct svc_rpc_gss_client));
412205328Skib	memset(client, 0, sizeof(struct svc_rpc_gss_client));
413205328Skib	client->cl_id = svc_rpc_gss_next_clientid++;
414205328Skib	list = &svc_rpc_gss_client_hash[client->cl_id % CLIENT_HASH_SIZE];
415205328Skib	TAILQ_INSERT_HEAD(list, client, cl_link);
416205328Skib	TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client, cl_alllink);
417205328Skib
418205328Skib	/*
419205328Skib	 * Start the client off with a short expiration time. We will
420205328Skib	 * try to get a saner value from the client creds later.
421205328Skib	 */
422205328Skib	client->cl_state = CLIENT_NEW;
423205328Skib	client->cl_locked = FALSE;
424205328Skib	client->cl_expiration = time(0) + 5*60;
425205328Skib	svc_rpc_gss_client_count++;
426205328Skib
427205328Skib	return (client);
428253531Skib}
429253531Skib
430253531Skibstatic void
431253531Skibsvc_rpc_gss_destroy_client(struct svc_rpc_gss_client *client)
432185879Sjhb{
433185879Sjhb	struct svc_rpc_gss_client_list *list;
434185879Sjhb	OM_uint32 min_stat;
435185879Sjhb
436205014Snwhitehorn	log_debug("in svc_rpc_gss_destroy_client()");
437171214Speter
438171214Speter	if (client->cl_ctx)
439171214Speter		gss_delete_sec_context(&min_stat,
440171214Speter		    &client->cl_ctx, GSS_C_NO_BUFFER);
441205014Snwhitehorn
442236027Sed	if (client->cl_cname)
443236027Sed		gss_release_name(&min_stat, &client->cl_cname);
444171214Speter
445171214Speter	if (client->cl_rawcred.client_principal)
446171214Speter		mem_free(client->cl_rawcred.client_principal,
447171214Speter		    sizeof(*client->cl_rawcred.client_principal)
448171214Speter		    + client->cl_rawcred.client_principal->len);
449205014Snwhitehorn
450236027Sed	if (client->cl_verf.value)
451236027Sed		gss_release_buffer(&min_stat, &client->cl_verf);
452171214Speter
453171214Speter	list = &svc_rpc_gss_client_hash[client->cl_id % CLIENT_HASH_SIZE];
454171214Speter	TAILQ_REMOVE(list, client, cl_link);
455171214Speter	TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
456171214Speter	svc_rpc_gss_client_count--;
457171214Speter	mem_free(client, sizeof(*client));
458171214Speter}
459205014Snwhitehorn
460236027Sedstatic void
461236027Sedsvc_rpc_gss_timeout_clients(void)
462171214Speter{
463171214Speter	struct svc_rpc_gss_client *client;
464171214Speter	struct svc_rpc_gss_client *nclient;
465205014Snwhitehorn	time_t now = time(0);
466236027Sed
467236027Sed	log_debug("in svc_rpc_gss_timeout_clients()");
468171214Speter	/*
469171214Speter	 * First enforce the max client limit. We keep
470171214Speter	 * svc_rpc_gss_clients in LRU order.
471171214Speter	 */
472205014Snwhitehorn	while (svc_rpc_gss_client_count > CLIENT_MAX)
473236027Sed		svc_rpc_gss_destroy_client(TAILQ_LAST(&svc_rpc_gss_clients,
474236027Sed			    svc_rpc_gss_client_list));
475171214Speter	TAILQ_FOREACH_SAFE(client, &svc_rpc_gss_clients, cl_alllink, nclient) {
476171214Speter		if (client->cl_state == CLIENT_STALE
477171214Speter		    || now > client->cl_expiration) {
478205014Snwhitehorn			log_debug("expiring client %p", client);
479236027Sed			svc_rpc_gss_destroy_client(client);
480236027Sed		}
481171214Speter	}
482205014Snwhitehorn}
483205014Snwhitehorn
484205014Snwhitehorn#ifdef DEBUG
485205014Snwhitehorn/*
486205014Snwhitehorn * OID<->string routines.  These are uuuuugly.
487236027Sed */
488236027Sedstatic OM_uint32
489205014Snwhitehorngss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
490205014Snwhitehorn{
491205014Snwhitehorn	char		numstr[128];
492205014Snwhitehorn	unsigned long	number;
493205014Snwhitehorn	int		numshift;
494236027Sed	size_t		string_length;
495236027Sed	size_t		i;
496205014Snwhitehorn	unsigned char	*cp;
497205014Snwhitehorn	char		*bp;
498205014Snwhitehorn
499205014Snwhitehorn	/* Decoded according to krb5/gssapi_krb5.c */
500205014Snwhitehorn
501205014Snwhitehorn	/* First determine the size of the string */
502205014Snwhitehorn	string_length = 0;
503236027Sed	number = 0;
504236027Sed	numshift = 0;
505205014Snwhitehorn	cp = (unsigned char *) oid->elements;
506205014Snwhitehorn	number = (unsigned long) cp[0];
507205014Snwhitehorn	sprintf(numstr, "%ld ", number/40);
508236027Sed	string_length += strlen(numstr);
509236027Sed	sprintf(numstr, "%ld ", number%40);
510205014Snwhitehorn	string_length += strlen(numstr);
511205014Snwhitehorn	for (i=1; i<oid->length; i++) {
512205014Snwhitehorn		if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
513205014Snwhitehorn			number = (number << 7) | (cp[i] & 0x7f);
514236027Sed			numshift += 7;
515236027Sed		}
516205014Snwhitehorn		else {
517205014Snwhitehorn			*minor_status = 0;
518205014Snwhitehorn			return(GSS_S_FAILURE);
519236027Sed		}
520236027Sed		if ((cp[i] & 0x80) == 0) {
521205014Snwhitehorn			sprintf(numstr, "%ld ", number);
522205014Snwhitehorn			string_length += strlen(numstr);
523205014Snwhitehorn			number = 0;
524180434Sbrooks			numshift = 0;
525180434Sbrooks		}
526205014Snwhitehorn	}
527236027Sed	/*
528236027Sed	 * If we get here, we've calculated the length of "n n n ... n ".  Add 4
529180434Sbrooks	 * here for "{ " and "}\0".
530180434Sbrooks	 */
531205014Snwhitehorn	string_length += 4;
532205014Snwhitehorn	if ((bp = (char *) mem_alloc(string_length))) {
533205014Snwhitehorn		strcpy(bp, "{ ");
534236027Sed		number = (unsigned long) cp[0];
535236027Sed		sprintf(numstr, "%ld ", number/40);
536205014Snwhitehorn		strcat(bp, numstr);
537205014Snwhitehorn		sprintf(numstr, "%ld ", number%40);
538205014Snwhitehorn		strcat(bp, numstr);
539180434Sbrooks		number = 0;
540180434Sbrooks		cp = (unsigned char *) oid->elements;
541180434Sbrooks		for (i=1; i<oid->length; i++) {
542236027Sed			number = (number << 7) | (cp[i] & 0x7f);
543236027Sed			if ((cp[i] & 0x80) == 0) {
544180434Sbrooks				sprintf(numstr, "%ld ", number);
545180434Sbrooks				strcat(bp, numstr);
546180434Sbrooks				number = 0;
547180434Sbrooks			}
548180434Sbrooks		}
549236027Sed		strcat(bp, "}");
550236027Sed		oid_str->length = strlen(bp)+1;
551180434Sbrooks		oid_str->value = (void *) bp;
552180434Sbrooks		*minor_status = 0;
553180434Sbrooks		return(GSS_S_COMPLETE);
554180434Sbrooks	}
555180434Sbrooks	*minor_status = 0;
556180434Sbrooks	return(GSS_S_FAILURE);
557236027Sed}
558236027Sed#endif
559180434Sbrooks
560180434Sbrooksstatic void
561180434Sbrookssvc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
562177790Skib    const gss_name_t name)
563177790Skib{
564236027Sed	OM_uint32		maj_stat, min_stat;
565236027Sed	char			buf[128];
566177790Skib	uid_t			uid;
567177790Skib	struct passwd		pwd, *pw;
568177790Skib	rpc_gss_ucred_t		*uc = &client->cl_ucred;
569177790Skib
570177790Skib	uc->uid = 65534;
571177790Skib	uc->gid = 65534;
572177790Skib	uc->gidlen = 0;
573177790Skib	uc->gidlist = client->cl_gid_storage;
574177790Skib
575177790Skib	maj_stat = gss_pname_to_uid(&min_stat, name, client->cl_mech, &uid);
576177790Skib	if (maj_stat != GSS_S_COMPLETE)
577177790Skib		return;
578191675Sjamie
579191675Sjamie	getpwuid_r(uid, &pwd, buf, sizeof(buf), &pw);
580191675Sjamie	if (pw) {
581191675Sjamie		int len = NGRPS;
582191675Sjamie		uc->uid = pw->pw_uid;
583191675Sjamie		uc->gid = pw->pw_gid;
584191675Sjamie		uc->gidlist = client->cl_gid_storage;
585191675Sjamie		getgrouplist(pw->pw_name, pw->pw_gid, uc->gidlist, &len);
586191675Sjamie		uc->gidlen = len;
587191675Sjamie	}
588194919Sjhb}
589194919Sjhb
590194919Sjhbstatic bool_t
591194919Sjhbsvc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
592194919Sjhb			       struct svc_req *rqst,
593194919Sjhb			       struct rpc_gss_init_res *gr,
594194919Sjhb			       struct rpc_gss_cred *gc)
595194919Sjhb{
596194919Sjhb	gss_buffer_desc		recv_tok;
597194919Sjhb	gss_OID			mech;
598194919Sjhb	OM_uint32		maj_stat = 0, min_stat = 0, ret_flags;
599194919Sjhb	OM_uint32		cred_lifetime;
600194919Sjhb	struct svc_rpc_gss_svc_name *sname;
601194919Sjhb
602194919Sjhb	log_debug("in svc_rpc_gss_accept_context()");
603194919Sjhb
604255658Sjilles	/* Deserialize arguments. */
605255658Sjilles	memset(&recv_tok, 0, sizeof(recv_tok));
606255658Sjilles
607198512Skib	if (!svc_getargs(rqst->rq_xprt,
608198512Skib		(xdrproc_t) xdr_gss_buffer_desc,
609198512Skib		(caddr_t) &recv_tok)) {
610198512Skib		client->cl_state = CLIENT_STALE;
611198512Skib		return (FALSE);
612198512Skib	}
613198512Skib
614198512Skib	/*
615250854Skib	 * First time round, try all the server names we have until
616220792Smdf	 * one matches. Afterwards, stick with that one.
617220792Smdf	 */
618250854Skib	if (!client->cl_sname) {
619226365Sjhb		SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
620226365Sjhb			if (sname->sn_program == rqst->rq_prog
621226365Sjhb			    && sname->sn_version == rqst->rq_vers) {
622226365Sjhb				gr->gr_major = gss_accept_sec_context(
623220792Smdf					&gr->gr_minor,
624227071Sjhb					&client->cl_ctx,
625227071Sjhb					sname->sn_cred,
626250854Skib					&recv_tok,
627227071Sjhb					GSS_C_NO_CHANNEL_BINDINGS,
628227071Sjhb					&client->cl_cname,
629227071Sjhb					&mech,
630227071Sjhb					&gr->gr_token,
631227071Sjhb					&ret_flags,
632227071Sjhb					&cred_lifetime,
633242959Skib					&client->cl_creds);
634242959Skib				if (gr->gr_major == GSS_S_COMPLETE
635250854Skib				    || gr->gr_major == GSS_S_CONTINUE_NEEDED) {
636250854Skib					client->cl_sname = sname;
637250854Skib					break;
638242959Skib				}
639242959Skib				client->cl_sname = sname;
640242959Skib				break;
641242959Skib			}
642242959Skib		}
643250854Skib		if (!sname) {
644250854Skib			xdr_free((xdrproc_t) xdr_gss_buffer_desc,
645250854Skib			    (char *) &recv_tok);
646250854Skib			return (FALSE);
647250854Skib		}
648250854Skib	} else {
649250854Skib		gr->gr_major = gss_accept_sec_context(
650250854Skib			&gr->gr_minor,
651250854Skib			&client->cl_ctx,
652250854Skib			client->cl_sname->sn_cred,
653250854Skib			&recv_tok,
654250854Skib			GSS_C_NO_CHANNEL_BINDINGS,
655250854Skib			&client->cl_cname,
656250854Skib			&mech,
657250854Skib			&gr->gr_token,
658250854Skib			&ret_flags,
659250854Skib			&cred_lifetime,
660250854Skib			NULL);
661250854Skib	}
662250854Skib
663250854Skib	xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
664250854Skib
665250854Skib	/*
666250854Skib	 * If we get an error from gss_accept_sec_context, send the
667250854Skib	 * reply anyway so that the client gets a chance to see what
668250854Skib	 * is wrong.
669254482Spjd	 */
670254482Spjd	if (gr->gr_major != GSS_S_COMPLETE &&
671254482Spjd	    gr->gr_major != GSS_S_CONTINUE_NEEDED) {
672254482Spjd		log_status("accept_sec_context", client->cl_mech,
673254482Spjd		    gr->gr_major, gr->gr_minor);
674254482Spjd		client->cl_state = CLIENT_STALE;
675254482Spjd		return (TRUE);
676254482Spjd	}
677254482Spjd
678254482Spjd	gr->gr_handle.value = &client->cl_id;
679251527Sglebius	gr->gr_handle.length = sizeof(client->cl_id);
680251527Sglebius	gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
681251527Sglebius
682255709Sjhb	/* Save client info. */
683255709Sjhb	client->cl_mech = mech;
684255709Sjhb	client->cl_qop = GSS_C_QOP_DEFAULT;
685255709Sjhb	client->cl_seq = gc->gc_seq;
686255709Sjhb	client->cl_win = gr->gr_win;
687255709Sjhb	client->cl_done_callback = FALSE;
688255709Sjhb
689255709Sjhb	if (gr->gr_major == GSS_S_COMPLETE) {
690255709Sjhb		gss_buffer_desc	export_name;
691255709Sjhb
692255709Sjhb		/*
693255709Sjhb		 * Change client expiration time to be near when the
694255709Sjhb		 * client creds expire (or 24 hours if we can't figure
695255709Sjhb		 * that out).
696255709Sjhb		 */
697255709Sjhb		if (cred_lifetime == GSS_C_INDEFINITE)
698255709Sjhb			cred_lifetime = time(0) + 24*60*60;
699255709Sjhb
700275987Sdchagin		client->cl_expiration = time(0) + cred_lifetime;
701275987Sdchagin
702275987Sdchagin		/*
703275987Sdchagin		 * Fill in cred details in the rawcred structure.
704275987Sdchagin		 */
705275987Sdchagin		client->cl_rawcred.version = RPCSEC_GSS_VERSION;
706293475Sdchagin		rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
707293475Sdchagin		maj_stat = gss_export_name(&min_stat, client->cl_cname,
708293475Sdchagin		    &export_name);
709293475Sdchagin		if (maj_stat != GSS_S_COMPLETE) {
710293475Sdchagin			log_status("gss_export_name", client->cl_mech,
711293475Sdchagin			    maj_stat, min_stat);
712293475Sdchagin			return (FALSE);
713293475Sdchagin		}
714293475Sdchagin		client->cl_rawcred.client_principal =
715293475Sdchagin			mem_alloc(sizeof(*client->cl_rawcred.client_principal)
716232449Sjmallett			    + export_name.length);
717205014Snwhitehorn		client->cl_rawcred.client_principal->len = export_name.length;
718205014Snwhitehorn		memcpy(client->cl_rawcred.client_principal->name,
719119332Speter		    export_name.value, export_name.length);
720151360Sps		gss_release_buffer(&min_stat, &export_name);
721151360Sps		client->cl_rawcred.svc_principal =
722151360Sps			client->cl_sname->sn_principal;
723119332Speter		client->cl_rawcred.service = gc->gc_svc;
724183271Sobrien
725119332Speter		/*
726226349Smarcel		 * Use gss_pname_to_uid to map to unix creds. For
727119332Speter		 * kerberos5, this uses krb5_aname_to_localname.
728125171Speter		 */
729271011Skib		svc_rpc_gss_build_ucred(client, client->cl_cname);
730119332Speter		gss_release_name(&min_stat, &client->cl_cname);
731119332Speter
732119332Speter#ifdef DEBUG
733119332Speter		{
734119332Speter			gss_buffer_desc mechname;
735119332Speter
736119332Speter			gss_oid_to_str(&min_stat, mech, &mechname);
737119332Speter
738190622Skib			log_debug("accepted context for %s with "
739119332Speter			    "<mech %.*s, qop %d, svc %d>",
740119332Speter			    client->cl_rawcred.client_principal->name,
741119332Speter			    mechname.length, (char *)mechname.value,
742119332Speter			    client->cl_qop, client->rawcred.service);
743119332Speter
744119332Speter			gss_release_buffer(&min_stat, &mechname);
745184184Sjhb		}
746119332Speter#endif /* DEBUG */
747154596Sambrisko	}
748165406Sjkim	return (TRUE);
749165406Sjkim}
750151358Sps
751151358Spsstatic bool_t
752151358Spssvc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
753253531Skib	gss_qop_t *qop)
754253531Skib{
755253531Skib	struct opaque_auth	*oa;
756140481Sps	gss_buffer_desc		 rpcbuf, checksum;
757253495Skib	OM_uint32		 maj_stat, min_stat;
758185879Sjhb	gss_qop_t		 qop_state;
759185879Sjhb	int32_t			 rpchdr[128 / sizeof(int32_t)];
760185879Sjhb	int32_t			*buf;
761154587Sambrisko
762147814Sjhb	log_debug("in svc_rpc_gss_validate()");
763147814Sjhb
764140482Sps	memset(rpchdr, 0, sizeof(rpchdr));
765220159Skib
766185879Sjhb	/* Reconstruct RPC header for signing (from xdr_callmsg). */
767185879Sjhb	buf = rpchdr;
768185879Sjhb	IXDR_PUT_LONG(buf, msg->rm_xid);
769185879Sjhb	IXDR_PUT_ENUM(buf, msg->rm_direction);
770185879Sjhb	IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
771185879Sjhb	IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
772185879Sjhb	IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
773185436Sbz	IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
774163020Sdavidxu	oa = &msg->rm_call.cb_cred;
775163020Sdavidxu	IXDR_PUT_ENUM(buf, oa->oa_flavor);
776185879Sjhb	IXDR_PUT_LONG(buf, oa->oa_length);
777119332Speter	if (oa->oa_length) {
778183189Sobrien		memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
779119332Speter		buf += RNDUP(oa->oa_length) / sizeof(int32_t);
780205328Skib	}
781205328Skib	rpcbuf.value = rpchdr;
782119332Speter	rpcbuf.length = (u_char *)buf - (u_char *)rpchdr;
783119332Speter
784150632Speter	checksum.value = msg->rm_call.cb_verf.oa_base;
785150632Speter	checksum.length = msg->rm_call.cb_verf.oa_length;
786150632Speter
787163047Sdavidxu	maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
788163047Sdavidxu				  &qop_state);
789205328Skib
790162552Sdavidxu	if (maj_stat != GSS_S_COMPLETE) {
791162537Sdavidxu		log_status("gss_verify_mic", client->cl_mech,
792162552Sdavidxu		    maj_stat, min_stat);
793318323Sbrooks		client->cl_state = CLIENT_STALE;
794205328Skib		return (FALSE);
795205328Skib	}
796205328Skib	*qop = qop_state;
797205328Skib	return (TRUE);
798253531Skib}
799185879Sjhb
800205014Snwhitehornstatic bool_t
801171214Spetersvc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
802171214Speter    struct svc_req *rqst, u_int seq)
803171214Speter{
804171214Speter	gss_buffer_desc		signbuf;
805171214Speter	OM_uint32		maj_stat, min_stat;
806171214Speter	uint32_t		nseq;
807205014Snwhitehorn
808205014Snwhitehorn	log_debug("in svc_rpc_gss_nextverf()");
809205014Snwhitehorn
810205014Snwhitehorn	nseq = htonl(seq);
811205014Snwhitehorn	signbuf.value = &nseq;
812205014Snwhitehorn	signbuf.length = sizeof(nseq);
813205014Snwhitehorn
814205014Snwhitehorn	if (client->cl_verf.value)
815205014Snwhitehorn		gss_release_buffer(&min_stat, &client->cl_verf);
816180434Sbrooks
817205014Snwhitehorn	maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
818205014Snwhitehorn	    &signbuf, &client->cl_verf);
819205014Snwhitehorn
820180434Sbrooks	if (maj_stat != GSS_S_COMPLETE) {
821180434Sbrooks		log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
822180434Sbrooks		client->cl_state = CLIENT_STALE;
823177790Skib		return (FALSE);
824177790Skib	}
825177790Skib	rqst->rq_xprt->xp_verf.oa_flavor = RPCSEC_GSS;
826191675Sjamie	rqst->rq_xprt->xp_verf.oa_base = (caddr_t)client->cl_verf.value;
827191675Sjamie	rqst->rq_xprt->xp_verf.oa_length = (u_int)client->cl_verf.length;
828194919Sjhb
829194919Sjhb	return (TRUE);
830194919Sjhb}
831255658Sjilles
832198512Skibstatic bool_t
833250854Skibsvc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
834220792Smdf{
835227071Sjhb	struct svc_rpc_gss_callback *scb;
836242959Skib	rpc_gss_lock_t	lock;
837250854Skib	void		*cookie;
838250854Skib	bool_t		cb_res;
839250854Skib	bool_t		result;
840250854Skib
841250854Skib	/*
842254482Spjd	 * See if we have a callback for this guy.
843254482Spjd	 */
844251527Sglebius	result = TRUE;
845255709Sjhb	SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
846255709Sjhb		if (scb->cb_callback.program == rqst->rq_prog
847255709Sjhb		    && scb->cb_callback.version == rqst->rq_vers) {
848255709Sjhb			/*
849255709Sjhb			 * This one matches. Call the callback and see
850275987Sdchagin			 * if it wants to veto or something.
851293475Sdchagin			 */
852293475Sdchagin			lock.locked = FALSE;
85394380Sdfr			lock.raw_cred = &client->cl_rawcred;
85494380Sdfr			cb_res = scb->cb_callback.callback(rqst,
85594380Sdfr			    client->cl_creds,
856232449Sjmallett			    client->cl_ctx,
857205014Snwhitehorn			    &lock,
858205014Snwhitehorn			    &cookie);
859223167Skib
860223167Skib			if (!cb_res) {
861223167Skib				client->cl_state = CLIENT_STALE;
862223167Skib				result = FALSE;
863223167Skib				break;
864220239Skib			}
865220239Skib
866220239Skib			/*
867220239Skib			 * The callback accepted the connection - it
868220239Skib			 * is responsible for freeing client->cl_creds
869220239Skib			 * now.
870220239Skib			 */
871220239Skib			client->cl_creds = GSS_C_NO_CREDENTIAL;
872151721Speter			client->cl_locked = lock.locked;
873151721Speter			client->cl_cookie = cookie;
874151721Speter			return (TRUE);
875151721Speter		}
876151721Speter	}
877151721Speter
878151721Speter	/*
879151721Speter	 * Either no callback exists for this program/version or one
880151721Speter	 * of the callbacks rejected the connection. We just need to
881220239Skib	 * clean up the delegated client creds, if any.
882220239Skib	 */
883220239Skib	if (client->cl_creds) {
884220239Skib		OM_uint32 min_ver;
885220239Skib		gss_release_cred(&min_ver, &client->cl_creds);
886220239Skib	}
887220239Skib	return (result);
888220239Skib}
889220239Skib
890220239Skibstatic bool_t
891151721Spetersvc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
892151721Speter{
893151721Speter	u_int32_t offset;
894151721Speter	int word, bit;
895151721Speter
896151721Speter	if (seq <= client->cl_seqlast) {
897151721Speter		/*
898151721Speter		 * The request sequence number is less than
899151721Speter		 * the largest we have seen so far. If it is
900151721Speter		 * outside the window or if we have seen a
901151721Speter		 * request with this sequence before, silently
902151721Speter		 * discard it.
903151721Speter		 */
904151721Speter		offset = client->cl_seqlast - seq;
905151721Speter		if (offset >= SVC_RPC_GSS_SEQWINDOW)
906151721Speter			return (FALSE);
907151721Speter		word = offset / 32;
908151721Speter		bit = offset % 32;
909220239Skib		if (client->cl_seqmask[word] & (1 << bit))
910220239Skib			return (FALSE);
911220239Skib	}
912220239Skib
913220239Skib	return (TRUE);
914220239Skib}
915205014Snwhitehorn
916205014Snwhitehornstatic void
917205014Snwhitehornsvc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
918205014Snwhitehorn{
919205014Snwhitehorn	int offset, i, word, bit;
920205014Snwhitehorn	uint32_t carry, newcarry;
921250854Skib
922250854Skib	if (seq > client->cl_seqlast) {
923250854Skib		/*
924255709Sjhb		 * This request has a sequence number greater
925255709Sjhb		 * than any we have seen so far. Advance the
926255709Sjhb		 * seq window and set bit zero of the window
927223167Skib		 * (which corresponds to the new sequence
928220239Skib		 * number)
929220239Skib		 */
930151721Speter		offset = seq - client->cl_seqlast;
931151721Speter		while (offset > 32) {
932151721Speter			for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
933220239Skib			     i > 0; i--) {
934220239Skib				client->cl_seqmask[i] = client->cl_seqmask[i-1];
935220239Skib			}
936151721Speter			client->cl_seqmask[0] = 0;
937151721Speter			offset -= 32;
938151721Speter		}
939151721Speter		carry = 0;
940151721Speter		for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
941220239Skib			newcarry = client->cl_seqmask[i] >> (32 - offset);
94294380Sdfr			client->cl_seqmask[i] =
94394380Sdfr				(client->cl_seqmask[i] << offset) | carry;
94494380Sdfr			carry = newcarry;
945100385Speter		}
946100385Speter		client->cl_seqmask[0] |= 1;
947100385Speter		client->cl_seqlast = seq;
948232449Sjmallett	} else {
949205014Snwhitehorn		offset = client->cl_seqlast - seq;
950205014Snwhitehorn		word = offset / 32;
951128261Speter		bit = offset % 32;
952128261Speter		client->cl_seqmask[word] |= (1 << bit);
953128261Speter	}
954128261Speter
955128261Speter}
956128261Speter
957128261Speterenum auth_stat
958128261Spetersvc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
959128261Speter
960128261Speter{
961128261Speter	OM_uint32		 min_stat;
962128261Speter	XDR	 		 xdrs;
963128261Speter	struct svc_rpc_gss_client *client;
964128261Speter	struct rpc_gss_cred	 gc;
965128261Speter	struct rpc_gss_init_res	 gr;
966128261Speter	gss_qop_t		 qop;
967128261Speter	int			 call_stat;
968119332Speter	enum auth_stat		 result;
969104739Speter
970104739Speter	log_debug("in svc_rpc_gss()");
971236027Sed
972236027Sed	/* Garbage collect old clients. */
973104739Speter	svc_rpc_gss_timeout_clients();
974156115Sps
975104739Speter	/* Initialize reply. */
976104739Speter	rqst->rq_xprt->xp_verf = _null_auth;
977104739Speter
978119332Speter	/* Deserialize client credentials. */
979114988Speter	if (rqst->rq_cred.oa_length <= 0)
980114988Speter		return (AUTH_BADCRED);
981114988Speter
982114988Speter	memset(&gc, 0, sizeof(gc));
983119332Speter
984126093Speter	xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
985114988Speter	    rqst->rq_cred.oa_length, XDR_DECODE);
986205014Snwhitehorn
987205014Snwhitehorn	if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
988205014Snwhitehorn		XDR_DESTROY(&xdrs);
989205014Snwhitehorn		return (AUTH_BADCRED);
990205014Snwhitehorn	}
991205014Snwhitehorn	XDR_DESTROY(&xdrs);
992250854Skib
993250854Skib	/* Check version. */
994250854Skib	if (gc.gc_version != RPCSEC_GSS_VERSION) {
995255709Sjhb		result = AUTH_BADCRED;
996255709Sjhb		goto out;
997255709Sjhb	}
998128261Speter
999128261Speter	/* Check the proc and find the client (or create it) */
1000128261Speter	if (gc.gc_proc == RPCSEC_GSS_INIT) {
1001128261Speter		if (gc.gc_handle.length != 0) {
1002119332Speter			result = AUTH_BADCRED;
1003119332Speter			goto out;
1004119332Speter		}
1005100385Speter		client = svc_rpc_gss_create_client();
1006100385Speter	} else {
1007100385Speter		if (gc.gc_handle.length != sizeof(uint32_t)) {
1008171214Speter			result = AUTH_BADCRED;
1009197637Srwatson			goto out;
1010171214Speter		}
1011232449Sjmallett		uint32_t *p = gc.gc_handle.value;
1012205014Snwhitehorn		client = svc_rpc_gss_find_client(*p);
1013205014Snwhitehorn		if (!client) {
1014171214Speter			/*
1015171214Speter			 * Can't find the client - we may have
1016171214Speter			 * destroyed it - tell the other side to
1017171214Speter			 * re-authenticate.
1018171214Speter			 */
1019236027Sed			result = RPCSEC_GSS_CREDPROBLEM;
1020236027Sed			goto out;
1021171214Speter		}
1022171214Speter	}
1023171214Speter	rqst->rq_clntcred = client;
1024171214Speter
1025171214Speter	/*
1026171214Speter	 * The service and sequence number must be ignored for
1027236027Sed	 * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
1028236027Sed	 */
1029171214Speter	if (gc.gc_proc != RPCSEC_GSS_INIT
1030171214Speter	    && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
1031171214Speter		/*
1032171214Speter		 * Check for sequence number overflow.
1033171214Speter		 */
1034171214Speter		if (gc.gc_seq >= MAXSEQ) {
1035171214Speter			result = RPCSEC_GSS_CTXPROBLEM;
1036171214Speter			goto out;
1037236027Sed		}
1038236027Sed		client->cl_seq = gc.gc_seq;
1039171214Speter
1040171214Speter		/*
1041171214Speter		 * Check for valid service.
1042171214Speter		 */
1043236027Sed		if (gc.gc_svc != rpc_gss_svc_none &&
1044236027Sed		    gc.gc_svc != rpc_gss_svc_integrity &&
1045171214Speter		    gc.gc_svc != rpc_gss_svc_privacy) {
1046171214Speter			result = AUTH_BADCRED;
1047171214Speter			goto out;
1048171214Speter		}
1049171214Speter	}
1050236027Sed
1051236027Sed	/* Handle RPCSEC_GSS control procedure. */
1052171214Speter	switch (gc.gc_proc) {
1053171214Speter
1054171214Speter	case RPCSEC_GSS_INIT:
1055171214Speter	case RPCSEC_GSS_CONTINUE_INIT:
1056236027Sed		if (rqst->rq_proc != NULLPROC) {
1057236027Sed			result = AUTH_REJECTEDCRED;
1058171214Speter			break;
1059205014Snwhitehorn		}
1060205014Snwhitehorn
1061205014Snwhitehorn		memset(&gr, 0, sizeof(gr));
1062205014Snwhitehorn		if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
1063205014Snwhitehorn			result = AUTH_REJECTEDCRED;
1064205014Snwhitehorn			break;
1065250854Skib		}
1066250854Skib
1067250854Skib		if (gr.gr_major == GSS_S_COMPLETE) {
1068255709Sjhb			if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
1069255709Sjhb				result = AUTH_REJECTEDCRED;
1070255709Sjhb				break;
1071171214Speter			}
1072171214Speter		} else {
1073171214Speter			rqst->rq_xprt->xp_verf.oa_flavor = AUTH_NULL;
1074171214Speter			rqst->rq_xprt->xp_verf.oa_length = 0;
1075171214Speter		}
1076171214Speter
1077171214Speter		call_stat = svc_sendreply(rqst->rq_xprt,
1078197637Srwatson		    (xdrproc_t) xdr_rpc_gss_init_res,
1079171214Speter		    (caddr_t) &gr);
1080194919Sjhb
1081200619Simp		gss_release_buffer(&min_stat, &gr.gr_token);
1082194919Sjhb
1083232449Sjmallett		if (!call_stat) {
1084205014Snwhitehorn			result = AUTH_FAILED;
1085205014Snwhitehorn			break;
1086194919Sjhb		}
1087194919Sjhb
1088194919Sjhb		if (gr.gr_major == GSS_S_COMPLETE)
1089194919Sjhb			client->cl_state = CLIENT_ESTABLISHED;
1090194919Sjhb
1091194919Sjhb		result = RPCSEC_GSS_NODISPATCH;
1092194919Sjhb		break;
1093194919Sjhb
1094194919Sjhb	case RPCSEC_GSS_DATA:
1095194919Sjhb	case RPCSEC_GSS_DESTROY:
1096194919Sjhb		if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
1097194919Sjhb			result = RPCSEC_GSS_NODISPATCH;
1098194919Sjhb			break;
1099194919Sjhb		}
1100194919Sjhb
1101194919Sjhb		if (!svc_rpc_gss_validate(client, msg, &qop)) {
1102205014Snwhitehorn			result = RPCSEC_GSS_CREDPROBLEM;
1103205014Snwhitehorn			break;
1104205014Snwhitehorn		}
1105205014Snwhitehorn
1106205014Snwhitehorn		if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
1107205014Snwhitehorn			result = RPCSEC_GSS_CTXPROBLEM;
1108250854Skib			break;
1109250854Skib		}
1110250854Skib
1111255709Sjhb		svc_rpc_gss_update_seq(client, gc.gc_seq);
1112255709Sjhb
1113255709Sjhb		/*
1114194919Sjhb		 * Change the SVCAUTH ops on the transport to point at
1115194919Sjhb		 * our own code so that we can unwrap the arguments
1116194919Sjhb		 * and wrap the result. The caller will re-set this on
1117194919Sjhb		 * every request to point to a set of null wrap/unwrap
1118200619Simp		 * methods.
1119194919Sjhb		 */
1120161330Sjhb		SVC_AUTH(rqst->rq_xprt).svc_ah_ops = &svc_auth_gss_ops;
1121194647Sjhb		SVC_AUTH(rqst->rq_xprt).svc_ah_private = client;
1122223167Skib
1123161330Sjhb		if (gc.gc_proc == RPCSEC_GSS_DATA) {
1124161330Sjhb			/*
1125161330Sjhb			 * We might be ready to do a callback to the server to
1126220239Skib			 * see if it wants to accept/reject the connection.
1127220239Skib			 */
1128194647Sjhb			if (!client->cl_done_callback) {
1129194647Sjhb				client->cl_done_callback = TRUE;
1130194647Sjhb				client->cl_qop = qop;
1131162374Srwatson				client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1132183271Sobrien					client->cl_rawcred.mechanism, qop);
1133161330Sjhb				if (!svc_rpc_gss_callback(client, rqst)) {
1134220239Skib					result = AUTH_REJECTEDCRED;
1135220239Skib					break;
1136226349Smarcel				}
1137161330Sjhb			}
1138161330Sjhb
1139271011Skib			/*
1140161330Sjhb			 * If the server has locked this client to a
1141220239Skib			 * particular service+qop pair, enforce that
1142194647Sjhb			 * restriction now.
1143194647Sjhb			 */
1144194647Sjhb			if (client->cl_locked) {
1145194647Sjhb				if (client->cl_rawcred.service != gc.gc_svc) {
1146194647Sjhb					result = AUTH_FAILED;
1147161330Sjhb					break;
1148161330Sjhb				} else if (client->cl_qop != qop) {
1149161330Sjhb					result = AUTH_BADVERF;
1150161330Sjhb					break;
1151161330Sjhb				}
1152161330Sjhb			}
1153161330Sjhb
1154220239Skib			/*
1155194647Sjhb			 * If the qop changed, look up the new qop
1156194647Sjhb			 * name for rawcred.
1157190622Skib			 */
1158161330Sjhb			if (client->cl_qop != qop) {
1159161330Sjhb				client->cl_qop = qop;
1160161330Sjhb				client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1161194647Sjhb					client->cl_rawcred.mechanism, qop);
1162194647Sjhb			}
1163161330Sjhb
1164161330Sjhb			/*
1165161330Sjhb			 * Make sure we use the right service value
1166184184Sjhb			 * for unwrap/wrap.
1167194647Sjhb			 */
1168194647Sjhb			client->cl_rawcred.service = gc.gc_svc;
1169194647Sjhb
1170194647Sjhb			result = AUTH_OK;
1171161330Sjhb		} else {
1172161330Sjhb			if (rqst->rq_proc != NULLPROC) {
1173194919Sjhb				result = AUTH_REJECTEDCRED;
1174194919Sjhb				break;
1175165406Sjkim			}
1176165406Sjkim
1177194919Sjhb			call_stat = svc_sendreply(rqst->rq_xprt,
1178161330Sjhb			    (xdrproc_t) xdr_void, (caddr_t) NULL);
1179161330Sjhb
1180161330Sjhb			if (!call_stat) {
1181253531Skib				result = AUTH_FAILED;
1182253531Skib				break;
1183253531Skib			}
1184161330Sjhb
1185253495Skib			svc_rpc_gss_destroy_client(client);
1186185879Sjhb
1187185879Sjhb			result = RPCSEC_GSS_NODISPATCH;
1188185879Sjhb			break;
1189161330Sjhb		}
1190161330Sjhb		break;
1191161330Sjhb
1192194647Sjhb	default:
1193161330Sjhb		result = AUTH_BADCRED;
1194220159Skib		break;
1195185879Sjhb	}
1196185879Sjhbout:
1197185879Sjhb	xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
1198185879Sjhb	return (result);
1199185879Sjhb}
1200185879Sjhb
1201185879Sjhbbool_t
1202194647Sjhbsvc_rpc_gss_wrap(SVCAUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
1203185436Sbz{
1204194647Sjhb	struct svc_rpc_gss_client *client;
1205194647Sjhb
1206163020Sdavidxu	log_debug("in svc_rpc_gss_wrap()");
1207163020Sdavidxu
1208185879Sjhb	client = (struct svc_rpc_gss_client *) auth->svc_ah_private;
1209161330Sjhb	if (client->cl_state != CLIENT_ESTABLISHED
1210183189Sobrien	    || client->cl_rawcred.service == rpc_gss_svc_none) {
1211161960Srwatson		return xdr_func(xdrs, xdr_ptr);
1212205328Skib	}
1213205328Skib	return (xdr_rpc_gss_wrap_data(xdrs, xdr_func, xdr_ptr,
1214161330Sjhb		client->cl_ctx, client->cl_qop,
1215161330Sjhb		client->cl_rawcred.service, client->cl_seq));
1216161330Sjhb}
1217161330Sjhb
1218161330Sjhbbool_t
1219163047Sdavidxusvc_rpc_gss_unwrap(SVCAUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
1220163047Sdavidxu{
1221205328Skib	struct svc_rpc_gss_client *client;
1222162552Sdavidxu
1223162537Sdavidxu	log_debug("in svc_rpc_gss_unwrap()");
1224162552Sdavidxu
1225318323Sbrooks	client = (struct svc_rpc_gss_client *) auth->svc_ah_private;
1226205328Skib	if (client->cl_state != CLIENT_ESTABLISHED
1227205328Skib	    || client->cl_rawcred.service == rpc_gss_svc_none) {
1228205328Skib		return xdr_func(xdrs, xdr_ptr);
1229205328Skib	}
1230253531Skib	return (xdr_rpc_gss_unwrap_data(xdrs, xdr_func, xdr_ptr,
1231185879Sjhb		client->cl_ctx, client->cl_qop,
1232171214Speter		client->cl_rawcred.service, client->cl_seq));
1233171214Speter}
1234171214Speter