1181344Sdfr/*-
2181344Sdfr * Copyright (c) 2008 Doug Rabson
3181344Sdfr * All rights reserved.
4181344Sdfr *
5181344Sdfr * Redistribution and use in source and binary forms, with or without
6181344Sdfr * modification, are permitted provided that the following conditions
7181344Sdfr * are met:
8181344Sdfr * 1. Redistributions of source code must retain the above copyright
9181344Sdfr *    notice, this list of conditions and the following disclaimer.
10181344Sdfr * 2. Redistributions in binary form must reproduce the above copyright
11181344Sdfr *    notice, this list of conditions and the following disclaimer in the
12181344Sdfr *    documentation and/or other materials provided with the distribution.
13181344Sdfr *
14181344Sdfr * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15181344Sdfr * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16181344Sdfr * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17181344Sdfr * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18181344Sdfr * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19181344Sdfr * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20181344Sdfr * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21181344Sdfr * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22181344Sdfr * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23181344Sdfr * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24181344Sdfr * SUCH DAMAGE.
25181344Sdfr *
26181344Sdfr *	$FreeBSD$
27181344Sdfr */
28181344Sdfr/*
29181344Sdfr  svc_rpcsec_gss.c
30181344Sdfr
31181344Sdfr  Copyright (c) 2000 The Regents of the University of Michigan.
32181344Sdfr  All rights reserved.
33181344Sdfr
34181344Sdfr  Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
35181344Sdfr  All rights reserved, all wrongs reversed.
36181344Sdfr
37181344Sdfr  Redistribution and use in source and binary forms, with or without
38181344Sdfr  modification, are permitted provided that the following conditions
39181344Sdfr  are met:
40181344Sdfr
41181344Sdfr  1. Redistributions of source code must retain the above copyright
42181344Sdfr     notice, this list of conditions and the following disclaimer.
43181344Sdfr  2. Redistributions in binary form must reproduce the above copyright
44181344Sdfr     notice, this list of conditions and the following disclaimer in the
45181344Sdfr     documentation and/or other materials provided with the distribution.
46181344Sdfr  3. Neither the name of the University nor the names of its
47181344Sdfr     contributors may be used to endorse or promote products derived
48181344Sdfr     from this software without specific prior written permission.
49181344Sdfr
50181344Sdfr  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
51181344Sdfr  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
52181344Sdfr  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
53181344Sdfr  DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
54181344Sdfr  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
55181344Sdfr  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
56181344Sdfr  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
57181344Sdfr  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
58181344Sdfr  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
59181344Sdfr  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
60181344Sdfr  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
61181344Sdfr
62181344Sdfr  $Id: svc_auth_gss.c,v 1.27 2002/01/15 15:43:00 andros Exp $
63181344Sdfr */
64181344Sdfr
65181344Sdfr#include <stdio.h>
66181344Sdfr#include <stdlib.h>
67181344Sdfr#include <string.h>
68181344Sdfr#include <pwd.h>
69181344Sdfr#include <grp.h>
70181344Sdfr#include <errno.h>
71181344Sdfr#include <unistd.h>
72181344Sdfr#include <sys/queue.h>
73181344Sdfr#include <rpc/rpc.h>
74181344Sdfr#include <rpc/rpcsec_gss.h>
75181344Sdfr#include "rpcsec_gss_int.h"
76181344Sdfr
77181344Sdfrstatic bool_t	svc_rpc_gss_initialised = FALSE;
78181344Sdfr
79181344Sdfrstatic bool_t   svc_rpc_gss_wrap(SVCAUTH *, XDR *, xdrproc_t, caddr_t);
80181344Sdfrstatic bool_t   svc_rpc_gss_unwrap(SVCAUTH *, XDR *, xdrproc_t, caddr_t);
81181344Sdfrstatic enum auth_stat svc_rpc_gss(struct svc_req *, struct rpc_msg *);
82181344Sdfr
83181344Sdfrstatic struct svc_auth_ops svc_auth_gss_ops = {
84181344Sdfr	svc_rpc_gss_wrap,
85181344Sdfr	svc_rpc_gss_unwrap,
86181344Sdfr};
87181344Sdfr
88181344Sdfrstruct svc_rpc_gss_callback {
89181344Sdfr	SLIST_ENTRY(svc_rpc_gss_callback) cb_link;
90181344Sdfr	rpc_gss_callback_t	cb_callback;
91181344Sdfr};
92181344Sdfrstatic SLIST_HEAD(svc_rpc_gss_callback_list, svc_rpc_gss_callback)
93201145Santoine	svc_rpc_gss_callbacks = SLIST_HEAD_INITIALIZER(svc_rpc_gss_callbacks);
94181344Sdfr
95181344Sdfrstruct svc_rpc_gss_svc_name {
96181344Sdfr	SLIST_ENTRY(svc_rpc_gss_svc_name) sn_link;
97181344Sdfr	char			*sn_principal;
98181344Sdfr	gss_OID			sn_mech;
99181344Sdfr	u_int			sn_req_time;
100181344Sdfr	gss_cred_id_t		sn_cred;
101181344Sdfr	u_int			sn_program;
102181344Sdfr	u_int			sn_version;
103181344Sdfr};
104181344Sdfrstatic SLIST_HEAD(svc_rpc_gss_svc_name_list, svc_rpc_gss_svc_name)
105201145Santoine	svc_rpc_gss_svc_names = SLIST_HEAD_INITIALIZER(svc_rpc_gss_svc_names);
106181344Sdfr
107181344Sdfrenum svc_rpc_gss_client_state {
108181344Sdfr	CLIENT_NEW,				/* still authenticating */
109181344Sdfr	CLIENT_ESTABLISHED,			/* context established */
110181344Sdfr	CLIENT_STALE				/* garbage to collect */
111181344Sdfr};
112181344Sdfr
113181344Sdfr#define SVC_RPC_GSS_SEQWINDOW	128
114181344Sdfr
115181344Sdfrstruct svc_rpc_gss_client {
116181344Sdfr	TAILQ_ENTRY(svc_rpc_gss_client) cl_link;
117181344Sdfr	TAILQ_ENTRY(svc_rpc_gss_client) cl_alllink;
118181344Sdfr	uint32_t		cl_id;
119181344Sdfr	time_t			cl_expiration;	/* when to gc */
120181344Sdfr	enum svc_rpc_gss_client_state cl_state;	/* client state */
121181344Sdfr	bool_t			cl_locked;	/* fixed service+qop */
122181344Sdfr	gss_ctx_id_t		cl_ctx;		/* context id */
123181344Sdfr	gss_cred_id_t		cl_creds;	/* delegated creds */
124181344Sdfr	gss_name_t		cl_cname;	/* client name */
125181344Sdfr	struct svc_rpc_gss_svc_name *cl_sname;	/* server name used */
126181344Sdfr	rpc_gss_rawcred_t	cl_rawcred;	/* raw credentials */
127181344Sdfr	rpc_gss_ucred_t		cl_ucred;	/* unix-style credentials */
128181344Sdfr	bool_t			cl_done_callback; /* TRUE after call */
129181344Sdfr	void			*cl_cookie;	/* user cookie from callback */
130181344Sdfr	gid_t			cl_gid_storage[NGRPS];
131181344Sdfr	gss_OID			cl_mech;	/* mechanism */
132181344Sdfr	gss_qop_t		cl_qop;		/* quality of protection */
133181344Sdfr	u_int			cl_seq;		/* current sequence number */
134181344Sdfr	u_int			cl_win;		/* sequence window size */
135181344Sdfr	u_int			cl_seqlast;	/* sequence window origin */
136181344Sdfr	uint32_t		cl_seqmask[SVC_RPC_GSS_SEQWINDOW/32]; /* bitmask of seqnums */
137181344Sdfr	gss_buffer_desc		cl_verf;	/* buffer for verf checksum */
138181344Sdfr};
139181344SdfrTAILQ_HEAD(svc_rpc_gss_client_list, svc_rpc_gss_client);
140181344Sdfr
141181344Sdfr#define CLIENT_HASH_SIZE	256
142181344Sdfr#define CLIENT_MAX		128
143241720Sedstatic struct svc_rpc_gss_client_list svc_rpc_gss_client_hash[CLIENT_HASH_SIZE];
144241720Sedstatic struct svc_rpc_gss_client_list svc_rpc_gss_clients;
145181344Sdfrstatic size_t svc_rpc_gss_client_count;
146181344Sdfrstatic uint32_t svc_rpc_gss_next_clientid = 1;
147181344Sdfr
148181344Sdfr#ifdef __GNUC__
149181344Sdfrstatic void svc_rpc_gss_init(void) __attribute__ ((constructor));
150181344Sdfr#endif
151181344Sdfr
152181344Sdfrstatic void
153181344Sdfrsvc_rpc_gss_init(void)
154181344Sdfr{
155181344Sdfr	int i;
156181344Sdfr
157181344Sdfr	if (!svc_rpc_gss_initialised) {
158181344Sdfr		for (i = 0; i < CLIENT_HASH_SIZE; i++)
159181344Sdfr			TAILQ_INIT(&svc_rpc_gss_client_hash[i]);
160181344Sdfr		TAILQ_INIT(&svc_rpc_gss_clients);
161181344Sdfr		svc_auth_reg(RPCSEC_GSS, svc_rpc_gss);
162181344Sdfr		svc_rpc_gss_initialised = TRUE;
163181344Sdfr	}
164181344Sdfr}
165181344Sdfr
166181344Sdfrbool_t
167181344Sdfrrpc_gss_set_callback(rpc_gss_callback_t *cb)
168181344Sdfr{
169181344Sdfr	struct svc_rpc_gss_callback *scb;
170181344Sdfr
171184588Sdfr	scb = mem_alloc(sizeof(struct svc_rpc_gss_callback));
172181344Sdfr	if (!scb) {
173181344Sdfr		_rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
174181344Sdfr		return (FALSE);
175181344Sdfr	}
176181344Sdfr	scb->cb_callback = *cb;
177181344Sdfr	SLIST_INSERT_HEAD(&svc_rpc_gss_callbacks, scb, cb_link);
178181344Sdfr
179181344Sdfr	return (TRUE);
180181344Sdfr}
181181344Sdfr
182181344Sdfrbool_t
183181344Sdfrrpc_gss_set_svc_name(const char *principal, const char *mechanism,
184181344Sdfr    u_int req_time, u_int program, u_int version)
185181344Sdfr{
186181344Sdfr	OM_uint32		maj_stat, min_stat;
187181344Sdfr	struct svc_rpc_gss_svc_name *sname;
188181344Sdfr	gss_buffer_desc		namebuf;
189181344Sdfr	gss_name_t		name;
190181344Sdfr	gss_OID			mech_oid;
191181344Sdfr	gss_OID_set_desc	oid_set;
192181344Sdfr	gss_cred_id_t		cred;
193181344Sdfr
194181344Sdfr	svc_rpc_gss_init();
195181344Sdfr
196181344Sdfr	if (!rpc_gss_mech_to_oid(mechanism, &mech_oid))
197181344Sdfr		return (FALSE);
198181344Sdfr	oid_set.count = 1;
199181344Sdfr	oid_set.elements = mech_oid;
200181344Sdfr
201181344Sdfr	namebuf.value = (void *)(intptr_t) principal;
202181344Sdfr	namebuf.length = strlen(principal);
203181344Sdfr
204181344Sdfr	maj_stat = gss_import_name(&min_stat, &namebuf,
205181344Sdfr				   GSS_C_NT_HOSTBASED_SERVICE, &name);
206181344Sdfr	if (maj_stat != GSS_S_COMPLETE)
207181344Sdfr		return (FALSE);
208181344Sdfr
209181344Sdfr	maj_stat = gss_acquire_cred(&min_stat, name,
210181344Sdfr	    req_time, &oid_set, GSS_C_ACCEPT, &cred, NULL, NULL);
211181344Sdfr	if (maj_stat != GSS_S_COMPLETE)
212181344Sdfr		return (FALSE);
213181344Sdfr
214181344Sdfr	gss_release_name(&min_stat, &name);
215181344Sdfr
216181344Sdfr	sname = malloc(sizeof(struct svc_rpc_gss_svc_name));
217181344Sdfr	if (!sname)
218181344Sdfr		return (FALSE);
219181344Sdfr	sname->sn_principal = strdup(principal);
220181344Sdfr	sname->sn_mech = mech_oid;
221181344Sdfr	sname->sn_req_time = req_time;
222181344Sdfr	sname->sn_cred = cred;
223181344Sdfr	sname->sn_program = program;
224181344Sdfr	sname->sn_version = version;
225181344Sdfr	SLIST_INSERT_HEAD(&svc_rpc_gss_svc_names, sname, sn_link);
226181344Sdfr
227181344Sdfr	return (TRUE);
228181344Sdfr}
229181344Sdfr
230181344Sdfrbool_t
231181344Sdfrrpc_gss_get_principal_name(rpc_gss_principal_t *principal,
232181344Sdfr    const char *mech, const char *name, const char *node, const char *domain)
233181344Sdfr{
234181344Sdfr	OM_uint32		maj_stat, min_stat;
235181344Sdfr	gss_OID			mech_oid;
236181344Sdfr	size_t			namelen;
237181344Sdfr	gss_buffer_desc		buf;
238181344Sdfr	gss_name_t		gss_name, gss_mech_name;
239181344Sdfr	rpc_gss_principal_t	result;
240181344Sdfr
241181344Sdfr	svc_rpc_gss_init();
242181344Sdfr
243181344Sdfr	if (!rpc_gss_mech_to_oid(mech, &mech_oid))
244181344Sdfr		return (FALSE);
245181344Sdfr
246181344Sdfr	/*
247181344Sdfr	 * Construct a gss_buffer containing the full name formatted
248181344Sdfr	 * as "name/node@domain" where node and domain are optional.
249181344Sdfr	 */
250181344Sdfr	namelen = strlen(name);
251181344Sdfr	if (node) {
252181344Sdfr		namelen += strlen(node) + 1;
253181344Sdfr	}
254181344Sdfr	if (domain) {
255181344Sdfr		namelen += strlen(domain) + 1;
256181344Sdfr	}
257181344Sdfr
258184588Sdfr	buf.value = mem_alloc(namelen);
259181344Sdfr	buf.length = namelen;
260181344Sdfr	strcpy((char *) buf.value, name);
261181344Sdfr	if (node) {
262181344Sdfr		strcat((char *) buf.value, "/");
263181344Sdfr		strcat((char *) buf.value, node);
264181344Sdfr	}
265181344Sdfr	if (domain) {
266181344Sdfr		strcat((char *) buf.value, "@");
267181344Sdfr		strcat((char *) buf.value, domain);
268181344Sdfr	}
269181344Sdfr
270181344Sdfr	/*
271181344Sdfr	 * Convert that to a gss_name_t and then convert that to a
272181344Sdfr	 * mechanism name in the selected mechanism.
273181344Sdfr	 */
274181344Sdfr	maj_stat = gss_import_name(&min_stat, &buf,
275181344Sdfr	    GSS_C_NT_USER_NAME, &gss_name);
276184588Sdfr	mem_free(buf.value, buf.length);
277181344Sdfr	if (maj_stat != GSS_S_COMPLETE) {
278181344Sdfr		log_status("gss_import_name", mech_oid, maj_stat, min_stat);
279181344Sdfr		return (FALSE);
280181344Sdfr	}
281181344Sdfr	maj_stat = gss_canonicalize_name(&min_stat, gss_name, mech_oid,
282181344Sdfr	    &gss_mech_name);
283181344Sdfr	if (maj_stat != GSS_S_COMPLETE) {
284181344Sdfr		log_status("gss_canonicalize_name", mech_oid, maj_stat,
285181344Sdfr		    min_stat);
286181344Sdfr		gss_release_name(&min_stat, &gss_name);
287181344Sdfr		return (FALSE);
288181344Sdfr	}
289181344Sdfr	gss_release_name(&min_stat, &gss_name);
290181344Sdfr
291181344Sdfr	/*
292181344Sdfr	 * Export the mechanism name and use that to construct the
293181344Sdfr	 * rpc_gss_principal_t result.
294181344Sdfr	 */
295181344Sdfr	maj_stat = gss_export_name(&min_stat, gss_mech_name, &buf);
296181344Sdfr	if (maj_stat != GSS_S_COMPLETE) {
297181344Sdfr		log_status("gss_export_name", mech_oid, maj_stat, min_stat);
298181344Sdfr		gss_release_name(&min_stat, &gss_mech_name);
299181344Sdfr		return (FALSE);
300181344Sdfr	}
301181344Sdfr	gss_release_name(&min_stat, &gss_mech_name);
302181344Sdfr
303184588Sdfr	result = mem_alloc(sizeof(int) + buf.length);
304181344Sdfr	if (!result) {
305181344Sdfr		gss_release_buffer(&min_stat, &buf);
306181344Sdfr		return (FALSE);
307181344Sdfr	}
308181344Sdfr	result->len = buf.length;
309181344Sdfr	memcpy(result->name, buf.value, buf.length);
310181344Sdfr	gss_release_buffer(&min_stat, &buf);
311181344Sdfr
312181344Sdfr	*principal = result;
313181344Sdfr	return (TRUE);
314181344Sdfr}
315181344Sdfr
316181344Sdfrbool_t
317181344Sdfrrpc_gss_getcred(struct svc_req *req, rpc_gss_rawcred_t **rcred,
318181344Sdfr    rpc_gss_ucred_t **ucred, void **cookie)
319181344Sdfr{
320181344Sdfr	struct svc_rpc_gss_client *client;
321181344Sdfr
322181344Sdfr	if (req->rq_cred.oa_flavor != RPCSEC_GSS)
323181344Sdfr		return (FALSE);
324181344Sdfr
325181344Sdfr	client = req->rq_clntcred;
326181344Sdfr	if (rcred)
327181344Sdfr		*rcred = &client->cl_rawcred;
328181344Sdfr	if (ucred)
329181344Sdfr		*ucred = &client->cl_ucred;
330181344Sdfr	if (cookie)
331181344Sdfr		*cookie = client->cl_cookie;
332181344Sdfr	return (TRUE);
333181344Sdfr}
334181344Sdfr
335181344Sdfrint
336181344Sdfrrpc_gss_svc_max_data_length(struct svc_req *req, int max_tp_unit_len)
337181344Sdfr{
338181344Sdfr	struct svc_rpc_gss_client *client = req->rq_clntcred;
339181344Sdfr	int			want_conf;
340181344Sdfr	OM_uint32		max;
341181344Sdfr	OM_uint32		maj_stat, min_stat;
342181344Sdfr	int			result;
343181344Sdfr
344181344Sdfr	switch (client->cl_rawcred.service) {
345181344Sdfr	case rpc_gss_svc_none:
346181344Sdfr		return (max_tp_unit_len);
347181344Sdfr		break;
348181344Sdfr
349181344Sdfr	case rpc_gss_svc_default:
350181344Sdfr	case rpc_gss_svc_integrity:
351181344Sdfr		want_conf = FALSE;
352181344Sdfr		break;
353181344Sdfr
354181344Sdfr	case rpc_gss_svc_privacy:
355181344Sdfr		want_conf = TRUE;
356181344Sdfr		break;
357181344Sdfr
358181344Sdfr	default:
359181344Sdfr		return (0);
360181344Sdfr	}
361181344Sdfr
362181344Sdfr	maj_stat = gss_wrap_size_limit(&min_stat, client->cl_ctx, want_conf,
363181344Sdfr	    client->cl_qop, max_tp_unit_len, &max);
364181344Sdfr
365181344Sdfr	if (maj_stat == GSS_S_COMPLETE) {
366181344Sdfr		result = (int) max;
367181344Sdfr		if (result < 0)
368181344Sdfr			result = 0;
369181344Sdfr		return (result);
370181344Sdfr	} else {
371181344Sdfr		log_status("gss_wrap_size_limit", client->cl_mech,
372181344Sdfr		    maj_stat, min_stat);
373181344Sdfr		return (0);
374181344Sdfr	}
375181344Sdfr}
376181344Sdfr
377181344Sdfrstatic struct svc_rpc_gss_client *
378181344Sdfrsvc_rpc_gss_find_client(uint32_t clientid)
379181344Sdfr{
380181344Sdfr	struct svc_rpc_gss_client *client;
381181344Sdfr	struct svc_rpc_gss_client_list *list;
382181344Sdfr
383181344Sdfr
384181344Sdfr	log_debug("in svc_rpc_gss_find_client(%d)", clientid);
385181344Sdfr
386181344Sdfr	list = &svc_rpc_gss_client_hash[clientid % CLIENT_HASH_SIZE];
387181344Sdfr	TAILQ_FOREACH(client, list, cl_link) {
388181344Sdfr		if (client->cl_id == clientid) {
389181344Sdfr			/*
390181344Sdfr			 * Move this client to the front of the LRU
391181344Sdfr			 * list.
392181344Sdfr			 */
393181344Sdfr			TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
394181344Sdfr			TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client,
395181344Sdfr			    cl_alllink);
396181344Sdfr			return client;
397181344Sdfr		}
398181344Sdfr	}
399181344Sdfr
400181344Sdfr	return (NULL);
401181344Sdfr}
402181344Sdfr
403181344Sdfrstatic struct svc_rpc_gss_client *
404181344Sdfrsvc_rpc_gss_create_client(void)
405181344Sdfr{
406181344Sdfr	struct svc_rpc_gss_client *client;
407181344Sdfr	struct svc_rpc_gss_client_list *list;
408181344Sdfr
409181344Sdfr	log_debug("in svc_rpc_gss_create_client()");
410181344Sdfr
411181344Sdfr	client = mem_alloc(sizeof(struct svc_rpc_gss_client));
412181344Sdfr	memset(client, 0, sizeof(struct svc_rpc_gss_client));
413181344Sdfr	client->cl_id = svc_rpc_gss_next_clientid++;
414181344Sdfr	list = &svc_rpc_gss_client_hash[client->cl_id % CLIENT_HASH_SIZE];
415181344Sdfr	TAILQ_INSERT_HEAD(list, client, cl_link);
416181344Sdfr	TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client, cl_alllink);
417181344Sdfr
418181344Sdfr	/*
419181344Sdfr	 * Start the client off with a short expiration time. We will
420181344Sdfr	 * try to get a saner value from the client creds later.
421181344Sdfr	 */
422181344Sdfr	client->cl_state = CLIENT_NEW;
423181344Sdfr	client->cl_locked = FALSE;
424181344Sdfr	client->cl_expiration = time(0) + 5*60;
425181344Sdfr	svc_rpc_gss_client_count++;
426181344Sdfr
427181344Sdfr	return (client);
428181344Sdfr}
429181344Sdfr
430181344Sdfrstatic void
431181344Sdfrsvc_rpc_gss_destroy_client(struct svc_rpc_gss_client *client)
432181344Sdfr{
433181344Sdfr	struct svc_rpc_gss_client_list *list;
434181344Sdfr	OM_uint32 min_stat;
435181344Sdfr
436181344Sdfr	log_debug("in svc_rpc_gss_destroy_client()");
437181344Sdfr
438181344Sdfr	if (client->cl_ctx)
439181344Sdfr		gss_delete_sec_context(&min_stat,
440181344Sdfr		    &client->cl_ctx, GSS_C_NO_BUFFER);
441181344Sdfr
442181344Sdfr	if (client->cl_cname)
443181344Sdfr		gss_release_name(&min_stat, &client->cl_cname);
444181344Sdfr
445181344Sdfr	if (client->cl_rawcred.client_principal)
446184588Sdfr		mem_free(client->cl_rawcred.client_principal,
447184588Sdfr		    sizeof(*client->cl_rawcred.client_principal)
448184588Sdfr		    + client->cl_rawcred.client_principal->len);
449181344Sdfr
450181344Sdfr	if (client->cl_verf.value)
451181344Sdfr		gss_release_buffer(&min_stat, &client->cl_verf);
452181344Sdfr
453181344Sdfr	list = &svc_rpc_gss_client_hash[client->cl_id % CLIENT_HASH_SIZE];
454181344Sdfr	TAILQ_REMOVE(list, client, cl_link);
455181344Sdfr	TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
456181344Sdfr	svc_rpc_gss_client_count--;
457181344Sdfr	mem_free(client, sizeof(*client));
458181344Sdfr}
459181344Sdfr
460181344Sdfrstatic void
461181344Sdfrsvc_rpc_gss_timeout_clients(void)
462181344Sdfr{
463181344Sdfr	struct svc_rpc_gss_client *client;
464181344Sdfr	struct svc_rpc_gss_client *nclient;
465181344Sdfr	time_t now = time(0);
466181344Sdfr
467181344Sdfr	log_debug("in svc_rpc_gss_timeout_clients()");
468181344Sdfr	/*
469181344Sdfr	 * First enforce the max client limit. We keep
470181344Sdfr	 * svc_rpc_gss_clients in LRU order.
471181344Sdfr	 */
472181344Sdfr	while (svc_rpc_gss_client_count > CLIENT_MAX)
473181344Sdfr		svc_rpc_gss_destroy_client(TAILQ_LAST(&svc_rpc_gss_clients,
474181344Sdfr			    svc_rpc_gss_client_list));
475181344Sdfr	TAILQ_FOREACH_SAFE(client, &svc_rpc_gss_clients, cl_alllink, nclient) {
476181344Sdfr		if (client->cl_state == CLIENT_STALE
477181344Sdfr		    || now > client->cl_expiration) {
478181344Sdfr			log_debug("expiring client %p", client);
479181344Sdfr			svc_rpc_gss_destroy_client(client);
480181344Sdfr		}
481181344Sdfr	}
482181344Sdfr}
483181344Sdfr
484181344Sdfr#ifdef DEBUG
485181344Sdfr/*
486181344Sdfr * OID<->string routines.  These are uuuuugly.
487181344Sdfr */
488181344Sdfrstatic OM_uint32
489181344Sdfrgss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
490181344Sdfr{
491181344Sdfr	char		numstr[128];
492181344Sdfr	unsigned long	number;
493181344Sdfr	int		numshift;
494181344Sdfr	size_t		string_length;
495181344Sdfr	size_t		i;
496181344Sdfr	unsigned char	*cp;
497181344Sdfr	char		*bp;
498181344Sdfr
499181344Sdfr	/* Decoded according to krb5/gssapi_krb5.c */
500181344Sdfr
501181344Sdfr	/* First determine the size of the string */
502181344Sdfr	string_length = 0;
503181344Sdfr	number = 0;
504181344Sdfr	numshift = 0;
505181344Sdfr	cp = (unsigned char *) oid->elements;
506181344Sdfr	number = (unsigned long) cp[0];
507181344Sdfr	sprintf(numstr, "%ld ", number/40);
508181344Sdfr	string_length += strlen(numstr);
509181344Sdfr	sprintf(numstr, "%ld ", number%40);
510181344Sdfr	string_length += strlen(numstr);
511181344Sdfr	for (i=1; i<oid->length; i++) {
512181344Sdfr		if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
513181344Sdfr			number = (number << 7) | (cp[i] & 0x7f);
514181344Sdfr			numshift += 7;
515181344Sdfr		}
516181344Sdfr		else {
517181344Sdfr			*minor_status = 0;
518181344Sdfr			return(GSS_S_FAILURE);
519181344Sdfr		}
520181344Sdfr		if ((cp[i] & 0x80) == 0) {
521181344Sdfr			sprintf(numstr, "%ld ", number);
522181344Sdfr			string_length += strlen(numstr);
523181344Sdfr			number = 0;
524181344Sdfr			numshift = 0;
525181344Sdfr		}
526181344Sdfr	}
527181344Sdfr	/*
528181344Sdfr	 * If we get here, we've calculated the length of "n n n ... n ".  Add 4
529181344Sdfr	 * here for "{ " and "}\0".
530181344Sdfr	 */
531181344Sdfr	string_length += 4;
532184588Sdfr	if ((bp = (char *) mem_alloc(string_length))) {
533181344Sdfr		strcpy(bp, "{ ");
534181344Sdfr		number = (unsigned long) cp[0];
535181344Sdfr		sprintf(numstr, "%ld ", number/40);
536181344Sdfr		strcat(bp, numstr);
537181344Sdfr		sprintf(numstr, "%ld ", number%40);
538181344Sdfr		strcat(bp, numstr);
539181344Sdfr		number = 0;
540181344Sdfr		cp = (unsigned char *) oid->elements;
541181344Sdfr		for (i=1; i<oid->length; i++) {
542181344Sdfr			number = (number << 7) | (cp[i] & 0x7f);
543181344Sdfr			if ((cp[i] & 0x80) == 0) {
544181344Sdfr				sprintf(numstr, "%ld ", number);
545181344Sdfr				strcat(bp, numstr);
546181344Sdfr				number = 0;
547181344Sdfr			}
548181344Sdfr		}
549181344Sdfr		strcat(bp, "}");
550181344Sdfr		oid_str->length = strlen(bp)+1;
551181344Sdfr		oid_str->value = (void *) bp;
552181344Sdfr		*minor_status = 0;
553181344Sdfr		return(GSS_S_COMPLETE);
554181344Sdfr	}
555181344Sdfr	*minor_status = 0;
556181344Sdfr	return(GSS_S_FAILURE);
557181344Sdfr}
558181344Sdfr#endif
559181344Sdfr
560181344Sdfrstatic void
561181344Sdfrsvc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
562181344Sdfr    const gss_name_t name)
563181344Sdfr{
564181344Sdfr	OM_uint32		maj_stat, min_stat;
565181344Sdfr	char			buf[128];
566181344Sdfr	uid_t			uid;
567181344Sdfr	struct passwd		pwd, *pw;
568181344Sdfr	rpc_gss_ucred_t		*uc = &client->cl_ucred;
569181344Sdfr
570181344Sdfr	uc->uid = 65534;
571181344Sdfr	uc->gid = 65534;
572181344Sdfr	uc->gidlen = 0;
573181344Sdfr	uc->gidlist = client->cl_gid_storage;
574181344Sdfr
575181344Sdfr	maj_stat = gss_pname_to_uid(&min_stat, name, client->cl_mech, &uid);
576181344Sdfr	if (maj_stat != GSS_S_COMPLETE)
577181344Sdfr		return;
578181344Sdfr
579181344Sdfr	getpwuid_r(uid, &pwd, buf, sizeof(buf), &pw);
580181344Sdfr	if (pw) {
581181344Sdfr		int len = NGRPS;
582181344Sdfr		uc->uid = pw->pw_uid;
583181344Sdfr		uc->gid = pw->pw_gid;
584181344Sdfr		uc->gidlist = client->cl_gid_storage;
585181344Sdfr		getgrouplist(pw->pw_name, pw->pw_gid, uc->gidlist, &len);
586181344Sdfr		uc->gidlen = len;
587181344Sdfr	}
588181344Sdfr}
589181344Sdfr
590181344Sdfrstatic bool_t
591181344Sdfrsvc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
592181344Sdfr			       struct svc_req *rqst,
593181344Sdfr			       struct rpc_gss_init_res *gr,
594181344Sdfr			       struct rpc_gss_cred *gc)
595181344Sdfr{
596181344Sdfr	gss_buffer_desc		recv_tok;
597181344Sdfr	gss_OID			mech;
598181344Sdfr	OM_uint32		maj_stat = 0, min_stat = 0, ret_flags;
599181344Sdfr	OM_uint32		cred_lifetime;
600181344Sdfr	struct svc_rpc_gss_svc_name *sname;
601181344Sdfr
602181344Sdfr	log_debug("in svc_rpc_gss_accept_context()");
603181344Sdfr
604181344Sdfr	/* Deserialize arguments. */
605181344Sdfr	memset(&recv_tok, 0, sizeof(recv_tok));
606181344Sdfr
607181344Sdfr	if (!svc_getargs(rqst->rq_xprt,
608181344Sdfr		(xdrproc_t) xdr_gss_buffer_desc,
609181344Sdfr		(caddr_t) &recv_tok)) {
610181344Sdfr		client->cl_state = CLIENT_STALE;
611181344Sdfr		return (FALSE);
612181344Sdfr	}
613181344Sdfr
614181344Sdfr	/*
615181344Sdfr	 * First time round, try all the server names we have until
616181344Sdfr	 * one matches. Afterwards, stick with that one.
617181344Sdfr	 */
618181344Sdfr	if (!client->cl_sname) {
619181344Sdfr		SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
620181344Sdfr			if (sname->sn_program == rqst->rq_prog
621181344Sdfr			    && sname->sn_version == rqst->rq_vers) {
622181344Sdfr				gr->gr_major = gss_accept_sec_context(
623181344Sdfr					&gr->gr_minor,
624181344Sdfr					&client->cl_ctx,
625181344Sdfr					sname->sn_cred,
626181344Sdfr					&recv_tok,
627181344Sdfr					GSS_C_NO_CHANNEL_BINDINGS,
628181344Sdfr					&client->cl_cname,
629181344Sdfr					&mech,
630181344Sdfr					&gr->gr_token,
631181344Sdfr					&ret_flags,
632181344Sdfr					&cred_lifetime,
633181344Sdfr					&client->cl_creds);
634181344Sdfr				if (gr->gr_major == GSS_S_COMPLETE
635181344Sdfr				    || gr->gr_major == GSS_S_CONTINUE_NEEDED) {
636181344Sdfr					client->cl_sname = sname;
637181344Sdfr					break;
638181344Sdfr				}
639184588Sdfr				client->cl_sname = sname;
640184588Sdfr				break;
641181344Sdfr			}
642181344Sdfr		}
643184588Sdfr		if (!sname) {
644184588Sdfr			xdr_free((xdrproc_t) xdr_gss_buffer_desc,
645184588Sdfr			    (char *) &recv_tok);
646184588Sdfr			return (FALSE);
647184588Sdfr		}
648181344Sdfr	} else {
649181344Sdfr		gr->gr_major = gss_accept_sec_context(
650181344Sdfr			&gr->gr_minor,
651181344Sdfr			&client->cl_ctx,
652181344Sdfr			client->cl_sname->sn_cred,
653181344Sdfr			&recv_tok,
654181344Sdfr			GSS_C_NO_CHANNEL_BINDINGS,
655181344Sdfr			&client->cl_cname,
656181344Sdfr			&mech,
657181344Sdfr			&gr->gr_token,
658181344Sdfr			&ret_flags,
659181344Sdfr			&cred_lifetime,
660181344Sdfr			NULL);
661181344Sdfr	}
662181344Sdfr
663181344Sdfr	xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
664181344Sdfr
665181344Sdfr	/*
666181344Sdfr	 * If we get an error from gss_accept_sec_context, send the
667181344Sdfr	 * reply anyway so that the client gets a chance to see what
668181344Sdfr	 * is wrong.
669181344Sdfr	 */
670181344Sdfr	if (gr->gr_major != GSS_S_COMPLETE &&
671181344Sdfr	    gr->gr_major != GSS_S_CONTINUE_NEEDED) {
672181344Sdfr		log_status("accept_sec_context", client->cl_mech,
673181344Sdfr		    gr->gr_major, gr->gr_minor);
674181344Sdfr		client->cl_state = CLIENT_STALE;
675184588Sdfr		return (TRUE);
676181344Sdfr	}
677181344Sdfr
678181344Sdfr	gr->gr_handle.value = &client->cl_id;
679184588Sdfr	gr->gr_handle.length = sizeof(client->cl_id);
680181344Sdfr	gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
681181344Sdfr
682181344Sdfr	/* Save client info. */
683181344Sdfr	client->cl_mech = mech;
684181344Sdfr	client->cl_qop = GSS_C_QOP_DEFAULT;
685181344Sdfr	client->cl_seq = gc->gc_seq;
686181344Sdfr	client->cl_win = gr->gr_win;
687181344Sdfr	client->cl_done_callback = FALSE;
688181344Sdfr
689181344Sdfr	if (gr->gr_major == GSS_S_COMPLETE) {
690181344Sdfr		gss_buffer_desc	export_name;
691181344Sdfr
692181344Sdfr		/*
693181344Sdfr		 * Change client expiration time to be near when the
694181344Sdfr		 * client creds expire (or 24 hours if we can't figure
695181344Sdfr		 * that out).
696181344Sdfr		 */
697181344Sdfr		if (cred_lifetime == GSS_C_INDEFINITE)
698181344Sdfr			cred_lifetime = time(0) + 24*60*60;
699181344Sdfr
700181344Sdfr		client->cl_expiration = time(0) + cred_lifetime;
701181344Sdfr
702181344Sdfr		/*
703181344Sdfr		 * Fill in cred details in the rawcred structure.
704181344Sdfr		 */
705181344Sdfr		client->cl_rawcred.version = RPCSEC_GSS_VERSION;
706181344Sdfr		rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
707181344Sdfr		maj_stat = gss_export_name(&min_stat, client->cl_cname,
708181344Sdfr		    &export_name);
709181344Sdfr		if (maj_stat != GSS_S_COMPLETE) {
710181344Sdfr			log_status("gss_export_name", client->cl_mech,
711181344Sdfr			    maj_stat, min_stat);
712181344Sdfr			return (FALSE);
713181344Sdfr		}
714181344Sdfr		client->cl_rawcred.client_principal =
715184588Sdfr			mem_alloc(sizeof(*client->cl_rawcred.client_principal)
716181344Sdfr			    + export_name.length);
717181344Sdfr		client->cl_rawcred.client_principal->len = export_name.length;
718181344Sdfr		memcpy(client->cl_rawcred.client_principal->name,
719181344Sdfr		    export_name.value, export_name.length);
720181344Sdfr		gss_release_buffer(&min_stat, &export_name);
721181344Sdfr		client->cl_rawcred.svc_principal =
722181344Sdfr			client->cl_sname->sn_principal;
723181344Sdfr		client->cl_rawcred.service = gc->gc_svc;
724181344Sdfr
725181344Sdfr		/*
726181344Sdfr		 * Use gss_pname_to_uid to map to unix creds. For
727181344Sdfr		 * kerberos5, this uses krb5_aname_to_localname.
728181344Sdfr		 */
729181344Sdfr		svc_rpc_gss_build_ucred(client, client->cl_cname);
730184588Sdfr		gss_release_name(&min_stat, &client->cl_cname);
731181344Sdfr
732181344Sdfr#ifdef DEBUG
733181344Sdfr		{
734181344Sdfr			gss_buffer_desc mechname;
735181344Sdfr
736181344Sdfr			gss_oid_to_str(&min_stat, mech, &mechname);
737181344Sdfr
738181344Sdfr			log_debug("accepted context for %s with "
739181344Sdfr			    "<mech %.*s, qop %d, svc %d>",
740181344Sdfr			    client->cl_rawcred.client_principal->name,
741181344Sdfr			    mechname.length, (char *)mechname.value,
742181344Sdfr			    client->cl_qop, client->rawcred.service);
743181344Sdfr
744181344Sdfr			gss_release_buffer(&min_stat, &mechname);
745181344Sdfr		}
746181344Sdfr#endif /* DEBUG */
747181344Sdfr	}
748181344Sdfr	return (TRUE);
749181344Sdfr}
750181344Sdfr
751181344Sdfrstatic bool_t
752181344Sdfrsvc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
753181344Sdfr	gss_qop_t *qop)
754181344Sdfr{
755181344Sdfr	struct opaque_auth	*oa;
756181344Sdfr	gss_buffer_desc		 rpcbuf, checksum;
757181344Sdfr	OM_uint32		 maj_stat, min_stat;
758181344Sdfr	gss_qop_t		 qop_state;
759181348Sdfr	int32_t			 rpchdr[128 / sizeof(int32_t)];
760181344Sdfr	int32_t			*buf;
761181344Sdfr
762181344Sdfr	log_debug("in svc_rpc_gss_validate()");
763181344Sdfr
764181344Sdfr	memset(rpchdr, 0, sizeof(rpchdr));
765181344Sdfr
766181344Sdfr	/* Reconstruct RPC header for signing (from xdr_callmsg). */
767181348Sdfr	buf = rpchdr;
768181344Sdfr	IXDR_PUT_LONG(buf, msg->rm_xid);
769181344Sdfr	IXDR_PUT_ENUM(buf, msg->rm_direction);
770181344Sdfr	IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
771181344Sdfr	IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
772181344Sdfr	IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
773181344Sdfr	IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
774181344Sdfr	oa = &msg->rm_call.cb_cred;
775181344Sdfr	IXDR_PUT_ENUM(buf, oa->oa_flavor);
776181344Sdfr	IXDR_PUT_LONG(buf, oa->oa_length);
777181344Sdfr	if (oa->oa_length) {
778181344Sdfr		memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
779181344Sdfr		buf += RNDUP(oa->oa_length) / sizeof(int32_t);
780181344Sdfr	}
781181344Sdfr	rpcbuf.value = rpchdr;
782181348Sdfr	rpcbuf.length = (u_char *)buf - (u_char *)rpchdr;
783181344Sdfr
784181344Sdfr	checksum.value = msg->rm_call.cb_verf.oa_base;
785181344Sdfr	checksum.length = msg->rm_call.cb_verf.oa_length;
786181344Sdfr
787181344Sdfr	maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
788181344Sdfr				  &qop_state);
789181344Sdfr
790181344Sdfr	if (maj_stat != GSS_S_COMPLETE) {
791181344Sdfr		log_status("gss_verify_mic", client->cl_mech,
792181344Sdfr		    maj_stat, min_stat);
793181344Sdfr		client->cl_state = CLIENT_STALE;
794181344Sdfr		return (FALSE);
795181344Sdfr	}
796181344Sdfr	*qop = qop_state;
797181344Sdfr	return (TRUE);
798181344Sdfr}
799181344Sdfr
800181344Sdfrstatic bool_t
801181344Sdfrsvc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
802181344Sdfr    struct svc_req *rqst, u_int seq)
803181344Sdfr{
804181344Sdfr	gss_buffer_desc		signbuf;
805181344Sdfr	OM_uint32		maj_stat, min_stat;
806181344Sdfr	uint32_t		nseq;
807181344Sdfr
808181344Sdfr	log_debug("in svc_rpc_gss_nextverf()");
809181344Sdfr
810181344Sdfr	nseq = htonl(seq);
811181344Sdfr	signbuf.value = &nseq;
812181344Sdfr	signbuf.length = sizeof(nseq);
813181344Sdfr
814181344Sdfr	if (client->cl_verf.value)
815181344Sdfr		gss_release_buffer(&min_stat, &client->cl_verf);
816181344Sdfr
817181344Sdfr	maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
818181344Sdfr	    &signbuf, &client->cl_verf);
819181344Sdfr
820181344Sdfr	if (maj_stat != GSS_S_COMPLETE) {
821181344Sdfr		log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
822181344Sdfr		client->cl_state = CLIENT_STALE;
823181344Sdfr		return (FALSE);
824181344Sdfr	}
825181344Sdfr	rqst->rq_xprt->xp_verf.oa_flavor = RPCSEC_GSS;
826181344Sdfr	rqst->rq_xprt->xp_verf.oa_base = (caddr_t)client->cl_verf.value;
827181344Sdfr	rqst->rq_xprt->xp_verf.oa_length = (u_int)client->cl_verf.length;
828181344Sdfr
829181344Sdfr	return (TRUE);
830181344Sdfr}
831181344Sdfr
832181344Sdfrstatic bool_t
833181344Sdfrsvc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
834181344Sdfr{
835181344Sdfr	struct svc_rpc_gss_callback *scb;
836181344Sdfr	rpc_gss_lock_t	lock;
837181344Sdfr	void		*cookie;
838181344Sdfr	bool_t		cb_res;
839181344Sdfr	bool_t		result;
840181344Sdfr
841181344Sdfr	/*
842181344Sdfr	 * See if we have a callback for this guy.
843181344Sdfr	 */
844181344Sdfr	result = TRUE;
845181344Sdfr	SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
846181344Sdfr		if (scb->cb_callback.program == rqst->rq_prog
847181344Sdfr		    && scb->cb_callback.version == rqst->rq_vers) {
848181344Sdfr			/*
849181344Sdfr			 * This one matches. Call the callback and see
850181344Sdfr			 * if it wants to veto or something.
851181344Sdfr			 */
852181344Sdfr			lock.locked = FALSE;
853181344Sdfr			lock.raw_cred = &client->cl_rawcred;
854181344Sdfr			cb_res = scb->cb_callback.callback(rqst,
855181344Sdfr			    client->cl_creds,
856181344Sdfr			    client->cl_ctx,
857181344Sdfr			    &lock,
858181344Sdfr			    &cookie);
859181344Sdfr
860181344Sdfr			if (!cb_res) {
861181344Sdfr				client->cl_state = CLIENT_STALE;
862181344Sdfr				result = FALSE;
863181344Sdfr				break;
864181344Sdfr			}
865181344Sdfr
866181344Sdfr			/*
867181344Sdfr			 * The callback accepted the connection - it
868181344Sdfr			 * is responsible for freeing client->cl_creds
869181344Sdfr			 * now.
870181344Sdfr			 */
871181344Sdfr			client->cl_creds = GSS_C_NO_CREDENTIAL;
872181344Sdfr			client->cl_locked = lock.locked;
873181344Sdfr			client->cl_cookie = cookie;
874181344Sdfr			return (TRUE);
875181344Sdfr		}
876181344Sdfr	}
877181344Sdfr
878181344Sdfr	/*
879181344Sdfr	 * Either no callback exists for this program/version or one
880181344Sdfr	 * of the callbacks rejected the connection. We just need to
881181344Sdfr	 * clean up the delegated client creds, if any.
882181344Sdfr	 */
883181344Sdfr	if (client->cl_creds) {
884181344Sdfr		OM_uint32 min_ver;
885181344Sdfr		gss_release_cred(&min_ver, &client->cl_creds);
886181344Sdfr	}
887181344Sdfr	return (result);
888181344Sdfr}
889181344Sdfr
890181344Sdfrstatic bool_t
891181344Sdfrsvc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
892181344Sdfr{
893181344Sdfr	u_int32_t offset;
894181344Sdfr	int word, bit;
895181344Sdfr
896182758Sdfr	if (seq <= client->cl_seqlast) {
897181344Sdfr		/*
898181344Sdfr		 * The request sequence number is less than
899181344Sdfr		 * the largest we have seen so far. If it is
900181344Sdfr		 * outside the window or if we have seen a
901181344Sdfr		 * request with this sequence before, silently
902181344Sdfr		 * discard it.
903181344Sdfr		 */
904181344Sdfr		offset = client->cl_seqlast - seq;
905184588Sdfr		if (offset >= SVC_RPC_GSS_SEQWINDOW)
906181344Sdfr			return (FALSE);
907181344Sdfr		word = offset / 32;
908181344Sdfr		bit = offset % 32;
909181344Sdfr		if (client->cl_seqmask[word] & (1 << bit))
910181344Sdfr			return (FALSE);
911181344Sdfr	}
912181344Sdfr
913181344Sdfr	return (TRUE);
914181344Sdfr}
915181344Sdfr
916181344Sdfrstatic void
917181344Sdfrsvc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
918181344Sdfr{
919184588Sdfr	int offset, i, word, bit;
920181344Sdfr	uint32_t carry, newcarry;
921181344Sdfr
922181344Sdfr	if (seq > client->cl_seqlast) {
923181344Sdfr		/*
924181344Sdfr		 * This request has a sequence number greater
925181344Sdfr		 * than any we have seen so far. Advance the
926181344Sdfr		 * seq window and set bit zero of the window
927181344Sdfr		 * (which corresponds to the new sequence
928181344Sdfr		 * number)
929181344Sdfr		 */
930181344Sdfr		offset = seq - client->cl_seqlast;
931181344Sdfr		while (offset > 32) {
932181344Sdfr			for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
933181344Sdfr			     i > 0; i--) {
934181344Sdfr				client->cl_seqmask[i] = client->cl_seqmask[i-1];
935181344Sdfr			}
936181344Sdfr			client->cl_seqmask[0] = 0;
937181344Sdfr			offset -= 32;
938181344Sdfr		}
939181344Sdfr		carry = 0;
940181344Sdfr		for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
941181344Sdfr			newcarry = client->cl_seqmask[i] >> (32 - offset);
942181344Sdfr			client->cl_seqmask[i] =
943181344Sdfr				(client->cl_seqmask[i] << offset) | carry;
944181344Sdfr			carry = newcarry;
945181344Sdfr		}
946181344Sdfr		client->cl_seqmask[0] |= 1;
947181344Sdfr		client->cl_seqlast = seq;
948184588Sdfr	} else {
949184588Sdfr		offset = client->cl_seqlast - seq;
950184588Sdfr		word = offset / 32;
951184588Sdfr		bit = offset % 32;
952184588Sdfr		client->cl_seqmask[word] |= (1 << bit);
953181344Sdfr	}
954184588Sdfr
955181344Sdfr}
956181344Sdfr
957181344Sdfrenum auth_stat
958181344Sdfrsvc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
959181344Sdfr
960181344Sdfr{
961181344Sdfr	OM_uint32		 min_stat;
962181344Sdfr	XDR	 		 xdrs;
963181344Sdfr	struct svc_rpc_gss_client *client;
964181344Sdfr	struct rpc_gss_cred	 gc;
965181344Sdfr	struct rpc_gss_init_res	 gr;
966181344Sdfr	gss_qop_t		 qop;
967181344Sdfr	int			 call_stat;
968181344Sdfr	enum auth_stat		 result;
969181344Sdfr
970181344Sdfr	log_debug("in svc_rpc_gss()");
971181344Sdfr
972181344Sdfr	/* Garbage collect old clients. */
973181344Sdfr	svc_rpc_gss_timeout_clients();
974181344Sdfr
975181344Sdfr	/* Initialize reply. */
976181344Sdfr	rqst->rq_xprt->xp_verf = _null_auth;
977181344Sdfr
978181344Sdfr	/* Deserialize client credentials. */
979181344Sdfr	if (rqst->rq_cred.oa_length <= 0)
980181344Sdfr		return (AUTH_BADCRED);
981181344Sdfr
982181344Sdfr	memset(&gc, 0, sizeof(gc));
983181344Sdfr
984181344Sdfr	xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
985181344Sdfr	    rqst->rq_cred.oa_length, XDR_DECODE);
986181344Sdfr
987181344Sdfr	if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
988181344Sdfr		XDR_DESTROY(&xdrs);
989181344Sdfr		return (AUTH_BADCRED);
990181344Sdfr	}
991181344Sdfr	XDR_DESTROY(&xdrs);
992181344Sdfr
993181344Sdfr	/* Check version. */
994181344Sdfr	if (gc.gc_version != RPCSEC_GSS_VERSION) {
995181344Sdfr		result = AUTH_BADCRED;
996181344Sdfr		goto out;
997181344Sdfr	}
998181344Sdfr
999181344Sdfr	/* Check the proc and find the client (or create it) */
1000181344Sdfr	if (gc.gc_proc == RPCSEC_GSS_INIT) {
1001184588Sdfr		if (gc.gc_handle.length != 0) {
1002184588Sdfr			result = AUTH_BADCRED;
1003184588Sdfr			goto out;
1004184588Sdfr		}
1005181344Sdfr		client = svc_rpc_gss_create_client();
1006181344Sdfr	} else {
1007181344Sdfr		if (gc.gc_handle.length != sizeof(uint32_t)) {
1008181344Sdfr			result = AUTH_BADCRED;
1009181344Sdfr			goto out;
1010181344Sdfr		}
1011181344Sdfr		uint32_t *p = gc.gc_handle.value;
1012181344Sdfr		client = svc_rpc_gss_find_client(*p);
1013181344Sdfr		if (!client) {
1014181344Sdfr			/*
1015181344Sdfr			 * Can't find the client - we may have
1016181344Sdfr			 * destroyed it - tell the other side to
1017181344Sdfr			 * re-authenticate.
1018181344Sdfr			 */
1019181344Sdfr			result = RPCSEC_GSS_CREDPROBLEM;
1020181344Sdfr			goto out;
1021181344Sdfr		}
1022181344Sdfr	}
1023181344Sdfr	rqst->rq_clntcred = client;
1024181344Sdfr
1025181344Sdfr	/*
1026181344Sdfr	 * The service and sequence number must be ignored for
1027181344Sdfr	 * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
1028181344Sdfr	 */
1029181344Sdfr	if (gc.gc_proc != RPCSEC_GSS_INIT
1030181344Sdfr	    && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
1031181344Sdfr		/*
1032181344Sdfr		 * Check for sequence number overflow.
1033181344Sdfr		 */
1034181344Sdfr		if (gc.gc_seq >= MAXSEQ) {
1035181344Sdfr			result = RPCSEC_GSS_CTXPROBLEM;
1036181344Sdfr			goto out;
1037181344Sdfr		}
1038181344Sdfr		client->cl_seq = gc.gc_seq;
1039181344Sdfr
1040181344Sdfr		/*
1041181344Sdfr		 * Check for valid service.
1042181344Sdfr		 */
1043181344Sdfr		if (gc.gc_svc != rpc_gss_svc_none &&
1044181344Sdfr		    gc.gc_svc != rpc_gss_svc_integrity &&
1045181344Sdfr		    gc.gc_svc != rpc_gss_svc_privacy) {
1046181344Sdfr			result = AUTH_BADCRED;
1047181344Sdfr			goto out;
1048181344Sdfr		}
1049181344Sdfr	}
1050181344Sdfr
1051181344Sdfr	/* Handle RPCSEC_GSS control procedure. */
1052181344Sdfr	switch (gc.gc_proc) {
1053181344Sdfr
1054181344Sdfr	case RPCSEC_GSS_INIT:
1055181344Sdfr	case RPCSEC_GSS_CONTINUE_INIT:
1056181344Sdfr		if (rqst->rq_proc != NULLPROC) {
1057181344Sdfr			result = AUTH_REJECTEDCRED;
1058181344Sdfr			break;
1059181344Sdfr		}
1060181344Sdfr
1061181344Sdfr		memset(&gr, 0, sizeof(gr));
1062181344Sdfr		if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
1063181344Sdfr			result = AUTH_REJECTEDCRED;
1064181344Sdfr			break;
1065181344Sdfr		}
1066181344Sdfr
1067181344Sdfr		if (gr.gr_major == GSS_S_COMPLETE) {
1068181344Sdfr			if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
1069181344Sdfr				result = AUTH_REJECTEDCRED;
1070181344Sdfr				break;
1071181344Sdfr			}
1072181344Sdfr		} else {
1073181344Sdfr			rqst->rq_xprt->xp_verf.oa_flavor = AUTH_NULL;
1074181344Sdfr			rqst->rq_xprt->xp_verf.oa_length = 0;
1075181344Sdfr		}
1076181344Sdfr
1077181344Sdfr		call_stat = svc_sendreply(rqst->rq_xprt,
1078181344Sdfr		    (xdrproc_t) xdr_rpc_gss_init_res,
1079181344Sdfr		    (caddr_t) &gr);
1080181344Sdfr
1081181344Sdfr		gss_release_buffer(&min_stat, &gr.gr_token);
1082181344Sdfr
1083181344Sdfr		if (!call_stat) {
1084181344Sdfr			result = AUTH_FAILED;
1085181344Sdfr			break;
1086181344Sdfr		}
1087181344Sdfr
1088181344Sdfr		if (gr.gr_major == GSS_S_COMPLETE)
1089181344Sdfr			client->cl_state = CLIENT_ESTABLISHED;
1090181344Sdfr
1091181344Sdfr		result = RPCSEC_GSS_NODISPATCH;
1092181344Sdfr		break;
1093181344Sdfr
1094181344Sdfr	case RPCSEC_GSS_DATA:
1095181344Sdfr	case RPCSEC_GSS_DESTROY:
1096181344Sdfr		if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
1097181344Sdfr			result = RPCSEC_GSS_NODISPATCH;
1098181344Sdfr			break;
1099181344Sdfr		}
1100181344Sdfr
1101181344Sdfr		if (!svc_rpc_gss_validate(client, msg, &qop)) {
1102181344Sdfr			result = RPCSEC_GSS_CREDPROBLEM;
1103181344Sdfr			break;
1104181344Sdfr		}
1105181344Sdfr
1106181344Sdfr		if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
1107181344Sdfr			result = RPCSEC_GSS_CTXPROBLEM;
1108181344Sdfr			break;
1109181344Sdfr		}
1110181344Sdfr
1111181344Sdfr		svc_rpc_gss_update_seq(client, gc.gc_seq);
1112181344Sdfr
1113181344Sdfr		/*
1114181344Sdfr		 * Change the SVCAUTH ops on the transport to point at
1115181344Sdfr		 * our own code so that we can unwrap the arguments
1116181344Sdfr		 * and wrap the result. The caller will re-set this on
1117181344Sdfr		 * every request to point to a set of null wrap/unwrap
1118181344Sdfr		 * methods.
1119181344Sdfr		 */
1120181344Sdfr		SVC_AUTH(rqst->rq_xprt).svc_ah_ops = &svc_auth_gss_ops;
1121181344Sdfr		SVC_AUTH(rqst->rq_xprt).svc_ah_private = client;
1122181344Sdfr
1123181344Sdfr		if (gc.gc_proc == RPCSEC_GSS_DATA) {
1124181344Sdfr			/*
1125181344Sdfr			 * We might be ready to do a callback to the server to
1126181344Sdfr			 * see if it wants to accept/reject the connection.
1127181344Sdfr			 */
1128181344Sdfr			if (!client->cl_done_callback) {
1129181344Sdfr				client->cl_done_callback = TRUE;
1130181344Sdfr				client->cl_qop = qop;
1131181344Sdfr				client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1132181344Sdfr					client->cl_rawcred.mechanism, qop);
1133181344Sdfr				if (!svc_rpc_gss_callback(client, rqst)) {
1134181344Sdfr					result = AUTH_REJECTEDCRED;
1135181344Sdfr					break;
1136181344Sdfr				}
1137181344Sdfr			}
1138181344Sdfr
1139181344Sdfr			/*
1140181344Sdfr			 * If the server has locked this client to a
1141181344Sdfr			 * particular service+qop pair, enforce that
1142181344Sdfr			 * restriction now.
1143181344Sdfr			 */
1144181344Sdfr			if (client->cl_locked) {
1145181344Sdfr				if (client->cl_rawcred.service != gc.gc_svc) {
1146181344Sdfr					result = AUTH_FAILED;
1147181344Sdfr					break;
1148181344Sdfr				} else if (client->cl_qop != qop) {
1149181344Sdfr					result = AUTH_BADVERF;
1150181344Sdfr					break;
1151181344Sdfr				}
1152181344Sdfr			}
1153181344Sdfr
1154181344Sdfr			/*
1155181344Sdfr			 * If the qop changed, look up the new qop
1156181344Sdfr			 * name for rawcred.
1157181344Sdfr			 */
1158181344Sdfr			if (client->cl_qop != qop) {
1159181344Sdfr				client->cl_qop = qop;
1160181344Sdfr				client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1161181344Sdfr					client->cl_rawcred.mechanism, qop);
1162181344Sdfr			}
1163181344Sdfr
1164181344Sdfr			/*
1165181344Sdfr			 * Make sure we use the right service value
1166181344Sdfr			 * for unwrap/wrap.
1167181344Sdfr			 */
1168181344Sdfr			client->cl_rawcred.service = gc.gc_svc;
1169181344Sdfr
1170181344Sdfr			result = AUTH_OK;
1171181344Sdfr		} else {
1172181344Sdfr			if (rqst->rq_proc != NULLPROC) {
1173181344Sdfr				result = AUTH_REJECTEDCRED;
1174181344Sdfr				break;
1175181344Sdfr			}
1176181344Sdfr
1177181344Sdfr			call_stat = svc_sendreply(rqst->rq_xprt,
1178181344Sdfr			    (xdrproc_t) xdr_void, (caddr_t) NULL);
1179181344Sdfr
1180181344Sdfr			if (!call_stat) {
1181181344Sdfr				result = AUTH_FAILED;
1182181344Sdfr				break;
1183181344Sdfr			}
1184181344Sdfr
1185181344Sdfr			svc_rpc_gss_destroy_client(client);
1186181344Sdfr
1187181344Sdfr			result = RPCSEC_GSS_NODISPATCH;
1188181344Sdfr			break;
1189181344Sdfr		}
1190181344Sdfr		break;
1191181344Sdfr
1192181344Sdfr	default:
1193181344Sdfr		result = AUTH_BADCRED;
1194181344Sdfr		break;
1195181344Sdfr	}
1196181344Sdfrout:
1197181344Sdfr	xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
1198181344Sdfr	return (result);
1199181344Sdfr}
1200181344Sdfr
1201181344Sdfrbool_t
1202181344Sdfrsvc_rpc_gss_wrap(SVCAUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
1203181344Sdfr{
1204181344Sdfr	struct svc_rpc_gss_client *client;
1205181344Sdfr
1206181344Sdfr	log_debug("in svc_rpc_gss_wrap()");
1207181344Sdfr
1208181344Sdfr	client = (struct svc_rpc_gss_client *) auth->svc_ah_private;
1209181344Sdfr	if (client->cl_state != CLIENT_ESTABLISHED
1210181344Sdfr	    || client->cl_rawcred.service == rpc_gss_svc_none) {
1211181344Sdfr		return xdr_func(xdrs, xdr_ptr);
1212181344Sdfr	}
1213181344Sdfr	return (xdr_rpc_gss_wrap_data(xdrs, xdr_func, xdr_ptr,
1214181344Sdfr		client->cl_ctx, client->cl_qop,
1215181344Sdfr		client->cl_rawcred.service, client->cl_seq));
1216181344Sdfr}
1217181344Sdfr
1218181344Sdfrbool_t
1219181344Sdfrsvc_rpc_gss_unwrap(SVCAUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
1220181344Sdfr{
1221181344Sdfr	struct svc_rpc_gss_client *client;
1222181344Sdfr
1223181344Sdfr	log_debug("in svc_rpc_gss_unwrap()");
1224181344Sdfr
1225181344Sdfr	client = (struct svc_rpc_gss_client *) auth->svc_ah_private;
1226181344Sdfr	if (client->cl_state != CLIENT_ESTABLISHED
1227181344Sdfr	    || client->cl_rawcred.service == rpc_gss_svc_none) {
1228181344Sdfr		return xdr_func(xdrs, xdr_ptr);
1229181344Sdfr	}
1230181344Sdfr	return (xdr_rpc_gss_unwrap_data(xdrs, xdr_func, xdr_ptr,
1231181344Sdfr		client->cl_ctx, client->cl_qop,
1232181344Sdfr		client->cl_rawcred.service, client->cl_seq));
1233181344Sdfr}
1234